From 58c3dc52a75ef889f76e6c284e8fea6ad2c9e1b0 Mon Sep 17 00:00:00 2001 From: baldurk Date: Tue, 27 Feb 2024 14:30:58 +0000 Subject: [PATCH] Add reporting of GL texture completeness and conflicts for descriptors --- qrenderdoc/Code/pyrenderdoc/renderdoc.i | 1 + renderdoc/api/replay/gl_pipestate.h | 43 ++++++ renderdoc/driver/gl/gl_replay.cpp | 172 +++++++++++++++++++++++ renderdoc/replay/renderdoc_serialise.inl | 13 +- 4 files changed, 228 insertions(+), 1 deletion(-) diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index f32011d4f..3609f9bfd 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -450,6 +450,7 @@ TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, GLPipe, Sampler) TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, GLPipe, Texture) TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, GLPipe, VertexBuffer) TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, GLPipe, VertexAttribute) +TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, GLPipe, TextureCompleteness) /////////////////////////////////////////////////////////////////////////////////////////// // declare a function for passing external objects into python diff --git a/renderdoc/api/replay/gl_pipestate.h b/renderdoc/api/replay/gl_pipestate.h index 3b1bad3ea..abbde0c4d 100644 --- a/renderdoc/api/replay/gl_pipestate.h +++ b/renderdoc/api/replay/gl_pipestate.h @@ -323,6 +323,42 @@ multiple uniforms are pointing to the same binding but with different types. In impossible to disambiguate which binding was used. +If this string is empty, no conflict is present. Otherwise it contains the bindings which are +in conflict and their types. +)"); + rdcstr typeConflict; +}; + +DOCUMENT("Describes the a texture completeness issue of a descriptor."); +struct TextureCompleteness +{ + DOCUMENT(""); + TextureCompleteness() = default; + TextureCompleteness(const TextureCompleteness &) = default; + TextureCompleteness &operator=(const TextureCompleteness &) = default; + + bool operator==(const TextureCompleteness &o) const + { + return descriptorByteOffset == o.descriptorByteOffset && completeStatus == o.completeStatus; + } + bool operator<(const TextureCompleteness &o) const + { + return descriptorByteOffset < o.descriptorByteOffset; + } + + DOCUMENT(R"(The byte offset in the GL descriptor storage of the problematic descriptor +)"); + uint64_t descriptorByteOffset = 0; + + DOCUMENT(R"(The details of the texture's (in)completeness. If this string is empty, the texture is +complete. Otherwise it contains an explanation of why the texture is believed to be incomplete. +)"); + rdcstr completeStatus; + + DOCUMENT(R"(The details of any type conflict on this binding. This can happen if +multiple uniforms are pointing to the same binding but with different types. In this case it is +impossible to disambiguate which binding was used. + If this string is empty, no conflict is present. Otherwise it contains the bindings which are in conflict and their types. )"); @@ -931,6 +967,12 @@ struct State )"); uint32_t descriptorByteSize; + DOCUMENT(R"(Texture completeness issues of descriptors in the descriptor store. + +:type: GLTextureCompleteness +)"); + rdcarray textureCompleteness; + DOCUMENT(R"(The transform feedback stage. :type: GLFeedback @@ -977,6 +1019,7 @@ DECLARE_REFLECTION_STRUCT(GLPipe::Shader); DECLARE_REFLECTION_STRUCT(GLPipe::FixedVertexProcessing); DECLARE_REFLECTION_STRUCT(GLPipe::Texture); DECLARE_REFLECTION_STRUCT(GLPipe::Sampler); +DECLARE_REFLECTION_STRUCT(GLPipe::TextureCompleteness); DECLARE_REFLECTION_STRUCT(GLPipe::Buffer); DECLARE_REFLECTION_STRUCT(GLPipe::ImageLoadStore); DECLARE_REFLECTION_STRUCT(GLPipe::Feedback); diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index 4e9c9ae25..f628db339 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -1193,6 +1193,8 @@ void GLReplay::SavePipelineState(uint32_t eventId) ResortBindings(refls[i], mappings[i]); } + pipe.textureCompleteness.clear(); + for(size_t s = 0; s < NumShaderStages; s++) { if(!refls[s]) @@ -1258,6 +1260,62 @@ void GLReplay::SavePipelineState(uint32_t eventId) access.byteOffset = EncodeGLDescriptorIndex({descType, (uint32_t)mapping.readOnlyResources[i].bind}); m_Access.push_back(access); + + // checking texture completeness is a pretty expensive operation since it requires a lot of + // queries against the driver's texture properties. + // We assume that if a texture and sampler are complete at any point, even if their + // properties change mid-frame they will stay complete. Similarly if they are _incomplete_ + // they will stay incomplete. Thus we can cache the results for a given pair, which if + // samplers don't change (or are only ever used consistently with the same texture) amounts + // to one entry per texture. + // Note that textures can't change target, so we don't need to icnlude the target in the key + drv.glActiveTexture(GLenum(eGL_TEXTURE0 + mapping.readOnlyResources[i].bind)); + + GLenum binding = eGL_NONE; + switch(refls[s]->readOnlyResources[i].resType) + { + case TextureType::Unknown: binding = eGL_NONE; break; + case TextureType::Buffer: binding = eGL_TEXTURE_BINDING_BUFFER; break; + case TextureType::Texture1D: binding = eGL_TEXTURE_BINDING_1D; break; + case TextureType::Texture1DArray: binding = eGL_TEXTURE_BINDING_1D_ARRAY; break; + case TextureType::Texture2D: binding = eGL_TEXTURE_BINDING_2D; break; + case TextureType::TextureRect: binding = eGL_TEXTURE_BINDING_RECTANGLE; break; + case TextureType::Texture2DArray: binding = eGL_TEXTURE_BINDING_2D_ARRAY; break; + case TextureType::Texture2DMS: binding = eGL_TEXTURE_BINDING_2D_MULTISAMPLE; break; + case TextureType::Texture2DMSArray: + binding = eGL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY; + break; + case TextureType::Texture3D: binding = eGL_TEXTURE_BINDING_3D; break; + case TextureType::TextureCube: binding = eGL_TEXTURE_BINDING_CUBE_MAP; break; + case TextureType::TextureCubeArray: binding = eGL_TEXTURE_BINDING_CUBE_MAP_ARRAY; break; + case TextureType::Count: RDCERR("Invalid shader resource type"); break; + } + + GLuint tex = 0; + + if(descType == GLDescriptorMapping::TexCubeArray && !HasExt[ARB_texture_cube_map_array]) + tex = 0; + else + drv.glGetIntegerv(binding, (GLint *)&tex); + + GLuint samp = 0; + if(HasExt[ARB_sampler_objects]) + drv.glGetIntegerv(eGL_SAMPLER_BINDING, (GLint *)&samp); + + CompleteCacheKey complete = {tex, samp}; + + auto it = m_CompleteCache.find(complete); + if(it == m_CompleteCache.end()) + it = m_CompleteCache.insert( + it, + std::make_pair(complete, GetTextureCompleteStatus(TextureTarget(binding), tex, samp))); + if(!it->second.empty()) + { + GLPipe::TextureCompleteness completeness; + completeness.descriptorByteOffset = access.byteOffset; + completeness.completeStatus = it->second; + pipe.textureCompleteness.push_back(completeness); + } } RDCASSERT(mapping.readWriteResources.size() < 0xffff, mapping.readWriteResources.size()); @@ -1293,6 +1351,120 @@ void GLReplay::SavePipelineState(uint32_t eventId) } } + { + // search for conflicts by looking at all stages + for(uint32_t unit = 0; unit < (uint32_t)numTexUnits; unit++) + { + rdcstr typeConflict; + GLenum binding = eGL_NONE; + GLenum target = eGL_NONE; + TextureType resType = TextureType::Unknown; + rdcstr firstBindName; + + rdcarray descriptorsReferenced; + + for(const DescriptorAccess &access : m_Access) + { + // only look at read-only descriptors, these are the texture units that can clash + if(!IsReadOnlyDescriptor(access.type)) + continue; + + ShaderReflection *refl = refls[(uint32_t)access.stage]; + if(refl == NULL) + { + RDCERR("Unexpected NULL reflection on %s shader with a descriptor access", + ToStr(access.stage).c_str()); + continue; + } + + uint32_t accessedUnit = DecodeGLDescriptorIndex(access.byteOffset).idx; + + // accessed the same unit, check its binding + if(accessedUnit == unit) + { + if(!descriptorsReferenced.contains(access.byteOffset)) + descriptorsReferenced.push_back(access.byteOffset); + + const ShaderResource &res = refl->readOnlyResources[access.index]; + GLenum t = eGL_NONE; + + switch(res.resType) + { + case TextureType::Unknown: target = eGL_NONE; break; + case TextureType::Buffer: target = eGL_TEXTURE_BUFFER; break; + case TextureType::Texture1D: target = eGL_TEXTURE_1D; break; + case TextureType::Texture1DArray: target = eGL_TEXTURE_1D_ARRAY; break; + case TextureType::Texture2D: target = eGL_TEXTURE_2D; break; + case TextureType::TextureRect: target = eGL_TEXTURE_RECTANGLE; break; + case TextureType::Texture2DArray: target = eGL_TEXTURE_2D_ARRAY; break; + case TextureType::Texture2DMS: target = eGL_TEXTURE_2D_MULTISAMPLE; break; + case TextureType::Texture2DMSArray: target = eGL_TEXTURE_2D_MULTISAMPLE_ARRAY; break; + case TextureType::Texture3D: target = eGL_TEXTURE_3D; break; + case TextureType::TextureCube: target = eGL_TEXTURE_CUBE_MAP; break; + case TextureType::TextureCubeArray: target = eGL_TEXTURE_CUBE_MAP_ARRAY; break; + case TextureType::Count: RDCERR("Invalid shader resource type"); break; + } + + if(target != eGL_NONE) + t = TextureBinding(target); + + if(binding == eGL_NONE) + { + binding = t; + firstBindName = res.name; + resType = res.resType; + } + else if(binding == t) + { + // two uniforms with the same type pointing to the same slot is fine + binding = t; + } + else if(binding != t) + { + RDCERR("Two uniforms pointing to texture unit %d with types %s and %s", unit, + ToStr(binding).c_str(), ToStr(t).c_str()); + + if(typeConflict.empty()) + { + typeConflict = StringFormat::Fmt("First binding found '%s' is %s", + firstBindName.c_str(), ToStr(resType).c_str()); + } + + typeConflict += + StringFormat::Fmt(", '%s' is %s", res.name.c_str(), ToStr(res.resType).c_str()); + } + } + } + + // if we found a type conflict, add an entry for all descriptors + if(!typeConflict.empty()) + { + for(uint32_t descriptor : descriptorsReferenced) + { + bool found = false; + for(GLPipe::TextureCompleteness &completeness : pipe.textureCompleteness) + { + if(completeness.descriptorByteOffset == descriptor) + { + // don't worry about overwriting, the descriptor byte offset is unique to the unit so + // we should only set this at most once + completeness.typeConflict = typeConflict; + found = true; + } + } + + if(!found) + { + GLPipe::TextureCompleteness completeness; + completeness.descriptorByteOffset = descriptor; + completeness.typeConflict = typeConflict; + pipe.textureCompleteness.push_back(completeness); + } + } + } + } + } + RDCEraseEl(pipe.transformFeedback); if(HasExt[ARB_transform_feedback2]) diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 1c62274fe..6730e73c0 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -1842,6 +1842,16 @@ void DoSerialise(SerialiserType &ser, GLPipe::Texture &el) SIZE_CHECK(80); } +template +void DoSerialise(SerialiserType &ser, GLPipe::TextureCompleteness &el) +{ + SERIALISE_MEMBER(descriptorByteOffset); + SERIALISE_MEMBER(completeStatus); + SERIALISE_MEMBER(typeConflict); + + SIZE_CHECK(56); +} + template void DoSerialise(SerialiserType &ser, GLPipe::Sampler &el) { @@ -2048,6 +2058,7 @@ void DoSerialise(SerialiserType &ser, GLPipe::State &el) SERIALISE_MEMBER(descriptorStore); SERIALISE_MEMBER(descriptorCount); SERIALISE_MEMBER(descriptorByteSize); + SERIALISE_MEMBER(textureCompleteness); SERIALISE_MEMBER(transformFeedback); @@ -2059,7 +2070,7 @@ void DoSerialise(SerialiserType &ser, GLPipe::State &el) SERIALISE_MEMBER(hints); - SIZE_CHECK(1968); + SIZE_CHECK(1992); } #pragma endregion OpenGL pipeline state