diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp index be3b20f5b..4e620aa24 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp @@ -470,6 +470,28 @@ void GLPipelineStateViewer::setEmptyRow(RDTreeWidgetItem *node) node->setForegroundColor(QColor(0, 0, 0)); } +void GLPipelineStateViewer::setViewDetails(RDTreeWidgetItem *node, TextureDescription *tex, + uint32_t firstMip, uint32_t numMips) +{ + if((tex->mips > 1 && firstMip > 0) || numMips < tex->mips) + { + QString text; + + if(numMips == 1) + text += tr("The texture has %1 mips, the view covers mip %2.").arg(tex->mips).arg(firstMip); + else + text += tr("The texture has %1 mips, the view covers mips %2-%3.") + .arg(tex->mips) + .arg(firstMip) + .arg(firstMip + numMips - 1); + + node->setToolTip(text); + + node->setBackgroundColor(QColor(127, 255, 212)); + node->setForegroundColor(QColor(0, 0, 0)); + } +} + bool GLPipelineStateViewer::showNode(bool usedSlot, bool filledSlot) { const bool showDisabled = ui->showDisabled->isChecked(); @@ -741,6 +763,9 @@ void GLPipelineStateViewer::setShaderState(const GLPipe::Shader &stage, RDLabel node->setTag(QVariant::fromValue(r.resourceId)); + if(tex) + setViewDetails(node, tex, r.firstMip, r.numMips); + if(!filledSlot) setEmptyRow(node); @@ -1097,6 +1122,9 @@ void GLPipelineStateViewer::setShaderState(const GLPipe::Shader &stage, RDLabel node->setTag(tag); + if(im && tex) + setViewDetails(node, tex, im->mipLevel, 1); + if(!filledSlot) setEmptyRow(node); @@ -1826,7 +1854,11 @@ void GLPipelineStateViewer::setState() new RDTreeWidgetItem({i, p, typeName, w, h, d, a, format, QString()}); if(tex) + { + if(r) + setViewDetails(node, tex, r->mipLevel, 1); node->setTag(QVariant::fromValue(p)); + } if(p == ResourceId()) { @@ -1848,9 +1880,15 @@ void GLPipelineStateViewer::setState() state.framebuffer.drawFBO.stencilAttachment.resourceId, }; + uint32_t dsMips[] = { + state.framebuffer.drawFBO.depthAttachment.mipLevel, + state.framebuffer.drawFBO.stencilAttachment.mipLevel, + }; + for(int dsIdx = 0; dsIdx < 2; dsIdx++) { ResourceId ds = dsObjects[dsIdx]; + uint32_t mip = dsMips[dsIdx]; bool filledSlot = (ds != ResourceId()); bool usedSlot = filledSlot; @@ -1897,7 +1935,10 @@ void GLPipelineStateViewer::setState() new RDTreeWidgetItem({slot, ds, typeName, w, h, d, a, format, QString()}); if(tex) + { + setViewDetails(node, tex, mip, 1); node->setTag(QVariant::fromValue(ds)); + } if(ds == ResourceId()) setEmptyRow(node); @@ -2680,7 +2721,7 @@ void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, const GLPipe::Shad } } - textureRows.push_back({slotname, name, typeName, w, h, d, a, format}); + textureRows.push_back({slotname, name, typeName, w, h, d, a, format, r.firstMip, r.numMips}); } // do sampler @@ -2939,9 +2980,10 @@ void GLPipelineStateViewer::exportHTML(QXmlStreamWriter &xml, const GLPipe::Shad xml.writeCharacters(tr("Textures")); xml.writeEndElement(); - m_Common.exportHTMLTable(xml, {tr("Slot"), tr("Name"), tr("Type"), tr("Width"), tr("Height"), - tr("Depth"), tr("Array Size"), tr("Format")}, - textureRows); + m_Common.exportHTMLTable( + xml, {tr("Slot"), tr("Name"), tr("Type"), tr("Width"), tr("Height"), tr("Depth"), + tr("Array Size"), tr("Format"), tr("First Mip"), tr("Num Mips")}, + textureRows); } { diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h index 36703f102..886c8efa1 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h @@ -105,6 +105,9 @@ private: bool showNode(bool usedSlot, bool filledSlot); + void setViewDetails(RDTreeWidgetItem *node, TextureDescription *tex, uint32_t firstMip, + uint32_t numMips); + void exportHTML(QXmlStreamWriter &xml, const GLPipe::VertexInput &vtx); void exportHTML(QXmlStreamWriter &xml, const GLPipe::Shader &sh); void exportHTML(QXmlStreamWriter &xml, const GLPipe::Feedback &xfb); diff --git a/renderdoc/api/replay/gl_pipestate.h b/renderdoc/api/replay/gl_pipestate.h index 203f50296..b578758a4 100644 --- a/renderdoc/api/replay/gl_pipestate.h +++ b/renderdoc/api/replay/gl_pipestate.h @@ -212,7 +212,7 @@ struct Texture bool operator==(const Texture &o) const { - return resourceId == o.resourceId && firstSlice == o.firstSlice && firstMip == o.firstMip && + return resourceId == o.resourceId && firstMip == o.firstMip && numMips == o.numMips && type == o.type && swizzle[0] == o.swizzle[0] && swizzle[1] == o.swizzle[1] && swizzle[2] == o.swizzle[2] && swizzle[3] == o.swizzle[3] && depthReadChannel == o.depthReadChannel; @@ -221,10 +221,10 @@ struct Texture { if(!(resourceId == o.resourceId)) return resourceId < o.resourceId; - if(!(firstSlice == o.firstSlice)) - return firstSlice < o.firstSlice; if(!(firstMip == o.firstMip)) return firstMip < o.firstMip; + if(!(numMips == o.numMips)) + return numMips < o.numMips; if(!(type == o.type)) return type < o.type; if(!(swizzle[0] == o.swizzle[0])) @@ -241,10 +241,10 @@ struct Texture } DOCUMENT("The :class:`ResourceId` of the underlying resource the view refers to."); ResourceId resourceId; - DOCUMENT("Valid for texture arrays or 3D textures - the first slice available."); - uint32_t firstSlice = 0; - DOCUMENT("Valid for textures - the highest mip that is available."); + DOCUMENT("Valid for textures - the first mip that is available."); uint32_t firstMip = 0; + DOCUMENT("Valid for textures - the number of mips that are available."); + uint32_t numMips = 0; DOCUMENT("The :class:`TextureType` of the texture."); TextureType type = TextureType::Unknown; diff --git a/renderdoc/api/replay/pipestate.inl b/renderdoc/api/replay/pipestate.inl index a32065e06..33771ed9b 100644 --- a/renderdoc/api/replay/pipestate.inl +++ b/renderdoc/api/replay/pipestate.inl @@ -1071,7 +1071,7 @@ rdcarray PipeState::GetReadOnlyResources(ShaderStage stage) val.resourceId = m_GL->textures[i].resourceId; val.firstMip = (int)m_GL->textures[i].firstMip; - val.firstSlice = (int)m_GL->textures[i].firstSlice; + val.firstSlice = 0; val.typeHint = CompType::Typeless; ret.push_back(BoundResourceArray(key, {val})); diff --git a/renderdoc/driver/gl/gl_rendertexture.cpp b/renderdoc/driver/gl/gl_rendertexture.cpp index 7f61ec1f4..fa0a3f0e8 100644 --- a/renderdoc/driver/gl/gl_rendertexture.cpp +++ b/renderdoc/driver/gl/gl_rendertexture.cpp @@ -49,6 +49,8 @@ bool GLReplay::RenderTextureInternal(TextureDisplay cfg, int flags) if(texDetails.internalFormat == eGL_NONE) return false; + CacheTexture(cfg.resourceId); + bool renderbuffer = false; int intIdx = 0; @@ -168,7 +170,7 @@ bool GLReplay::RenderTextureInternal(TextureDisplay cfg, int flags) drv.glBindProgramPipeline(0); drv.glUseProgram(DebugData.texDisplayProg[intIdx]); - int numMips = GetNumMips(target, texname, texDetails.width, texDetails.height, texDetails.depth); + uint32_t numMips = m_CachedTextures[cfg.resourceId].mips; GLuint customProgram = 0; @@ -246,18 +248,21 @@ bool GLReplay::RenderTextureInternal(TextureDisplay cfg, int flags) // defined as arrays mostly for Coverity code analysis to stay calm about passing // them to the *TexParameter* functions + GLint baseLevel[4] = {-1}; GLint maxlevel[4] = {-1}; - GLint clampmaxlevel[4] = {}; - - if(cfg.resourceId != DebugData.CustomShaderTexID) - clampmaxlevel[0] = GLint(numMips - 1); + GLint forcedparam[4] = {}; + drv.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, baseLevel); drv.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, maxlevel); - // need to ensure texture is mipmap complete by clamping TEXTURE_MAX_LEVEL. - if(clampmaxlevel[0] != maxlevel[0] && cfg.resourceId != DebugData.CustomShaderTexID) + // ensure texture is mipmap complete and we can view all mips (if the range has been reduced) by + // forcing TEXTURE_MAX_LEVEL to cover all valid mips. + if(cfg.resourceId != DebugData.CustomShaderTexID) { - drv.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, clampmaxlevel); + forcedparam[0] = 0; + drv.glTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, forcedparam); + forcedparam[0] = GLint(numMips - 1); + drv.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, forcedparam); } else { @@ -431,6 +436,9 @@ bool GLReplay::RenderTextureInternal(TextureDisplay cfg, int flags) drv.glBindVertexArray(DebugData.emptyVAO); drv.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4); + if(baseLevel[0] >= 0) + drv.glTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, baseLevel); + if(maxlevel[0] >= 0) drv.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, maxlevel); diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index 5a6f52dd2..f6a1a503c 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -359,6 +359,9 @@ TextureDescription GLReplay::GetTexture(ResourceId id) void GLReplay::CacheTexture(ResourceId id) { + if(m_CachedTextures.find(id) != m_CachedTextures.end()) + return; + TextureDescription tex = {}; MakeCurrentReplayContext(&m_ReplayCtx); @@ -1175,7 +1178,8 @@ void GLReplay::SavePipelineState() if(tex == 0) { pipe.textures[unit].resourceId = ResourceId(); - pipe.textures[unit].firstSlice = 0; + pipe.textures[unit].firstMip = 0; + pipe.textures[unit].numMips = 1; pipe.textures[unit].type = TextureType::Unknown; pipe.textures[unit].depthReadChannel = -1; pipe.textures[unit].swizzle[0] = TextureSwizzle::Red; @@ -1197,18 +1201,19 @@ void GLReplay::SavePipelineState() } else { - // very bespoke/specific - GLint firstSlice = 0, firstMip = 0; + GLint firstMip = 0, numMips = 1; - if(target != eGL_TEXTURE_BUFFER && HasExt[ARB_texture_view]) + if(target != eGL_TEXTURE_BUFFER) { - drv.glGetTexParameteriv(target, eGL_TEXTURE_VIEW_MIN_LEVEL, &firstMip); - drv.glGetTexParameteriv(target, eGL_TEXTURE_VIEW_MIN_LAYER, &firstSlice); + drv.glGetTexParameteriv(target, eGL_TEXTURE_BASE_LEVEL, &firstMip); + drv.glGetTexParameteriv(target, eGL_TEXTURE_MAX_LEVEL, &numMips); + + numMips = numMips - firstMip + 1; } pipe.textures[unit].resourceId = rm->GetOriginalID(rm->GetID(TextureRes(ctx, tex))); pipe.textures[unit].firstMip = (uint32_t)firstMip; - pipe.textures[unit].firstSlice = (uint32_t)firstSlice; + pipe.textures[unit].numMips = (uint32_t)numMips; pipe.textures[unit].type = resType; pipe.textures[unit].depthReadChannel = -1; @@ -1462,6 +1467,8 @@ void GLReplay::SavePipelineState() } pipe.images[i].imageFormat = MakeResourceFormat(eGL_TEXTURE_2D, rs.Images[i].format); + CacheTexture(id); + pipe.images[i].type = m_CachedTextures[id].type; } } diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 74ed03da5..019550424 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -1543,8 +1543,8 @@ template void DoSerialise(SerialiserType &ser, GLPipe::Texture &el) { SERIALISE_MEMBER(resourceId); - SERIALISE_MEMBER(firstSlice); SERIALISE_MEMBER(firstMip); + SERIALISE_MEMBER(numMips); SERIALISE_MEMBER(type); SERIALISE_MEMBER(swizzle); SERIALISE_MEMBER(depthReadChannel); diff --git a/util/test/demos/d3d11/d3d11_mip_gen_rt.cpp b/util/test/demos/d3d11/d3d11_mip_gen_rt.cpp index 24457c06b..7210cdf07 100644 --- a/util/test/demos/d3d11/d3d11_mip_gen_rt.cpp +++ b/util/test/demos/d3d11/d3d11_mip_gen_rt.cpp @@ -24,7 +24,7 @@ #include "d3d11_test.h" -struct Mip_Gen_RT : D3D11GraphicsTest +struct D3D11_Mip_Gen_RT : D3D11GraphicsTest { static constexpr const char *Description = "Tests rendering from one mip to another to do a downsample chain"; @@ -155,4 +155,4 @@ float4 main(v2f IN) : SV_Target0 } }; -REGISTER_TEST(Mip_Gen_RT); \ No newline at end of file +REGISTER_TEST(D3D11_Mip_Gen_RT); \ No newline at end of file diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index 77b8e240d..ea1750f05 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -167,6 +167,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index e82ff241f..1e05c182b 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -240,6 +240,9 @@ Vulkan\demos + + OpenGL\demos + diff --git a/util/test/demos/gl/gl_mip_gen_rt.cpp b/util/test/demos/gl/gl_mip_gen_rt.cpp new file mode 100644 index 000000000..2b0fb72e2 --- /dev/null +++ b/util/test/demos/gl/gl_mip_gen_rt.cpp @@ -0,0 +1,180 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2015-2018 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "gl_test.h" + +struct GL_Mip_Gen_RT : OpenGLGraphicsTest +{ + static constexpr const char *Description = + "Tests rendering from one mip to another to do a downsample chain"; + + std::string common = R"EOSHADER( + +#version 420 core + +#define v2f v2f_block \ +{ \ + vec4 pos; \ + vec4 col; \ + vec4 uv; \ +} + +)EOSHADER"; + + std::string vertex = R"EOSHADER( + +out vec2 uv; + +void main() +{ + const vec4 verts[4] = vec4[4](vec4(-1.0, -1.0, 0.5, 1.0), vec4(1.0, -1.0, 0.5, 1.0), + vec4(-1.0, 1.0, 0.5, 1.0), vec4(1.0, 1.0, 0.5, 1.0)); + + gl_Position = verts[gl_VertexID]; + uv = gl_Position.xy * 0.5f + 0.5f; +} + +)EOSHADER"; + + std::string pixel = R"EOSHADER( + +in vec2 uv; + +layout(location = 0, index = 0) out vec4 Color; + +layout(binding = 0) uniform sampler2D tex2D; + +void main() +{ + Color = textureLod(tex2D, -uv, 0.0f); +} + +)EOSHADER"; + + int main(int argc, char **argv) + { + // initialise, create window, create context, etc + if(!Init(argc, argv)) + return 3; + + GLuint vao = MakeVAO(); + glBindVertexArray(vao); + + GLuint vb = MakeBuffer(); + glBindBuffer(GL_ARRAY_BUFFER, vb); + glBufferStorage(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, 0); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(0)); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(sizeof(Vec3f))); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), + (void *)(sizeof(Vec3f) + sizeof(Vec4f))); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + GLuint program = MakeProgram(common + vertex, common + pixel); + glObjectLabel(GL_PROGRAM, program, -1, "Full program"); + + GLuint tex = MakeTexture(); + glBindTexture(GL_TEXTURE_2D, tex); + glTexStorage2D(GL_TEXTURE_2D, 8, GL_SRGB8_ALPHA8, 1024, 1024); + + GLuint fbo[8]; + for(int i = 0; i < 8; i++) + { + fbo[i] = MakeFBO(); + glBindFramebuffer(GL_FRAMEBUFFER, fbo[i]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, i); + } + + // fill upper mip with colour ramp + uint32_t *ramp = new uint32_t[1024 * 1024]; + for(uint32_t i = 0; i < 1024 * 1024; i++) + { + float x = float(i % 1024); + float y = float(i / 1024); + ramp[i] = uint32_t(uint32_t(255.0f * (x / 1024.0f)) | (uint32_t(255.0f * (y / 1024.0f)) << 8) | + (uint32_t(255.0f * ((x + y) / 2048.0f)) << 16) | 0xff000000); + } + + while(Running()) + { + float col[] = {0.4f, 0.5f, 0.6f, 1.0f}; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClearBufferfv(GL_COLOR, 0, col); + + // clear all FBOs + for(int i = 0; i < 8; i++) + { + glBindFramebuffer(GL_FRAMEBUFFER, fbo[i]); + glClearBufferfv(GL_COLOR, 0, col); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glBindVertexArray(vao); + + glUseProgram(program); + + { + // view first mip and upload data + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1024, 1024, GL_RGBA, GL_UNSIGNED_BYTE, ramp); + + for(int i = 1; i < 8; i++) + { + // bind relevant fbo + glBindFramebuffer(GL_FRAMEBUFFER, fbo[i]); + + // change texture parameters to view previous mip + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, i - 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i - 1); + + // set viewport + glViewport(0, 0, 1024 >> i, 1024 >> i); + + // do downsample + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + // reset texture parameters to default + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 4); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); + + // bind default framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + Present(); + } + + delete[] ramp; + + return 0; + } +}; + +REGISTER_TEST(GL_Mip_Gen_RT); \ No newline at end of file