diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index eda7949d8..a936538b5 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -1967,7 +1967,42 @@ void GLReplay::OpenGLFillCBufferVariables(GLuint prog, bool bufferBacked, std::s if(idx == GL_INVALID_INDEX) { - RDCERR("Can't find program resource index for %s", fullname.c_str()); + // this might not be an error, this might be the corresponding member in an array-of-structs + // that doesn't exist because it's not in a UBO. + // e.g.: + // struct foo { float a; float b; } + // uniform foo bar[2]; + // + // If the program only references bar[0].a and bar[1].b then we'd reflect the full structure + // but only bar[0].a and bar[1].b would have indices - bar[0].b and bar[1].a would not. + RDCWARN("Can't find program resource index for %s", fullname.c_str()); + + if(bufferBacked) + RDCERR("Uniform is buffer backed - index expected"); + + // if this is an array, generate empty members + if(desc.elements > 0) + { + std::vector elems; + for(uint32_t a = 0; a < desc.elements; a++) + { + ShaderVariable el = var; + + // if this is the last part of a multidimensional array, don't include the variable name + if(var.name[0] != '[') + el.name = StringFormat::Fmt("%s[%u]", var.name.c_str(), a); + else + el.name = StringFormat::Fmt("[%u]", a); + + el.isStruct = false; + + elems.push_back(el); + } + + var.members = elems; + var.isStruct = false; + var.rows = var.columns = 0; + } } else { @@ -2042,7 +2077,8 @@ void GLReplay::OpenGLFillCBufferVariables(GLuint prog, bool bufferBacked, std::s StandardFillCBufferVariable(offset, data, el, matStride); - offset += desc.arrayByteStride; + if(bufferBacked) + offset += desc.arrayByteStride; el.isStruct = false; diff --git a/renderdoc/driver/gl/gl_shader_refl.cpp b/renderdoc/driver/gl/gl_shader_refl.cpp index 644964cd2..4dd5882a5 100644 --- a/renderdoc/driver/gl/gl_shader_refl.cpp +++ b/renderdoc/driver/gl/gl_shader_refl.cpp @@ -712,6 +712,23 @@ void ReconstructVarTree(GLenum query, GLuint sepProg, GLuint varIdx, GLint numPa var.type.descriptor.arrayByteStride = values[7]; var.type.descriptor.matrixByteStride = (uint8_t)values[8]; + bool bareUniform = false; + + // for plain uniforms we won't get an array/matrix byte stride. Calculate tightly packed strides + if(values[3] == -1) + { + bareUniform = true; + + // plain matrices are always column major, so this is the size of a column + var.type.descriptor.rowMajorStorage = false; + + const uint32_t elemByteStride = (var.type.descriptor.type == VarType::Double) ? 8 : 4; + var.type.descriptor.matrixByteStride = uint8_t(var.type.descriptor.rows * elemByteStride); + + // arrays are fetched as individual glGetUniform calls + var.type.descriptor.arrayByteStride = 0; + } + // set vectors as row major for convenience, since that's how they're stored in the fv array. switch(values[0]) { @@ -741,6 +758,8 @@ void ReconstructVarTree(GLenum query, GLuint sepProg, GLuint varIdx, GLint numPa var.name.resize(values[1] - 1); GL.glGetProgramResourceName(sepProg, query, varIdx, values[1], NULL, &var.name[0]); + std::string fullname = var.name; + int32_t c = values[1] - 1; // trim off trailing [0] if it's an array @@ -758,7 +777,7 @@ void ReconstructVarTree(GLenum query, GLuint sepProg, GLuint varIdx, GLint numPa rdcarray *parentmembers = defaultBlock; - if(values[3] != -1 && values[3] < numParentBlocks) + if(!bareUniform && values[3] < numParentBlocks) { parentmembers = &parentBlocks[values[3]]; } @@ -917,7 +936,12 @@ void ReconstructVarTree(GLenum query, GLuint sepProg, GLuint varIdx, GLint numPa // the 0th element of each array fills out the actual members, when we // encounter an index above that we only use it to increase the type.descriptor.elements // member (which we've done by this point) and can stop recursing - if(arrayIdx > 0) + // + // The exception is when we're looking at bare uniforms - there the struct members all have + // individual locations and are generally aggressively stripped by the driver. + // So then it's possible to get foo[0].bar.a and foo[1].bar.b - so higher indices can reveal + // more of the structure. + if(arrayIdx > 0 && !bareUniform) { parentmembers = NULL; break; @@ -926,10 +950,43 @@ void ReconstructVarTree(GLenum query, GLuint sepProg, GLuint varIdx, GLint numPa if(parentmembers) { + // if this is a bare uniform we need to be careful - just above we continued iterating for + // higher indices, because you can have cases that return e.g. foo[0].bar.a and foo[1].bar.b and + // get 'more information' from later indices, the full structure is not revealed under foo[0]. + // However as a result of that it means we could see foo[0].bar.a and foo[1].bar.a - we need to + // be careful not to add the final 'a' twice. Check for duplicates and be sure it's really a + // duplicate. + bool duplicate = false; + // nm points into var.name's storage, so copy out to a temporary - string n = nm; + std::string n = nm; var.name = n; + if(bareUniform && !multiDimArray) + { + for(size_t i = 0; i < parentmembers->size(); i++) + { + if((*parentmembers)[i].name == var.name) + { + ShaderVariableDescriptor &oldtype = (*parentmembers)[i].type.descriptor; + ShaderVariableDescriptor &newtype = var.type.descriptor; + + if(oldtype.rows != newtype.rows || oldtype.columns != newtype.columns || + oldtype.type != newtype.type || oldtype.elements != newtype.elements) + { + RDCERR("When reconstructing %s, found duplicate but different final member %s", + fullname.c_str(), (*parentmembers)[i].name.c_str()); + } + + duplicate = true; + break; + } + } + } + + if(duplicate) + return; + // for multidimensional arrays there will be no proper name, so name the variable by the index if(multiDimArray) var.name = StringFormat::Fmt("[%d]", arrayIdx);