private void AddResourceRow(ShaderReflection shaderDetails, VulkanPipelineState.ShaderStage stage, int bindset, int bind, VulkanPipelineState.Pipeline pipe, TreelistView.TreeListView resources, FetchTexture[] texs, FetchBuffer[] bufs, ref Dictionary<ResourceId, SamplerData> samplers)
        {
            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 = null;
            ShaderBindType bindType = ShaderBindType.Unknown;
            ShaderStageBits stageBits = (ShaderStageBits)0;

            if (bindset < pipe.DescSets.Length && bind < pipe.DescSets[bindset].bindings.Length)
            {
                slotBinds = pipe.DescSets[bindset].bindings[bind].binds;
                bindType = pipe.DescSets[bindset].bindings[bind].type;
                stageBits = pipe.DescSets[bindset].bindings[bind].stageFlags;
            }
            else
            {
                if (shaderRes.IsSampler)
                    bindType = ShaderBindType.Sampler;
                else if (shaderRes.IsSampler && shaderRes.IsTexture)
                    bindType = ShaderBindType.ImageSampler;
                else if (shaderRes.resType == ShaderResourceType.Buffer)
                    bindType = ShaderBindType.ReadOnlyTBuffer;
                else
                    bindType = ShaderBindType.ReadOnlyImage;
            }

            bool usedSlot = bindMap != null && bindMap.used;
            bool stageBitsIncluded = stageBits.HasFlag((ShaderStageBits)(1 << (int)stage.stage));

            // skip descriptors that aren't for this shader stage
            if (!usedSlot && !stageBitsIncluded)
                return;

            if (bindType == ShaderBindType.ConstantBuffer)
                return;

            // TODO - check compatibility between bindType and shaderRes.resType ?

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

            // if it's masked out by stage bits, act as if it's not filled, so it's marked in red
            if (!stageBitsIncluded)
                filledSlot = false;

            // 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 && shaderRes.name.Length > 0)
                    slotname += ": " + shaderRes.name;

                int arrayLength = 0;
                if (slotBinds != null) arrayLength = slotBinds.Length;
                else arrayLength = (int)bindMap.arraySize;

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

                    node.TreeColumn = 0;

                    if (!filledSlot)
                        EmptyRow(node);

                    if (!usedSlot)
                        InactiveRow(node);

                    parentNodes = node.Nodes;
                }

                for (int idx = 0; idx < arrayLength; idx++)
                {
                    VulkanPipelineState.Pipeline.DescriptorSet.DescriptorBinding.BindingElement descriptorBind = null;
                    if (slotBinds != null) descriptorBind = slotBinds[idx];

                    if (arrayLength > 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 = "Empty";
                    ShaderResourceType restype = ShaderResourceType.None;
                    object tag = null;
                    bool viewDetails = false;

                    if (filledSlot && descriptorBind != null)
                    {
                        name = "Object " + descriptorBind.res.ToString();

                        format = descriptorBind.viewfmt.ToString();

                        // 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;
                                name = texs[t].name;
                                restype = texs[t].resType;
                                samples = texs[t].msSamp;

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

                                tag = new ViewTexTag(
                                        descriptorBind.viewfmt,
                                        descriptorBind.baseMip, descriptorBind.baseLayer,
                                        descriptorBind.numMip, descriptorBind.numLayer,
                                        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].length;
                                w = 0;
                                h = 0;
                                d = 0;
                                a = 0;
                                name = bufs[t].name;
                                restype = ShaderResourceType.Buffer;

                                ulong descriptorLen = descriptorBind.size;

                                if(descriptorLen == ulong.MaxValue)
                                    descriptorLen = len - descriptorBind.offset;

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

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

                                isbuf = true;
                            }
                        }
                    }
                    else
                    {
                        name = "Empty";
                        format = "-";
                        w = h = d = a = 0;
                    }

                    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
                        {
                            string range = "-";
                            if (descriptorBind != null)
                                range = String.Format("{0} - {1}", descriptorBind.offset, descriptorBind.size);
                            node = parentNodes.Add(new object[] {
                                "", bindset, slotname, bindType,
                                name,
                                String.Format("{0} bytes", len),
                                range,
                            });

                            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 == null || 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 == null || 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);

                            if (descriptorBind.swizzle[0] != TextureSwizzle.Red ||
                                descriptorBind.swizzle[1] != TextureSwizzle.Green ||
                                descriptorBind.swizzle[2] != TextureSwizzle.Blue ||
                                descriptorBind.swizzle[3] != TextureSwizzle.Alpha)
                            {
                                format += String.Format(" swizzle[{0}{1}{2}{3}]",
                                    descriptorBind.swizzle[0].Str(),
                                    descriptorBind.swizzle[1].Str(),
                                    descriptorBind.swizzle[2].Str(),
                                    descriptorBind.swizzle[3].Str());
                            }

                            if (restype == ShaderResourceType.Texture1DArray ||
                               restype == ShaderResourceType.Texture2DArray ||
                               restype == ShaderResourceType.Texture2DMSArray ||
                               restype == ShaderResourceType.TextureCubeArray)
                            {
                                dim += 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,
                            });

                            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);

                            ViewDetailsRow(node, viewDetails);
                        }

                        if (bindType == ShaderBindType.ImageSampler)
                        {
                            if (descriptorBind == null || 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);
                                }
                            }
                        }
                    }
                }
            }
        }
        // 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);
        }