private void ShowCBuffer(D3D12PipelineState.ShaderStage stage, CBufTag tag)
        {
            if (tag.idx == uint.MaxValue)
            {
                // unused cbuffer, open regular buffer viewer
                var viewer = new BufferViewer(m_Core, false);

                var buf = stage.Spaces[tag.space].ConstantBuffers[tag.reg];
                viewer.ViewRawBuffer(true, buf.Offset, buf.ByteSize, buf.Buffer);
                viewer.Show(m_DockContent.DockPanel);

                return;
            }

            var existing = ConstantBufferPreviewer.Has(stage.stage, tag.idx, 0);
            if (existing != null)
            {
                existing.Show();
                return;
            }

            var prev = new ConstantBufferPreviewer(m_Core, stage.stage, tag.idx, 0);

            prev.ShowDock(m_DockContent.Pane, DockAlignment.Right, 0.3);
        }
        // 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);
        }