// Set a shader stage's resources and values
        private void SetShaderState(FetchTexture[] texs, FetchBuffer[] bufs,
            D3D11PipelineState.ShaderStage stage,
            Label shader, TreelistView.TreeListView resources, TreelistView.TreeListView samplers,
            TreelistView.TreeListView cbuffers, TreelistView.TreeListView classes)
        {
            ShaderReflection shaderDetails = stage.ShaderDetails;

            if (stage.Shader == ResourceId.Null)
                shader.Text = "Unbound";
            else
                shader.Text = stage.ShaderName;

            if (shaderDetails != null && shaderDetails.DebugInfo.entryFunc.Length > 0 && shaderDetails.DebugInfo.files.Length > 0)
            {
                string shaderfn = "";

                int entryFile = shaderDetails.DebugInfo.entryFile;
                if (entryFile < 0 || entryFile >= shaderDetails.DebugInfo.files.Length)
                    entryFile = 0;

                shaderfn = shaderDetails.DebugInfo.files[entryFile].BaseFilename;

                shader.Text = shaderDetails.DebugInfo.entryFunc + "()" + " - " + shaderfn;
            }

            int vs = 0;

            vs = resources.VScrollValue();
            resources.BeginUpdate();
            resources.Nodes.Clear();
            if (stage.SRVs != null)
            {
                int i = 0;
                foreach (var r in stage.SRVs)
                {
                    ShaderResource shaderInput = null;

                    if (shaderDetails != null)
                    {
                        foreach (var bind in shaderDetails.ReadOnlyResources)
                        {
                            if (bind.IsSRV && bind.bindPoint == i)
                            {
                                shaderInput = bind;
                                break;
                            }
                        }
                    }

                    bool filledSlot = (r.Resource != ResourceId.Null);
                    bool usedSlot = (shaderInput != null);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string slotname = i.ToString();

                        if (shaderInput != null && shaderInput.name.Length > 0)
                            slotname += ": " + shaderInput.name;

                        UInt64 w = 1;
                        UInt32 h = 1, d = 1;
                        UInt32 a = 1;
                        string format = "Unknown";
                        string name = "Shader Resource " + r.Resource.ToString();
                        string typename = "Unknown";
                        object tag = null;
                        bool viewDetails = false;

                        if (!filledSlot)
                        {
                            name = "Empty";
                            format = "-";
                            typename = "-";
                            w = h = d = a = 0;
                        }

                        // check to see if it's a texture
                        for (int t = 0; t < texs.Length; t++)
                        {
                            if (texs[t].ID == r.Resource)
                            {
                                w = texs[t].width;
                                h = texs[t].height;
                                d = texs[t].depth;
                                a = texs[t].arraysize;
                                format = texs[t].format.ToString();
                                name = texs[t].name;
                                typename = texs[t].resType.Str();

                                if (texs[t].resType == ShaderResourceType.Texture2DMS ||
                                    texs[t].resType == ShaderResourceType.Texture2DMSArray)
                                {
                                    typename += String.Format(" {0}x", texs[t].msSamp);
                                }

                                // if it's a typeless format, show the format of the view
                                if (texs[t].format != r.Format)
                                {
                                    format = "Viewed as " + r.Format.ToString();
                                }

                                tag = new ViewTexTag(r, texs[t]);

                                if (HasImportantViewParams(r, texs[t]))
                                    viewDetails = true;
                            }
                        }

                        // if not a texture, it must be a buffer
                        for (int t = 0; t < bufs.Length; t++)
                        {
                            if (bufs[t].ID == r.Resource)
                            {
                                w = bufs[t].length;
                                h = 0;
                                d = 0;
                                a = 0;
                                format = "";
                                name = bufs[t].name;
                                typename = "Buffer";

                                // for structured buffers, display how many 'elements' there are in the buffer
                                if (r.ElementSize > 0)
                                {
                                    typename = "StructuredBuffer[" + (bufs[t].length / r.ElementSize) + "]";
                                }
                                else if (r.Flags.HasFlag(D3D11BufferViewFlags.Raw))
                                {
                                    typename = "ByteAddressBuffer";
                                }

                                if (r.Flags.HasFlag(D3D11BufferViewFlags.Append) || r.Flags.HasFlag(D3D11BufferViewFlags.Counter))
                                {
                                    typename += " (Count: " + r.BufferStructCount + ")";
                                }

                                // get the buffer type, whether it's just a basic type or a complex struct
                                if (shaderInput != null && !shaderInput.IsTexture)
                                {
                                    if (r.Format.compType == FormatComponentType.None)
                                    {
                                        if (shaderInput.variableType.members.Length > 0)
                                            format = "struct " + shaderInput.variableType.Name;
                                        else
                                            format = shaderInput.variableType.Name;
                                    }
                                    else
                                    {
                                        format = r.Format.ToString();
                                    }
                                }

                                tag = new ViewBufTag(r, bufs[t]);

                                if (HasImportantViewParams(r, bufs[t]))
                                    viewDetails = true;
                            }
                        }

                        var node = resources.Nodes.Add(new object[] { slotname, name, typename, w, h, d, a, format });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = tag;

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);

                        if (viewDetails)
                            ViewDetailsRow(node);
                    }
                    i++;
                }
            }
            resources.EndUpdate();
            resources.NodesSelection.Clear();
            resources.SetVScrollValue(vs);

            vs = samplers.VScrollValue();
            samplers.BeginUpdate();
            samplers.Nodes.Clear();
            if (stage.Samplers != null)
            {
                int i = 0;
                foreach (var s in stage.Samplers)
                {
                    ShaderResource shaderInput = null;

                    if (shaderDetails != null)
                    {
                        foreach (var bind in shaderDetails.ReadOnlyResources)
                        {
                            if (bind.IsSampler && bind.bindPoint == i)
                            {
                                shaderInput = bind;
                                break;
                            }
                        }
                    }

                    bool filledSlot = (s.AddressU.Length > 0);
                    bool usedSlot = (shaderInput != null);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string slotname = i.ToString();

                        if (shaderInput != null && shaderInput.name.Length > 0)
                            slotname += ": " + shaderInput.name;

                        if (s.customSamplerName)
                            slotname += "(" + s.SamplerName + ")";

                        string borderColor = s.BorderColor[0].ToString() + ", " +
                                                s.BorderColor[1].ToString() + ", " +
                                                s.BorderColor[2].ToString() + ", " +
                                                s.BorderColor[3].ToString();

                        string addressing = "";

                        string addPrefix = "";
                        string addVal = "";

                        string[] addr = { s.AddressU, s.AddressV, s.AddressW };

                        // arrange like either UVW: WRAP or UV: WRAP, W: CLAMP
                        for (int a = 0; a < 3; a++)
                        {
                            string prefix = "" + "UVW"[a];

                            if (a == 0 || addr[a] == addr[a - 1])
                            {
                                addPrefix += prefix;
                            }
                            else
                            {
                                addressing += addPrefix + ": " + addVal + ", ";

                                addPrefix = prefix;
                            }
                            addVal = addr[a];
                        }

                        addressing += addPrefix + ": " + addVal;

                        if(s.UseBorder)
                            addressing += String.Format("<{0}>", borderColor);

                        string filter = s.Filter;

                        if (s.MaxAniso > 0)
                            filter += String.Format(" {0}x", s.MaxAniso);

                        if (s.UseComparison)
                            filter += String.Format(" ({0})", s.Comparison);

                        var node = samplers.Nodes.Add(new object[] { slotname, addressing,
                                                            filter,
                                                            (s.MinLOD == -float.MaxValue ? "0" : s.MinLOD.ToString()) + " - " +
                                                            (s.MaxLOD == float.MaxValue ? "FLT_MAX" : s.MaxLOD.ToString()),
                                                            s.MipLODBias.ToString() });

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }

                    i++;
                }
            }
            samplers.EndUpdate();
            samplers.NodesSelection.Clear();
            samplers.SetVScrollValue(vs);

            vs = cbuffers.VScrollValue();
            cbuffers.BeginUpdate();
            cbuffers.Nodes.Clear();
            if (stage.ConstantBuffers != null)
            {
                UInt32 i = 0;
                foreach (var b in stage.ConstantBuffers)
                {
                    ConstantBlock shaderCBuf = null;

                    if (shaderDetails != null && i < shaderDetails.ConstantBlocks.Length && shaderDetails.ConstantBlocks[i].name.Length > 0)
                        shaderCBuf = shaderDetails.ConstantBlocks[i];

                    bool filledSlot = (b.Buffer != ResourceId.Null);
                    bool usedSlot = (shaderCBuf != null);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string name = "Constant Buffer " + b.Buffer.ToString();
                        UInt64 length = 1;
                        int numvars = shaderCBuf != null ? shaderCBuf.variables.Length : 0;
                        UInt32 byteSize = shaderCBuf != null ? shaderCBuf.byteSize : 0;

                        if (!filledSlot)
                        {
                            name = "Empty";
                            length = 0;
                        }

                        for (int t = 0; t < bufs.Length; t++)
                        {
                            if (bufs[t].ID == b.Buffer)
                            {
                                name = bufs[t].name;
                                length = bufs[t].length;
                            }
                        }

                        string slotname = i.ToString();

                        if (shaderCBuf != null && shaderCBuf.name.Length > 0)
                            slotname += ": " + shaderCBuf.name;

                        string sizestr;
                        if (byteSize == length)
                            sizestr = String.Format("{0} Variables, {1} bytes", numvars, length);
                        else
                            sizestr = String.Format("{0} Variables, {1} bytes needed, {2} provided", numvars, byteSize, length);

                        if (length < byteSize)
                            filledSlot = false;

                        string vecrange = String.Format("{0} - {1}", b.VecOffset, b.VecOffset + b.VecCount);

                        var node = cbuffers.Nodes.Add(new object[] { slotname, name, vecrange, sizestr });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = i;

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }
                    i++;
                }
            }
            cbuffers.EndUpdate();
            cbuffers.NodesSelection.Clear();
            cbuffers.SetVScrollValue(vs);

            vs = classes.VScrollValue();
            classes.BeginUpdate();
            classes.Nodes.Clear();
            {
                UInt32 i = 0;
                foreach (var inst in stage.ClassInstances)
                {
                    string interfaceName = String.Format("Interface {0}", i);

                    if (shaderDetails != null && i < shaderDetails.Interfaces.Length)
                        interfaceName = shaderDetails.Interfaces[i].Name;

                    classes.Nodes.Add(new object[] { i.ToString(), interfaceName, inst });

                    i++;
                }
            }
            classes.EndUpdate();
            classes.NodesSelection.Clear();
            classes.SetVScrollValue(vs);

            classes.Visible = classes.Parent.Visible = (stage.ClassInstances.Length > 0);
        }
        // Set a shader stage's resources and values
        private void SetShaderState(FetchTexture[] texs, FetchBuffer[] bufs,
            GLPipelineState state, GLPipelineState.ShaderStage stage,
            TableLayoutPanel table, Label shader,
            TreelistView.TreeListView textures, TreelistView.TreeListView samplers,
            TreelistView.TreeListView cbuffers, TreelistView.TreeListView subs,
            TreelistView.TreeListView readwrites)
        {
            ShaderReflection shaderDetails = stage.ShaderDetails;
            var mapping = stage.BindpointMapping;

            if (stage.Shader == ResourceId.Null)
                shader.Text = "Unbound";
            else
                shader.Text = stage.stage.Str(APIPipelineStateType.OpenGL) + " Shader " + stage.Shader.ToString();

            // disabled since entry function is always main, and filenames have no names, so this is useless.
            /*
            if (shaderDetails != null && shaderDetails.DebugInfo.entryFunc != "" && shaderDetails.DebugInfo.files.Length > 0)
                shader.Text = shaderDetails.DebugInfo.entryFunc + "()" + " - " +
                                Path.GetFileName(shaderDetails.DebugInfo.files[0].filename);
             */

            int vs = 0;
            int vs2 = 0;

            // simultaneous update of resources and samplers
            vs = textures.VScrollValue();
            textures.BeginUpdate();
            textures.Nodes.Clear();
            vs2 = samplers.VScrollValue();
            samplers.BeginUpdate();
            samplers.Nodes.Clear();
            if (state.Textures != null)
            {
                for (int i = 0; i < state.Textures.Length; i++)
                {
                    var r = state.Textures[i];
                    var s = state.Samplers[i];

                    ShaderResource shaderInput = null;
                    BindpointMap map = null;

                    if (shaderDetails != null)
                    {
                        foreach (var bind in shaderDetails.Resources)
                        {
                            if (bind.IsSRV && !bind.IsReadWrite && mapping.Resources[bind.bindPoint].bind == i)
                            {
                                shaderInput = bind;
                                map = mapping.Resources[bind.bindPoint];
                            }
                        }
                    }

                    bool filledSlot = (r.Resource != ResourceId.Null);
                    bool usedSlot = (shaderInput != null && map.used);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        // do texture
                        {
                            string slotname = i.ToString();

                            if (shaderInput != null && shaderInput.name != "")
                                slotname += ": " + shaderInput.name;

                            UInt32 w = 1, h = 1, d = 1;
                            UInt32 a = 1;
                            string format = "Unknown";
                            string name = "Shader Resource " + r.Resource.ToString();
                            string typename = "Unknown";
                            object tag = null;

                            if (!filledSlot)
                            {
                                name = "Empty";
                                format = "-";
                                typename = "-";
                                w = h = d = a = 0;
                            }

                            // check to see if it's a texture
                            for (int t = 0; t < texs.Length; t++)
                            {
                                if (texs[t].ID == r.Resource)
                                {
                                    w = texs[t].width;
                                    h = texs[t].height;
                                    d = texs[t].depth;
                                    a = texs[t].arraysize;
                                    format = texs[t].format.ToString();
                                    name = texs[t].name;
                                    typename = texs[t].resType.Str();

                                    if (texs[t].format.special &&
                                        (texs[t].format.specialFormat == SpecialFormat.D24S8 ||
                                         texs[t].format.specialFormat == SpecialFormat.D32S8)
                                        )
                                    {
                                        if (r.DepthReadChannel == 0)
                                            format += " Depth-Read";
                                        else if (r.DepthReadChannel == 1)
                                            format += " Stencil-Read";
                                    }
                                    else if (
                                        r.Swizzle[0] != TextureSwizzle.Red ||
                                        r.Swizzle[1] != TextureSwizzle.Green ||
                                        r.Swizzle[2] != TextureSwizzle.Blue ||
                                        r.Swizzle[3] != TextureSwizzle.Alpha)
                                    {
                                        format += String.Format(" swizzle[{0}{1}{2}{3}]",
                                            r.Swizzle[0].Str(),
                                            r.Swizzle[1].Str(),
                                            r.Swizzle[2].Str(),
                                            r.Swizzle[3].Str());
                                    }

                                    tag = texs[t];
                                }
                            }

                            var node = textures.Nodes.Add(new object[] { slotname, name, typename, w, h, d, a, format });

                            node.Image = global::renderdocui.Properties.Resources.action;
                            node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                            node.Tag = tag;

                            if (!filledSlot)
                                EmptyRow(node);

                            if (!usedSlot)
                                InactiveRow(node);
                        }

                        // do sampler
                        {
                            string slotname = i.ToString();

                            if (shaderInput != null && shaderInput.name.Length > 0)
                                slotname += ": " + shaderInput.name;

                            string borderColor = s.BorderColor[0].ToString() + ", " +
                                                    s.BorderColor[1].ToString() + ", " +
                                                    s.BorderColor[2].ToString() + ", " +
                                                    s.BorderColor[3].ToString();

                            string addressing = "";

                            string addPrefix = "";
                            string addVal = "";

                            string[] addr = { s.AddressS, s.AddressT, s.AddressR };

                            // arrange like either STR: WRAP or ST: WRAP, R: CLAMP
                            for (int a = 0; a < 3; a++)
                            {
                                string prefix = "" + "STR"[a];

                                if (a == 0 || addr[a] == addr[a - 1])
                                {
                                    addPrefix += prefix;
                                }
                                else
                                {
                                    addressing += addPrefix + ": " + addVal + ", ";

                                    addPrefix = prefix;
                                }
                                addVal = addr[a];
                            }

                            addressing += addPrefix + ": " + addVal;

                            if (s.UseBorder)
                                addressing += String.Format("<{0}>", borderColor);

                            if (r.ResType == ShaderResourceType.TextureCube ||
                                r.ResType == ShaderResourceType.TextureCubeArray)
                            {
                                addressing += s.SeamlessCube ? " Seamless" : " Non-Seamless";
                            }

                            string minfilter = s.MinFilter;

                            if (s.MaxAniso > 1)
                                minfilter += String.Format(" Aniso{0}x", s.MaxAniso);

                            if (s.UseComparison)
                                minfilter = String.Format("{0}", s.Comparison);

                            var node = samplers.Nodes.Add(new object[] { slotname, addressing,
                                                            minfilter, s.MagFilter,
                                                            (s.MinLOD == -float.MaxValue ? "0" : s.MinLOD.ToString()) + " - " +
                                                            (s.MaxLOD == float.MaxValue ? "FLT_MAX" : s.MaxLOD.ToString()),
                                                            s.MipLODBias.ToString() });

                            if (!filledSlot)
                                EmptyRow(node);

                            if (!usedSlot)
                                InactiveRow(node);
                        }
                    }
                }
            }
            textures.EndUpdate();
            textures.NodesSelection.Clear();
            textures.SetVScrollValue(vs);
            samplers.EndUpdate();
            samplers.NodesSelection.Clear();
            samplers.SetVScrollValue(vs2);

            vs = cbuffers.VScrollValue();
            cbuffers.BeginUpdate();
            cbuffers.Nodes.Clear();
            if (shaderDetails != null)
            {
                UInt32 i = 0;
                foreach (var shaderCBuf in shaderDetails.ConstantBlocks)
                {
                    int bindPoint = stage.BindpointMapping.ConstantBlocks[i].bind;

                    GLPipelineState.Buffer b = null;

                    if (bindPoint >= 0 && bindPoint < state.UniformBuffers.Length)
                        b = state.UniformBuffers[bindPoint];

                    bool filledSlot = !shaderCBuf.bufferBacked ||
                        (b != null && b.Resource != ResourceId.Null);
                    bool usedSlot = stage.BindpointMapping.ConstantBlocks[i].used;

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        ulong offset = 0;
                        ulong length = 0;
                        int numvars = shaderCBuf.variables.Length;

                        string slotname = "Uniforms";
                        string name = "";
                        string sizestr = String.Format("{0} Variables", numvars);
                        string byterange = "";

                        if (!filledSlot)
                        {
                            name = "Empty";
                            length = 0;
                        }

                        if (b != null)
                        {
                            slotname = String.Format("{0}: {1}", bindPoint, shaderCBuf.name);
                            name = "UBO " + b.Resource.ToString();
                            offset = b.Offset;
                            length = b.Size;

                            for (int t = 0; t < bufs.Length; t++)
                            {
                                if (bufs[t].ID == b.Resource)
                                {
                                    name = bufs[t].name;
                                    if (length == 0)
                                        length = bufs[t].length;
                                }
                            }

                            sizestr = String.Format("{0} Variables, {1} bytes", numvars, length);
                            byterange = String.Format("{0} - {1}", offset, offset + length);
                        }

                        var node = cbuffers.Nodes.Add(new object[] { slotname, name, byterange, sizestr });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = i;

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }
                    i++;
                }
            }
            cbuffers.EndUpdate();
            cbuffers.NodesSelection.Clear();
            cbuffers.SetVScrollValue(vs);

            vs = subs.VScrollValue();
            subs.BeginUpdate();
            subs.Nodes.Clear();
            {
                UInt32 i = 0;
                foreach (var subval in stage.Subroutines)
                {
                    subs.Nodes.Add(new object[] { i.ToString(), subval.ToString() });

                    i++;
                }
            }
            subs.EndUpdate();
            subs.NodesSelection.Clear();
            subs.SetVScrollValue(vs);

            {
                subs.Visible = subs.Parent.Visible = (stage.Subroutines.Length > 0);
                int row = table.GetRow(subs.Parent);
                if (row >= 0 && row < table.RowStyles.Count)
                {
                    if (stage.Subroutines.Length > 0)
                        table.RowStyles[row].Height = table.RowStyles[1].Height;
                    else
                        table.RowStyles[row].Height = 0;
                }
            }

            vs = readwrites.VScrollValue();
            readwrites.BeginUpdate();
            readwrites.Nodes.Clear();
            if (shaderDetails != null)
            {
                UInt32 i = 0;
                foreach (var res in shaderDetails.Resources)
                {
                    int bindPoint = stage.BindpointMapping.Resources[i].bind;

                    bool atomic = false;
                    bool ssbo = false;
                    bool image = false;

                    if (!res.IsReadWrite)
                    {
                        i++;
                        continue;
                    }

                    GLPipelineState.Buffer bf = null;
                    GLPipelineState.ImageLoadStore im = null;
                    ResourceId id = ResourceId.Null;

                    if (res.IsTexture)
                    {
                        image = true;
                        if (bindPoint >= 0 && bindPoint < state.Images.Length)
                        {
                            im = state.Images[bindPoint];
                            id = state.Images[bindPoint].Resource;
                        }
                    }
                    else
                    {
                        if (res.variableType.descriptor.rows == 1 &&
                            res.variableType.descriptor.cols == 1 &&
                            res.variableType.descriptor.type == VarType.UInt)
                        {
                            atomic = true;
                            if (bindPoint >= 0 && bindPoint < state.AtomicBuffers.Length)
                            {
                                bf = state.AtomicBuffers[bindPoint];
                                id = state.AtomicBuffers[bindPoint].Resource;
                            }
                        }
                        else
                        {
                            ssbo = true;
                            if (bindPoint >= 0 && bindPoint < state.ShaderStorageBuffers.Length)
                            {
                                bf = state.ShaderStorageBuffers[bindPoint];
                                id = state.ShaderStorageBuffers[bindPoint].Resource;
                            }
                        }
                    }

                    bool filledSlot = id != ResourceId.Null;
                    bool usedSlot = stage.BindpointMapping.Resources[i].used;

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string binding = image ? "Image" :
                            atomic ? "Atomic" :
                            ssbo ? "SSBO" :
                            "Unknown";

                        string slotname = String.Format("{0}: {1}", bindPoint, res.name);
                        string name = "";
                        string dimensions = "";
                        string format = "-";
                        string access = "Read/Write";
                        if (im != null)
                        {
                            if (im.readAllowed && !im.writeAllowed) access = "Read-Only";
                            if (!im.readAllowed && im.writeAllowed) access = "Write-Only";
                            format = im.Format.ToString();
                        }

                        object tag = null;

                        // check to see if it's a texture
                        for (int t = 0; t < texs.Length; t++)
                        {
                            if (texs[t].ID == id)
                            {
                                if (texs[t].dimension == 1)
                                {
                                    if(texs[t].arraysize > 1)
                                        dimensions = String.Format("{0}[{1}]", texs[t].width, texs[t].arraysize);
                                    else
                                        dimensions = String.Format("{0}", texs[t].width);
                                }
                                else if (texs[t].dimension == 2)
                                {
                                    if (texs[t].arraysize > 1)
                                        dimensions = String.Format("{0}x{1}[{2}]", texs[t].width, texs[t].height, texs[t].arraysize);
                                    else
                                        dimensions = String.Format("{0}x{1}", texs[t].width, texs[t].height);
                                }
                                else if (texs[t].dimension == 3)
                                {
                                    dimensions = String.Format("{0}x{1}x{2}", texs[t].width, texs[t].height, texs[t].depth);
                                }

                                name = texs[t].name;

                                tag = texs[t];
                            }
                        }

                        // if not a texture, it must be a buffer
                        for (int t = 0; t < bufs.Length; t++)
                        {
                            if (bufs[t].ID == id)
                            {
                                ulong offset = 0;
                                ulong length = bufs[t].length;
                                if (bf != null && bf.Size > 0)
                                {
                                    offset = bf.Offset;
                                    length = bf.Size;
                                }

                                if(offset > 0)
                                    dimensions = String.Format("{0} bytes at offset {1} bytes", length, offset);
                                else
                                    dimensions = String.Format("{0} bytes", length);

                                name = bufs[t].name;

                                tag = new ReadWriteTag(i, bufs[t]);
                            }
                        }

                        if (!filledSlot)
                        {
                            name = "Empty";
                            dimensions = "-";
                            access = "-";
                        }

                        var node = readwrites.Nodes.Add(new object[] { binding, slotname, name, dimensions, format, access });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = tag;

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }
                    i++;
                }
            }
            readwrites.EndUpdate();
            readwrites.NodesSelection.Clear();
            readwrites.SetVScrollValue(vs);

            {
                readwrites.Visible = readwrites.Parent.Visible = (readwrites.Nodes.Count > 0);
                int row = table.GetRow(readwrites.Parent);
                if (row >= 0 && row < table.RowStyles.Count)
                {
                    if (readwrites.Nodes.Count > 0)
                        table.RowStyles[row].Height = table.RowStyles[1].Height;
                    else
                        table.RowStyles[row].Height = 0;
                }
            }
        }
        // Set a shader stage's resources and values
        private void SetShaderState(FetchTexture[] texs, FetchBuffer[] bufs,
            VulkanPipelineState.ShaderStage stage, VulkanPipelineState.Pipeline pipe,
            Label shader, TreelistView.TreeListView resources,
            TreelistView.TreeListView cbuffers)
        {
            ShaderReflection shaderDetails = stage.ShaderDetails;

            if (stage.Shader == ResourceId.Null)
                shader.Text = "Unbound";
            else
                shader.Text = stage.ShaderName;

            if (shaderDetails != null && shaderDetails.DebugInfo.entryFunc.Length > 0)
            {
                if (shaderDetails.DebugInfo.files.Length > 0 || shaderDetails.DebugInfo.entryFunc != "main")
                    shader.Text = shaderDetails.DebugInfo.entryFunc + "()";

                if (shaderDetails.DebugInfo.files.Length > 0)
                {
                    string shaderfn = "";

                    int entryFile = shaderDetails.DebugInfo.entryFile;
                    if (entryFile < 0 || entryFile >= shaderDetails.DebugInfo.files.Length)
                        entryFile = 0;

                    shaderfn = shaderDetails.DebugInfo.files[entryFile].BaseFilename;

                    shader.Text += " - " + shaderfn;
                }
            }

            int vs = 0;

            vs = resources.VScrollValue();
            resources.BeginUpdate();
            resources.Nodes.Clear();

            var samplers = new Dictionary<ResourceId, SamplerData>();

            for(int bindset = 0; bindset < pipe.DescSets.Length; bindset++)
            {
                for(int bind = 0; bind < pipe.DescSets[bindset].bindings.Length; bind++)
                {
                    AddResourceRow(shaderDetails, stage, bindset, bind, pipe, resources, texs, bufs, ref samplers);
                }

                // if we have a shader bound, go through and add rows for any resources it wants for binds that aren't
                // in this descriptor set (e.g. if layout mismatches)
                if (shaderDetails != null)
                {
                    for (int i = 0; i < shaderDetails.ReadOnlyResources.Length; i++)
                    {
                        var ro = shaderDetails.ReadOnlyResources[i];

                        if (stage.BindpointMapping.ReadOnlyResources[ro.bindPoint].bindset == bindset &&
                            stage.BindpointMapping.ReadOnlyResources[ro.bindPoint].bind >= pipe.DescSets[bindset].bindings.Length)
                        {
                            AddResourceRow(shaderDetails, stage, bindset,
                                stage.BindpointMapping.ReadOnlyResources[ro.bindPoint].bind,
                                pipe, resources, texs, bufs, ref samplers);
                        }
                    }

                    for (int i = 0; i < shaderDetails.ReadWriteResources.Length; i++)
                    {
                        var rw = shaderDetails.ReadWriteResources[i];

                        if (stage.BindpointMapping.ReadWriteResources[rw.bindPoint].bindset == bindset &&
                            stage.BindpointMapping.ReadWriteResources[rw.bindPoint].bind >= pipe.DescSets[bindset].bindings.Length)
                        {
                            AddResourceRow(shaderDetails, stage, bindset,
                                stage.BindpointMapping.ReadWriteResources[rw.bindPoint].bind,
                                pipe, resources, texs, bufs, ref samplers);
                        }
                    }
                }
            }

            // if we have a shader bound, go through and add rows for any resources it wants for descriptor sets that aren't
            // bound at all
            if (shaderDetails != null)
            {
                for (int i = 0; i < shaderDetails.ReadOnlyResources.Length; i++)
                {
                    var ro = shaderDetails.ReadOnlyResources[i];

                    if (stage.BindpointMapping.ReadOnlyResources[ro.bindPoint].bindset >= pipe.DescSets.Length)
                    {
                        AddResourceRow(shaderDetails, stage,
                            stage.BindpointMapping.ReadOnlyResources[ro.bindPoint].bindset,
                            stage.BindpointMapping.ReadOnlyResources[ro.bindPoint].bind,
                            pipe, resources, texs, bufs, ref samplers);
                    }
                }

                for (int i = 0; i < shaderDetails.ReadWriteResources.Length; i++)
                {
                    var rw = shaderDetails.ReadWriteResources[i];

                    if (stage.BindpointMapping.ReadWriteResources[rw.bindPoint].bindset >= pipe.DescSets.Length)
                    {
                        AddResourceRow(shaderDetails, stage,
                            stage.BindpointMapping.ReadWriteResources[rw.bindPoint].bindset,
                            stage.BindpointMapping.ReadWriteResources[rw.bindPoint].bind,
                            pipe, resources, texs, bufs, ref samplers);
                    }
                }
            }

            resources.EndUpdate();
            resources.NodesSelection.Clear();
            resources.SetVScrollValue(vs);

            vs = cbuffers.VScrollValue();
            cbuffers.BeginUpdate();
            cbuffers.Nodes.Clear();
            for(int bindset = 0; bindset < pipe.DescSets.Length; bindset++)
            {
                for(int bind = 0; bind < pipe.DescSets[bindset].bindings.Length; bind++)
                {
                    AddConstantBlockRow(shaderDetails, stage, bindset, bind, pipe, cbuffers, bufs);
                }

                // if we have a shader bound, go through and add rows for any cblocks it wants for binds that aren't
                // in this descriptor set (e.g. if layout mismatches)
                if (shaderDetails != null)
                {
                    for (int i = 0; i < shaderDetails.ConstantBlocks.Length; i++)
                    {
                        var cb = shaderDetails.ConstantBlocks[i];

                        if (stage.BindpointMapping.ConstantBlocks[cb.bindPoint].bindset == bindset &&
                            stage.BindpointMapping.ConstantBlocks[cb.bindPoint].bind >= pipe.DescSets[bindset].bindings.Length)
                        {
                            AddConstantBlockRow(shaderDetails, stage, bindset,
                                stage.BindpointMapping.ConstantBlocks[cb.bindPoint].bind,
                                pipe, cbuffers, bufs);
                        }
                    }
                }
            }

            // if we have a shader bound, go through and add rows for any resources it wants for descriptor sets that aren't
            // bound at all
            if (shaderDetails != null)
            {
                for (int i = 0; i < shaderDetails.ConstantBlocks.Length; i++)
                {
                    var cb = shaderDetails.ConstantBlocks[i];

                    if (stage.BindpointMapping.ConstantBlocks[cb.bindPoint].bindset >= pipe.DescSets.Length && cb.bufferBacked)
                    {
                        AddConstantBlockRow(shaderDetails, stage,
                            stage.BindpointMapping.ConstantBlocks[cb.bindPoint].bindset,
                            stage.BindpointMapping.ConstantBlocks[cb.bindPoint].bind,
                            pipe, cbuffers, bufs);
                    }
                }
            }

            // search for push constants and add them last
            if (shaderDetails != null)
            {
                for (int cb = 0; cb < shaderDetails.ConstantBlocks.Length; cb++)
                {
                    var cblock = shaderDetails.ConstantBlocks[cb];
                    if (cblock.bufferBacked == false)
                    {
                        // could maybe get range from ShaderVariable.reg if it's filled out
                        // from SPIR-V side.

                        var node = cbuffers.Nodes.Add(new object[] { "", "",
                        cblock.name, "Push constants",
                        "", String.Format("{0} Variables", cblock.variables.Length) });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = new CBufferTag((uint)cb, 0);
                    }
                }
            }
            cbuffers.EndUpdate();
            cbuffers.NodesSelection.Clear();
            cbuffers.SetVScrollValue(vs);
        }
        // Set a shader stage's resources and values
        private void SetShaderState(D3D12PipelineState.ShaderStage stage,
            Label shader, TreelistView.TreeListView resources, TreelistView.TreeListView samplers,
            TreelistView.TreeListView cbuffers, TreelistView.TreeListView uavs)
        {
            FetchTexture[] texs = m_Core.CurTextures;
            FetchBuffer[] bufs = m_Core.CurBuffers;

            D3D12PipelineState state = m_Core.CurD3D12PipelineState;
            ShaderReflection shaderDetails = stage.ShaderDetails;
            ShaderBindpointMapping bindpointMapping = stage.BindpointMapping;

            if (stage.Shader == ResourceId.Null)
                shader.Text = "Unbound";
            else if (state.customName)
                shader.Text = state.PipelineName + " - " + m_Core.CurPipelineState.Abbrev(stage.stage);
            else
                shader.Text = state.PipelineName + " - " + stage.stage.Str(GraphicsAPI.D3D12) + " Shader";

            if (shaderDetails != null && shaderDetails.DebugInfo.entryFunc.Length > 0 && shaderDetails.DebugInfo.files.Length > 0)
            {
                string shaderfn = "";

                int entryFile = shaderDetails.DebugInfo.entryFile;
                if (entryFile < 0 || entryFile >= shaderDetails.DebugInfo.files.Length)
                    entryFile = 0;

                shaderfn = shaderDetails.DebugInfo.files[entryFile].BaseFilename;

                shader.Text = shaderDetails.DebugInfo.entryFunc + "()" + " - " + shaderfn;
            }

            int vs = 0;

            vs = resources.VScrollValue();
            resources.BeginUpdate();
            resources.Nodes.Clear();
            for (int space = 0; space < stage.Spaces.Length; space++)
            {
                for (int reg = 0; reg < stage.Spaces[space].SRVs.Length; reg++)
                {
                    AddResourceRow(stage, resources, space, reg, false);
                }
            }
            resources.EndUpdate();
            resources.NodesSelection.Clear();
            resources.SetVScrollValue(vs);

            vs = uavs.VScrollValue();
            uavs.BeginUpdate();
            uavs.Nodes.Clear();
            for (int space = 0; space < stage.Spaces.Length; space++)
            {
                for (int reg = 0; reg < stage.Spaces[space].UAVs.Length; reg++)
                {
                    AddResourceRow(stage, uavs, space, reg, true);
                }
            }
            uavs.EndUpdate();
            uavs.NodesSelection.Clear();
            uavs.SetVScrollValue(vs);

            vs = samplers.VScrollValue();
            samplers.BeginUpdate();
            samplers.Nodes.Clear();
            for (int space = 0; space < stage.Spaces.Length; space++)
            {
                for (int reg = 0; reg < stage.Spaces[space].Samplers.Length; reg++)
                {
                    D3D12PipelineState.Sampler s = stage.Spaces[space].Samplers[reg];

                    // consider this register to not exist - it's in a gap defined by sparse root signature elements
                    if (s.RootElement == uint.MaxValue)
                        continue;

                    BindpointMap bind = null;
                    ShaderResource shaderInput = null;

                    if (stage.BindpointMapping != null && stage.ShaderDetails != null)
                    {
                        for (int i = 0; i < stage.BindpointMapping.ReadOnlyResources.Length; i++)
                        {
                            var b = stage.BindpointMapping.ReadOnlyResources[i];
                            var res = stage.ShaderDetails.ReadOnlyResources[i];

                            bool regMatch = b.bind == reg;

                            // handle unbounded arrays specially. It's illegal to have an unbounded array with
                            // anything after it
                            if (b.bind <= reg)
                                regMatch = (b.arraySize == UInt32.MaxValue) || (b.bind + b.arraySize > reg);

                            if (b.bindset == space && regMatch && res.IsSampler)
                            {
                                bind = b;
                                shaderInput = res;
                                break;
                            }
                        }
                    }

                    string rootel = s.Immediate ? String.Format("#{0} Static", s.RootElement) : String.Format("#{0} Table[{1}]", s.RootElement, s.TableIndex);

                    bool filledSlot = (s.AddressU.Length > 0);
                    bool usedSlot = (bind != null && bind.used);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string regname = reg.ToString();

                        if (shaderInput != null && shaderInput.name.Length > 0)
                            regname += ": " + shaderInput.name;

                        string borderColor = "";

                        string addressing = "";

                        string addPrefix = "";
                        string addVal = "";

                        string[] addr = { "", "", "" };

                        if (s != null)
                        {
                            borderColor = s.BorderColor[0].ToString() + ", " +
                                          s.BorderColor[1].ToString() + ", " +
                                          s.BorderColor[2].ToString() + ", " +
                                          s.BorderColor[3].ToString();

                            addr[0] = s.AddressU;
                            addr[1] = s.AddressV;
                            addr[2] = s.AddressW;
                        }

                        // arrange like either UVW: WRAP or UV: WRAP, W: CLAMP
                        for (int a = 0; a < 3; a++)
                        {
                            string prefix = "" + "UVW"[a];

                            if (a == 0 || addr[a] == addr[a - 1])
                            {
                                addPrefix += prefix;
                            }
                            else
                            {
                                addressing += addPrefix + ": " + addVal + ", ";

                                addPrefix = prefix;
                            }
                            addVal = addr[a];
                        }

                        addressing += addPrefix + ": " + addVal;

                        string filter = "";
                        string lodclamp = "";
                        float lodbias = 0.0f;

                        if (s != null)
                        {
                            if (s.UseBorder)
                                addressing += String.Format("<{0}>", borderColor);

                            filter = s.Filter;

                            if (s.MaxAniso > 0)
                                filter += String.Format(" {0}x", s.MaxAniso);

                            if (s.UseComparison)
                                filter += String.Format(" ({0})", s.Comparison);

                            lodclamp = (s.MinLOD == -float.MaxValue ? "0" : s.MinLOD.ToString()) + " - " +
                                       (s.MaxLOD == float.MaxValue ? "FLT_MAX" : s.MaxLOD.ToString());

                            lodbias = s.MipLODBias;
                        }

                        var node = samplers.Nodes.Add(new object[] { rootel, space, regname, addressing,
                                                                     filter, lodclamp, lodbias.ToString() });

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }
                }
            }
            samplers.EndUpdate();
            samplers.NodesSelection.Clear();
            samplers.SetVScrollValue(vs);

            vs = cbuffers.VScrollValue();
            cbuffers.BeginUpdate();
            cbuffers.Nodes.Clear();
            for (int space = 0; space < stage.Spaces.Length; space++)
            {
                for (int reg = 0; reg < stage.Spaces[space].ConstantBuffers.Length; reg++)
                {
                    D3D12PipelineState.CBuffer b = stage.Spaces[space].ConstantBuffers[reg];

                    // consider this register to not exist - it's in a gap defined by sparse root signature elements
                    if (b.RootElement == uint.MaxValue)
                        continue;

                    BindpointMap bind = null;
                    ConstantBlock shaderCBuf = null;

                    object tag = null;

                    if (stage.BindpointMapping != null && stage.ShaderDetails != null)
                    {
                        for (int i = 0; i < stage.BindpointMapping.ConstantBlocks.Length; i++)
                        {
                            var bd = stage.BindpointMapping.ConstantBlocks[i];
                            var res = stage.ShaderDetails.ConstantBlocks[i];

                            bool regMatch = bd.bind == reg;

                            // handle unbounded arrays specially. It's illegal to have an unbounded array with
                            // anything after it
                            if (bd.bind <= reg)
                                regMatch = (bd.arraySize == UInt32.MaxValue) || (bd.bind + bd.arraySize > reg);

                            if (bd.bindset == space && regMatch)
                            {
                                bind = bd;
                                shaderCBuf = res;
                                tag = new CBufTag((uint)i);
                                break;
                            }
                        }
                    }

                    if(tag == null)
                        tag = new CBufTag(space, reg);

                    string rootel;

                    if (b.Immediate)
                    {
                        if (b.RootValues.Length > 0)
                            rootel = String.Format("#{0} Consts", b.RootElement);
                        else
                            rootel = String.Format("#{0} Direct", b.RootElement);
                    }
                    else
                    {
                        rootel = String.Format("#{0} Table[{1}]", b.RootElement, b.TableIndex);
                    }

                    bool filledSlot = (b.Buffer != ResourceId.Null);
                    if (b.Immediate && b.RootValues.Length > 0)
                        filledSlot = true;

                    bool usedSlot = (bind != null && bind.used);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string name = "Constant Buffer " + b.Buffer.ToString();
                        UInt64 length = 0;
                        UInt64 offset = 0;
                        int numvars = shaderCBuf != null ? shaderCBuf.variables.Length : 0;
                        UInt32 byteSize = shaderCBuf != null ? shaderCBuf.byteSize : 0;

                        if (b.Immediate && b.RootValues.Length > 0)
                            byteSize = (UInt32)(b.RootValues.Length * 4);

                        if (!filledSlot)
                            name = "Empty";

                        if (b != null)
                        {
                            offset = b.Offset;
                            length = b.ByteSize;

                            for (int t = 0; t < bufs.Length; t++)
                                if (bufs[t].ID == b.Buffer)
                                    name = bufs[t].name;
                        }

                        string regname = reg.ToString();

                        if (shaderCBuf != null && shaderCBuf.name.Length > 0)
                            regname += ": " + shaderCBuf.name;

                        string sizestr;
                        if (byteSize == length)
                            sizestr = String.Format("{0} Variables, {1} bytes", numvars, length);
                        else
                            sizestr = String.Format("{0} Variables, {1} bytes needed, {2} provided", numvars, byteSize, length);

                        if (length < byteSize)
                            filledSlot = false;

                        var node = cbuffers.Nodes.Add(new object[] { rootel, space, regname, name, offset, sizestr });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = tag;

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }
                }
            }
            cbuffers.EndUpdate();
            cbuffers.NodesSelection.Clear();
            cbuffers.SetVScrollValue(vs);
        }
        // Set a shader stage's resources and values
        private void SetShaderState(FetchTexture[] texs, FetchBuffer[] bufs,
            D3D11PipelineState.ShaderStage stage,
            Label shader, TreelistView.TreeListView resources, TreelistView.TreeListView samplers,
            TreelistView.TreeListView cbuffers, TreelistView.TreeListView classes)
        {
            ShaderReflection shaderDetails = stage.ShaderDetails;

            if (stage.Shader == ResourceId.Null)
                shader.Text = "Unbound";
            else
                shader.Text = stage.ShaderName;

            if (shaderDetails != null && shaderDetails.DebugInfo.entryFunc.Length > 0 && shaderDetails.DebugInfo.files.Length > 0)
            {
                string shaderfn = "";

                try
                {
                    shaderfn = Path.GetFileName(shaderDetails.DebugInfo.files[0].filename);
                }
                catch (ArgumentException)
                {
                    // invalid path or similar, just try to go from last \ or / onwards

                    shaderfn = shaderDetails.DebugInfo.files[0].filename;
                    int idx = shaderfn.LastIndexOfAny(new char[] { '/', '\\' });
                    if (idx > 0)
                        shaderfn = shaderfn.Substring(idx + 1);
                }

                shader.Text = shaderDetails.DebugInfo.entryFunc + "()" + " - " + shaderfn;
            }

            int vs = 0;

            vs = resources.VScrollValue();
            resources.BeginUpdate();
            resources.Nodes.Clear();
            if (stage.SRVs != null)
            {
                int i = 0;
                foreach (var r in stage.SRVs)
                {
                    ShaderResource shaderInput = null;

                    if (shaderDetails != null)
                    {
                        foreach (var bind in shaderDetails.Resources)
                        {
                            if (bind.IsSRV && bind.bindPoint == i)
                            {
                                shaderInput = bind;
                                break;
                            }
                        }
                    }

                    bool filledSlot = (r.Resource != ResourceId.Null);
                    bool usedSlot = (shaderInput != null);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string slotname = i.ToString();

                        if (shaderInput != null && shaderInput.name.Length > 0)
                            slotname += ": " + shaderInput.name;

                        UInt32 w = 1, h = 1, d = 1;
                        UInt32 a = 1;
                        string format = "Unknown";
                        string name = "Shader Resource " + r.Resource.ToString();
                        string typename = "Unknown";
                        object tag = null;

                        if (!filledSlot)
                        {
                            name = "Empty";
                            format = "-";
                            typename = "-";
                            w = h = d = a = 0;
                        }

                        // check to see if it's a texture
                        for (int t = 0; t < texs.Length; t++)
                        {
                            if (texs[t].ID == r.Resource)
                            {
                                w = texs[t].width;
                                h = texs[t].height;
                                d = texs[t].depth;
                                a = texs[t].arraysize;
                                format = texs[t].format.ToString();
                                name = texs[t].name;
                                typename = texs[t].resType.Str();

                                tag = texs[t];
                            }
                        }

                        // if not a texture, it must be a buffer
                        for (int t = 0; t < bufs.Length; t++)
                        {
                            if (bufs[t].ID == r.Resource)
                            {
                                w = bufs[t].length;
                                h = 0;
                                d = 0;
                                a = 0;
                                format = "";
                                name = bufs[t].name;
                                typename = "Buffer";

                                // for structured buffers, display how many 'elements' there are in the buffer
                                if (bufs[t].structureSize > 0)
                                    typename = "StructuredBuffer[" + (bufs[t].length / bufs[t].structureSize) + "]";

                                // get the buffer type, whether it's just a basic type or a complex struct
                                if (shaderInput != null && !shaderInput.IsTexture)
                                {
                                    if (r.Format.compType == FormatComponentType.None)
                                    {
                                        if (shaderInput.variableType.members.Length > 0)
                                            format = "struct " + shaderInput.variableType.Name;
                                        else
                                            format = shaderInput.variableType.Name;
                                    }
                                    else
                                    {
                                        format = r.Format.ToString();
                                    }
                                }

                                tag = bufs[t];
                            }
                        }

                        var node = resources.Nodes.Add(new object[] { slotname, name, typename, w, h, d, a, format });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = tag;

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }
                    i++;
                }
            }
            resources.EndUpdate();
            resources.NodesSelection.Clear();
            resources.SetVScrollValue(vs);

            vs = samplers.VScrollValue();
            samplers.BeginUpdate();
            samplers.Nodes.Clear();
            if (stage.Samplers != null)
            {
                int i = 0;
                foreach (var s in stage.Samplers)
                {
                    ShaderResource shaderInput = null;

                    if (shaderDetails != null)
                    {
                        foreach (var bind in shaderDetails.Resources)
                        {
                            if (bind.IsSampler && bind.bindPoint == i)
                            {
                                shaderInput = bind;
                                break;
                            }
                        }
                    }

                    bool filledSlot = (s.AddressU.Length > 0);
                    bool usedSlot = (shaderInput != null);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string slotname = i.ToString();

                        if (shaderInput != null && shaderInput.name.Length > 0)
                            slotname += ": " + shaderInput.name;

                        string borderColor = s.BorderColor[0].ToString() + ", " +
                                                s.BorderColor[1].ToString() + ", " +
                                                s.BorderColor[2].ToString() + ", " +
                                                s.BorderColor[3].ToString();

                        string addressing = "";

                        string addPrefix = "";
                        string addVal = "";

                        string[] addr = { s.AddressU, s.AddressV, s.AddressW };

                        // arrange like either UVW: WRAP or UV: WRAP, W: CLAMP
                        for (int a = 0; a < 3; a++)
                        {
                            string prefix = "" + "UVW"[a];

                            if (a == 0 || addr[a] == addr[a - 1])
                            {
                                addPrefix += prefix;
                            }
                            else
                            {
                                addressing += addPrefix + ": " + addVal + ", ";

                                addPrefix = prefix;
                            }
                            addVal = addr[a];
                        }

                        addressing += addPrefix + ": " + addVal;

                        if(s.UseBorder)
                            addressing += String.Format("<{0}>", borderColor);

                        string filter = s.Filter;

                        if (s.MaxAniso > 0)
                            filter += String.Format(" {0}x", s.MaxAniso);

                        if (s.UseComparison)
                            filter += String.Format(" ({0})", s.Comparison);

                        var node = samplers.Nodes.Add(new object[] { slotname, addressing,
                                                            filter,
                                                            (s.MinLOD == -float.MaxValue ? "0" : s.MinLOD.ToString()) + " - " +
                                                            (s.MaxLOD == float.MaxValue ? "FLT_MAX" : s.MaxLOD.ToString()),
                                                            s.MipLODBias.ToString() });

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }

                    i++;
                }
            }
            samplers.EndUpdate();
            samplers.NodesSelection.Clear();
            samplers.SetVScrollValue(vs);

            vs = cbuffers.VScrollValue();
            cbuffers.BeginUpdate();
            cbuffers.Nodes.Clear();
            if (stage.ConstantBuffers != null)
            {
                UInt32 i = 0;
                foreach (var b in stage.ConstantBuffers)
                {
                    ConstantBlock shaderCBuf = null;

                    if (shaderDetails != null && i < shaderDetails.ConstantBlocks.Length && shaderDetails.ConstantBlocks[i].name.Length > 0)
                        shaderCBuf = shaderDetails.ConstantBlocks[i];

                    bool filledSlot = (b.Buffer != ResourceId.Null);
                    bool usedSlot = (shaderCBuf != null);

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        string name = "Constant Buffer " + b.Buffer.ToString();
                        UInt32 length = 1;
                        int numvars = shaderCBuf != null ? shaderCBuf.variables.Length : 0;

                        if (!filledSlot)
                        {
                            name = "Empty";
                            length = 0;
                        }

                        for (int t = 0; t < bufs.Length; t++)
                        {
                            if (bufs[t].ID == b.Buffer)
                            {
                                name = bufs[t].name;
                                length = bufs[t].length;
                            }
                        }

                        string slotname = i.ToString();

                        if (shaderCBuf != null && shaderCBuf.name.Length > 0)
                            slotname += ": " + shaderCBuf.name;

                        string sizestr = String.Format("{0} Variables, {1} bytes", numvars, length);
                        string vecrange = String.Format("{0} - {1}", b.VecOffset, b.VecOffset + b.VecCount);

                        var node = cbuffers.Nodes.Add(new object[] { slotname, name, vecrange, sizestr });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = i;

                        if (!filledSlot)
                            EmptyRow(node);

                        if (!usedSlot)
                            InactiveRow(node);
                    }
                    i++;
                }
            }
            cbuffers.EndUpdate();
            cbuffers.NodesSelection.Clear();
            cbuffers.SetVScrollValue(vs);

            vs = classes.VScrollValue();
            classes.BeginUpdate();
            classes.Nodes.Clear();
            {
                UInt32 i = 0;
                foreach (var inst in stage.ClassInstances)
                {
                    string interfaceName = String.Format("Interface {0}", i);

                    if (shaderDetails != null && i < shaderDetails.Interfaces.Length)
                        interfaceName = shaderDetails.Interfaces[i].Name;

                    classes.Nodes.Add(new object[] { i.ToString(), interfaceName, inst });

                    i++;
                }
            }
            classes.EndUpdate();
            classes.NodesSelection.Clear();
            classes.SetVScrollValue(vs);

            classes.Visible = classes.Parent.Visible = (stage.ClassInstances.Length > 0);
        }
        // Set a shader stage's resources and values
        private void SetShaderState(FetchTexture[] texs, FetchBuffer[] bufs,
            VulkanPipelineState.ShaderStage stage, VulkanPipelineState.Pipeline pipe,
            Label shader, TreelistView.TreeListView resources,
            TreelistView.TreeListView cbuffers)
        {
            ShaderReflection shaderDetails = stage.ShaderDetails;

            if (stage.Shader == ResourceId.Null)
                shader.Text = "Unbound";
            else
                shader.Text = stage.ShaderName;

            if (shaderDetails != null && shaderDetails.DebugInfo.entryFunc.Length > 0)
            {
                if (shaderDetails.DebugInfo.files.Length > 0 || shaderDetails.DebugInfo.entryFunc != "main")
                    shader.Text = shaderDetails.DebugInfo.entryFunc + "()";

                if (shaderDetails.DebugInfo.files.Length > 0)
                {
                    string shaderfn = "";

                    int entryFile = shaderDetails.DebugInfo.entryFile;
                    if (entryFile < 0 || entryFile >= shaderDetails.DebugInfo.files.Length)
                        entryFile = 0;

                    shaderfn = shaderDetails.DebugInfo.files[entryFile].BaseFilename;

                    shader.Text += " - " + shaderfn;
                }
            }

            int vs = 0;

            vs = resources.VScrollValue();
            resources.BeginUpdate();
            resources.Nodes.Clear();

            var samplers = new Dictionary<ResourceId, SamplerData>();

            for(int bindset = 0; bindset < pipe.DescSets.Length; bindset++)
            {
                for(int bind = 0; bind < pipe.DescSets[bindset].bindings.Length; bind++)
                {
                    ShaderResource shaderRes = null;
                    BindpointMap bindMap = null;

                    bool isrw = false;
                    uint bindPoint = 0;

                    if (shaderDetails != null)
                    {
                        for(int i=0; i < shaderDetails.ReadOnlyResources.Length; i++)
                        {
                            var ro = shaderDetails.ReadOnlyResources[i];

                            if (stage.BindpointMapping.ReadOnlyResources[ro.bindPoint].bindset == bindset &&
                                stage.BindpointMapping.ReadOnlyResources[ro.bindPoint].bind == bind)
                            {
                                bindPoint = (uint)i;
                                shaderRes = ro;
                                bindMap = stage.BindpointMapping.ReadOnlyResources[ro.bindPoint];
                            }
                        }

                        for(int i=0; i < shaderDetails.ReadWriteResources.Length; i++)
                        {
                            var rw = shaderDetails.ReadWriteResources[i];

                            if (stage.BindpointMapping.ReadWriteResources[rw.bindPoint].bindset == bindset &&
                                stage.BindpointMapping.ReadWriteResources[rw.bindPoint].bind == bind)
                            {
                                bindPoint = (uint)i;
                                isrw = true;
                                shaderRes = rw;
                                bindMap = stage.BindpointMapping.ReadWriteResources[rw.bindPoint];
                            }
                        }
                    }

                    VulkanPipelineState.Pipeline.DescriptorSet.DescriptorBinding.BindingElement[] slotBinds =
                        pipe.DescSets[bindset].bindings[bind].binds;
                    ShaderBindType bindType = pipe.DescSets[bindset].bindings[bind].type;
                    ShaderStageBits stageBits = pipe.DescSets[bindset].bindings[bind].stageFlags;

                    // skip descriptors that aren't for this shader stage
                    if (!stageBits.HasFlag((ShaderStageBits)(1 << (int)stage.stage)))
                        continue;

                    // these are treated as uniform buffers
                    if (bindType == ShaderBindType.ReadOnlyBuffer)
                        continue;

                    // consider it filled if any array element is filled
                    bool filledSlot = false;
                    for (int idx = 0; idx < slotBinds.Length; idx++)
                    {
                        filledSlot |= slotBinds[idx].res != ResourceId.Null;
                        if(bindType == ShaderBindType.Sampler || bindType == ShaderBindType.ImageSampler)
                            filledSlot |= slotBinds[idx].sampler != ResourceId.Null;
                    }
                    bool usedSlot = bindMap != null && bindMap.used;

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        TreelistView.NodeCollection parentNodes = resources.Nodes;

                        string setname = bindset.ToString();

                        string slotname = bind.ToString();
                        if(shaderRes != null)
                            slotname += ": " + shaderRes.name;

                        // for arrays, add a parent element that we add the real cbuffers below
                        if (slotBinds.Length > 1)
                        {
                            var node = parentNodes.Add(new object[] { "", setname, slotname, String.Format("Array[{0}]", slotBinds.Length), "", "", "", "" });

                            node.TreeColumn = 0;

                            if (!filledSlot)
                                EmptyRow(node);

                            if (!usedSlot)
                                InactiveRow(node);

                            parentNodes = node.Nodes;
                        }

                        for (int idx = 0; idx < slotBinds.Length; idx++)
                        {
                            var descriptorBind = slotBinds[idx];

                            if (slotBinds.Length > 1)
                            {
                                slotname = String.Format("{0}[{1}]", bind, idx);

                                if (shaderRes != null && shaderRes.name.Length > 0)
                                    slotname += ": " + shaderRes.name;
                            }

                            bool isbuf = false;
                            UInt32 w = 1, h = 1, d = 1;
                            UInt32 a = 1;
                            UInt32 samples = 1;
                            UInt64 len = 0;
                            string format = "Unknown";
                            string name = "Object " + descriptorBind.res.ToString();
                            ShaderResourceType restype = ShaderResourceType.None;
                            object tag = null;

                            if (!filledSlot)
                            {
                                name = "Empty";
                                format = "-";
                                w = h = d = a = 0;
                            }

                            // check to see if it's a texture
                            for (int t = 0; t < texs.Length; t++)
                            {
                                if (texs[t].ID == descriptorBind.res)
                                {
                                    w = texs[t].width;
                                    h = texs[t].height;
                                    d = texs[t].depth;
                                    a = texs[t].arraysize;
                                    format = texs[t].format.ToString();
                                    name = texs[t].name;
                                    restype = texs[t].resType;
                                    samples = texs[t].msSamp;

                                    tag = texs[t];
                                }
                            }

                            // if not a texture, it must be a buffer
                            for (int t = 0; t < bufs.Length; t++)
                            {
                                if (bufs[t].ID == descriptorBind.res)
                                {
                                    len = bufs[t].byteSize;
                                    w = bufs[t].length;
                                    h = 0;
                                    d = 0;
                                    a = 0;
                                    format = "";
                                    name = bufs[t].name;
                                    restype = ShaderResourceType.Buffer;

                                    tag = new BufferResTag(isrw, bindPoint, bufs[t].ID);

                                    isbuf = true;
                                }
                            }

                            TreelistView.Node node = null;

                            if (bindType == ShaderBindType.ReadWriteBuffer ||
                                bindType == ShaderBindType.ReadOnlyTBuffer ||
                                bindType == ShaderBindType.ReadWriteTBuffer
                                )
                            {
                                if (!isbuf)
                                {
                                    node = parentNodes.Add(new object[] {
                                        "", bindset, slotname, bindType, "-",
                                        "-",
                                        "",
                                        "",
                                    });

                                    EmptyRow(node);
                                }
                                else
                                {
                                    node = parentNodes.Add(new object[] {
                                        "", bindset, slotname, bindType, name,
                                        String.Format("{0} bytes", len),
                                        String.Format("{0} - {1}", descriptorBind.offset, descriptorBind.size),
                                        "",
                                    });

                                    node.Image = global::renderdocui.Properties.Resources.action;
                                    node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                                    node.Tag = tag;

                                    if (!filledSlot)
                                        EmptyRow(node);

                                    if (!usedSlot)
                                        InactiveRow(node);
                                }
                            }
                            else if (bindType == ShaderBindType.Sampler)
                            {
                                if (descriptorBind.sampler == ResourceId.Null)
                                {
                                    node = parentNodes.Add(new object[] {
                                        "", bindset, slotname, bindType, "-",
                                        "-",
                                        "",
                                        "",
                                    });

                                    EmptyRow(node);
                                }
                                else
                                {
                                    node = parentNodes.Add(MakeSampler(bindset.ToString(), slotname, descriptorBind));

                                    if (!filledSlot)
                                        EmptyRow(node);

                                    if (!usedSlot)
                                        InactiveRow(node);

                                    var data = new SamplerData(node);
                                    node.Tag = data;

                                    if (!samplers.ContainsKey(descriptorBind.sampler))
                                        samplers.Add(descriptorBind.sampler, data);
                                }
                            }
                            else
                            {
                                if (descriptorBind.res == ResourceId.Null)
                                {
                                    node = parentNodes.Add(new object[] {
                                        "", bindset, slotname, bindType, "-",
                                        "-",
                                        "",
                                        "",
                                    });

                                    EmptyRow(node);
                                }
                                else
                                {
                                    string typename = restype.Str() + " " + bindType.Str().Replace("&", "&&");

                                    string dim;

                                    if (restype == ShaderResourceType.Texture3D)
                                        dim = String.Format("{0}x{1}x{2}", w, h, d);
                                    else if (restype == ShaderResourceType.Texture1D || restype == ShaderResourceType.Texture1DArray)
                                        dim = w.ToString();
                                    else
                                        dim = String.Format("{0}x{1}", w, h);

                                    string arraydim = "-";

                                    if(restype == ShaderResourceType.Texture1DArray ||
                                       restype == ShaderResourceType.Texture2DArray ||
                                       restype == ShaderResourceType.Texture2DMSArray ||
                                       restype == ShaderResourceType.TextureCubeArray)
                                        arraydim = String.Format("{0}[{1}]", restype.Str(), a);

                                    if (restype == ShaderResourceType.Texture2DMS || restype == ShaderResourceType.Texture2DMSArray)
                                        dim += String.Format(", {0}x MSAA", samples);

                                    node = parentNodes.Add(new object[] {
                                        "", bindset, slotname, typename, name,
                                        dim,
                                        format,
                                        arraydim,
                                    });

                                    node.Image = global::renderdocui.Properties.Resources.action;
                                    node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                                    node.Tag = tag;

                                    if (!filledSlot)
                                        EmptyRow(node);

                                    if (!usedSlot)
                                        InactiveRow(node);
                                }

                                if (bindType == ShaderBindType.ImageSampler)
                                {
                                    if (descriptorBind.sampler == ResourceId.Null)
                                    {
                                        node = parentNodes.Add(new object[] {
                                            "", bindset, slotname, bindType, "-",
                                            "-",
                                            "",
                                            "",
                                        });

                                        EmptyRow(node);
                                    }
                                    else
                                    {
                                        var texnode = node;

                                        if (!samplers.ContainsKey(descriptorBind.sampler))
                                        {
                                            node = parentNodes.Add(MakeSampler("", "", descriptorBind));

                                            if (!filledSlot)
                                                EmptyRow(node);

                                            if (!usedSlot)
                                                InactiveRow(node);

                                            var data = new SamplerData(node);
                                            node.Tag = data;

                                            samplers.Add(descriptorBind.sampler, data);
                                        }

                                        if (texnode != null)
                                        {
                                            m_CombinedImageSamplers[texnode] = samplers[descriptorBind.sampler].node;
                                            samplers[descriptorBind.sampler].images.Add(texnode);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            resources.EndUpdate();
            resources.NodesSelection.Clear();
            resources.SetVScrollValue(vs);

            vs = cbuffers.VScrollValue();
            cbuffers.BeginUpdate();
            cbuffers.Nodes.Clear();
            for(int bindset = 0; bindset < pipe.DescSets.Length; bindset++)
            {
                for(int bind = 0; bind < pipe.DescSets[bindset].bindings.Length; bind++)
                {
                    ConstantBlock cblock = null;
                    BindpointMap bindMap = null;

                    uint slot = uint.MaxValue;
                    if (shaderDetails != null)
                    {
                        for (slot = 0; slot < (uint)shaderDetails.ConstantBlocks.Length; slot++)
                        {
                            ConstantBlock cb = shaderDetails.ConstantBlocks[slot];
                            if (stage.BindpointMapping.ConstantBlocks[cb.bindPoint].bindset == bindset &&
                                stage.BindpointMapping.ConstantBlocks[cb.bindPoint].bind == bind)
                            {
                                cblock = cb;
                                bindMap = stage.BindpointMapping.ConstantBlocks[cb.bindPoint];
                                break;
                            }
                        }

                        if (slot >= (uint)shaderDetails.ConstantBlocks.Length)
                            slot = uint.MaxValue;
                    }

                    var slotBinds = pipe.DescSets[bindset].bindings[bind].binds;
                    ShaderBindType bindType = pipe.DescSets[bindset].bindings[bind].type;
                    ShaderStageBits stageBits = pipe.DescSets[bindset].bindings[bind].stageFlags;

                    // skip descriptors that aren't for this shader stage
                    if (!stageBits.HasFlag((ShaderStageBits)(1 << (int)stage.stage)))
                        continue;

                    // these are treated as uniform buffers
                    if (bindType != ShaderBindType.ReadOnlyBuffer)
                        continue;

                    bool usedSlot = bindMap != null && bindMap.used;

                    // consider it filled if any array element is filled (or it's push constants)
                    bool filledSlot = cblock != null && !cblock.bufferBacked;
                    for (int idx = 0; idx < slotBinds.Length; idx++)
                        filledSlot |= slotBinds[idx].res != ResourceId.Null;

                    // show if
                    if (usedSlot || // it's referenced by the shader - regardless of empty or not
                        (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled"
                        (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty"
                        )
                    {
                        TreelistView.NodeCollection parentNodes = cbuffers.Nodes;

                        string setname = bindset.ToString();

                        string slotname = bind.ToString();
                        if (cblock != null && cblock.name.Length > 0)
                            slotname += ": " + cblock.name;

                        // for arrays, add a parent element that we add the real cbuffers below
                        if (slotBinds.Length > 1)
                        {
                            var node = parentNodes.Add(new object[] { "", setname, slotname, String.Format("Array[{0}]", slotBinds.Length), "", "" });

                            node.TreeColumn = 0;

                            if (!filledSlot)
                                EmptyRow(node);

                            if (!usedSlot)
                                InactiveRow(node);

                            parentNodes = node.Nodes;
                        }

                        for (int idx = 0; idx < slotBinds.Length; idx++)
                        {
                            var descriptorBind = slotBinds[idx];

                            if (slotBinds.Length > 1)
                            {
                                slotname = String.Format("{0}[{1}]", bind, idx);

                                if (cblock != null && cblock.name.Length > 0)
                                    slotname += ": " + cblock.name;
                            }

                            string name = "UBO " + descriptorBind.res.ToString();
                            UInt64 length = descriptorBind.size;
                            int numvars = cblock != null ? cblock.variables.Length : 0;

                            if (!filledSlot)
                            {
                                name = "Empty";
                                length = 0;
                            }

                            for (int t = 0; t < bufs.Length; t++)
                                if (bufs[t].ID == descriptorBind.res)
                                    name = bufs[t].name;

                            if (name == "")
                                name = "UBO " + descriptorBind.res.ToString();

                            string sizestr = String.Format("{0} Variables, {1} bytes", numvars, length);
                            string vecrange = String.Format("{0} - {1}", descriptorBind.offset, descriptorBind.offset + descriptorBind.size);

                            // push constants
                            if (cblock != null && !cblock.bufferBacked)
                            {
                                setname = "";
                                slotname = cblock.name;
                                name = "Push constants";
                                vecrange = "";
                                sizestr = String.Format("{0} Variables", numvars);

                                // could maybe get range from ShaderVariable.reg if it's filled out
                                // from SPIR-V side.
                            }

                            var node = parentNodes.Add(new object[] { "", setname, slotname, name, vecrange, sizestr });

                            node.Image = global::renderdocui.Properties.Resources.action;
                            node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                            node.Tag = new CBufferTag(slot, (uint)idx);

                            if (!filledSlot)
                                EmptyRow(node);

                            if (!usedSlot)
                                InactiveRow(node);
                        }
                    }
                }
            }

            // search for push constants and add them last
            if (shaderDetails != null)
            {
                for (int cb = 0; cb < shaderDetails.ConstantBlocks.Length; cb++)
                {
                    var cblock = shaderDetails.ConstantBlocks[cb];
                    if (cblock.bufferBacked == false)
                    {
                        // could maybe get range from ShaderVariable.reg if it's filled out
                        // from SPIR-V side.

                        var node = cbuffers.Nodes.Add(new object[] { "", "",
                        cblock.name, "Push constants",
                        "", String.Format("{0} Variables", cblock.variables.Length) });

                        node.Image = global::renderdocui.Properties.Resources.action;
                        node.HoverImage = global::renderdocui.Properties.Resources.action_hover;
                        node.Tag = new CBufferTag((uint)cb, 0);
                    }
                }
            }
            cbuffers.EndUpdate();
            cbuffers.NodesSelection.Clear();
            cbuffers.SetVScrollValue(vs);
        }