// 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;
                }
            }
        }
        private void ExportHTML(XmlTextWriter writer, GLPipelineState state, GLPipelineState.ShaderStage sh)
        {
            FetchTexture[] texs = m_Core.CurTextures;
            FetchBuffer[] bufs = m_Core.CurBuffers;

            ShaderReflection shaderDetails = sh.ShaderDetails;
            ShaderBindpointMapping mapping = sh.BindpointMapping;

            {
                writer.WriteStartElement("h3");
                writer.WriteString("Shader");
                writer.WriteEndElement();

                string shadername = "Unknown";

                if (sh.Shader == ResourceId.Null)
                    shadername = "Unbound";
                else
                    shadername = sh.ShaderName;

                if (sh.Shader == ResourceId.Null)
                {
                    shadername = "Unbound";
                }
                else
                {
                    string shname = sh.stage.Str(APIPipelineStateType.OpenGL) + " Shader";

                    if (!sh.customShaderName && !sh.customProgramName && !sh.customPipelineName)
                    {
                        shadername = shname + " " + sh.Shader.ToString();
                    }
                    else
                    {
                        if (sh.customShaderName)
                            shname = sh.ShaderName;

                        if (sh.customProgramName)
                            shname = sh.ProgramName + " - " + shname;

                        if (sh.customPipelineName && sh.PipelineActive)
                            shname = sh.PipelineName + " - " + shname;

                        shadername = shname;
                    }
                }

                writer.WriteStartElement("p");
                writer.WriteString(shadername);
                writer.WriteEndElement();

                if (sh.Shader == ResourceId.Null)
                    return;
            }

            List<object[]> textureRows = new List<object[]>();
            List<object[]> samplerRows = new List<object[]>();
            List<object[]> cbufferRows = new List<object[]>();
            List<object[]> readwriteRows = new List<object[]>();
            List<object[]> subRows = new List<object[]>();

            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.ReadOnlyResources)
                        {
                            if (bind.IsSRV && mapping.ReadOnlyResources[bind.bindPoint].bind == i)
                            {
                                shaderInput = bind;
                                map = mapping.ReadOnlyResources[bind.bindPoint];
                            }
                        }
                    }

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

                    if (shaderInput != null)
                    {
                        // 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.D16S8 ||
                                         texs[t].format.specialFormat == SpecialFormat.D24S8 ||
                                         texs[t].format.specialFormat == SpecialFormat.D32S8)
                                        )
                                    {
                                        if (r.DepthReadChannel == 0)
                                            format += " Depth-Repipead";
                                        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];
                                }
                            }

                            textureRows.Add(new object[] { slotname, name, typename, w, h, d, a, format });
                        }

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

                            samplerRows.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 (shaderDetails != null)
            {
                UInt32 i = 0;
                foreach (var shaderCBuf in shaderDetails.ConstantBlocks)
                {
                    int bindPoint = mapping.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 = mapping.ConstantBlocks[i].used;

                    // show if
                    {
                        ulong offset = 0;
                        ulong length = 0;
                        int numvars = shaderCBuf.variables.Length;
                        ulong byteSize = (ulong)shaderCBuf.byteSize;

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

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

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

                        cbufferRows.Add(new object[] { slotname, name, byterange, sizestr });
                    }
                    i++;
                }
            }

            {
                UInt32 i = 0;
                foreach (var subval in sh.Subroutines)
                {
                    subRows.Add(new object[] { i.ToString(), subval.ToString() });

                    i++;
                }
            }

            if (shaderDetails != null)
            {
                UInt32 i = 0;
                foreach (var res in shaderDetails.ReadWriteResources)
                {
                    int bindPoint = mapping.ReadWriteResources[i].bind;

                    GLReadWriteType readWriteType = GetGLReadWriteType(res);

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

                    if (readWriteType == GLReadWriteType.Image && bindPoint >= 0 && bindPoint < state.Images.Length)
                    {
                        im = state.Images[bindPoint];
                        id = state.Images[bindPoint].Resource;
                    }

                    if (readWriteType == GLReadWriteType.Atomic && bindPoint >= 0 && bindPoint < state.AtomicBuffers.Length)
                    {
                        bf = state.AtomicBuffers[bindPoint];
                        id = state.AtomicBuffers[bindPoint].Resource;
                    }

                    if (readWriteType == GLReadWriteType.SSBO && bindPoint >= 0 && bindPoint < state.ShaderStorageBuffers.Length)
                    {
                        bf = state.ShaderStorageBuffers[bindPoint];
                        id = state.ShaderStorageBuffers[bindPoint].Resource;
                    }

                    bool filledSlot = id != ResourceId.Null;
                    bool usedSlot = mapping.ReadWriteResources[i].used;

                    // show if
                    {
                        string binding = readWriteType == GLReadWriteType.Image ? "Image" :
                            readWriteType == GLReadWriteType.Atomic ? "Atomic" :
                            readWriteType == GLReadWriteType.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 = "-";
                        }

                        readwriteRows.Add(new object[] { binding, slotname, name, dimensions, format, access });
                    }
                    i++;
                }
            }

            {
                writer.WriteStartElement("h3");
                writer.WriteString("Textures");
                writer.WriteEndElement();

                ExportHTMLTable(writer,
                    new string[] { "Slot", "Name", "Type", "Width", "Height", "Depth", "Array Size", "Format" },
                    textureRows.ToArray()
                );
            }

            {
                writer.WriteStartElement("h3");
                writer.WriteString("Samplers");
                writer.WriteEndElement();

                ExportHTMLTable(writer,
                    new string[] { "Slot", "Addressing", "Min Filter", "Mag Filter", "LOD Clamping", "LOD Bias" },
                    samplerRows.ToArray()
                );
            }

            {
                writer.WriteStartElement("h3");
                writer.WriteString("Uniform Buffers");
                writer.WriteEndElement();

                ExportHTMLTable(writer,
                    new string[] { "Slot", "Name", "Byte Range", "Size" },
                    cbufferRows.ToArray()
                );
            }

            {
                writer.WriteStartElement("h3");
                writer.WriteString("Subroutines");
                writer.WriteEndElement();

                ExportHTMLTable(writer,
                    new string[] { "Index", "Value" },
                    subRows.ToArray()
                );
            }

            {
                writer.WriteStartElement("h3");
                writer.WriteString("Read-write resources");
                writer.WriteEndElement();

                ExportHTMLTable(writer,
                    new string[] { "Binding", "Resource", "Name", "Dimensions", "Format", "Access", },
                    readwriteRows.ToArray()
                );
            }
        }