From 3aa64e2e8e600e513ea5380961cf69e1b8d7fdcf Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 30 Jan 2019 17:19:00 +0000 Subject: [PATCH] Completely emulate ARB_program_interface_query using glslang * We already did this partially, enough for capturing, but now we also do it on replay. --- renderdoc/driver/gl/gl_common.cpp | 5 +- renderdoc/driver/gl/gl_common.h | 26 + renderdoc/driver/gl/gl_debug.cpp | 5 + renderdoc/driver/gl/gl_driver.h | 5 + renderdoc/driver/gl/gl_initstate.cpp | 50 +- renderdoc/driver/gl/gl_replay.cpp | 26 - renderdoc/driver/gl/gl_shader_refl.cpp | 265 ++-- renderdoc/driver/gl/gl_shader_refl.h | 32 +- renderdoc/driver/gl/wrappers/gl_emulated.cpp | 1325 ++++++++++++++++- .../driver/gl/wrappers/gl_shader_funcs.cpp | 60 +- .../driver/shaders/spirv/spirv_common.cpp | 380 ++++- renderdoc/driver/shaders/spirv/spirv_common.h | 5 +- .../driver/shaders/spirv/spirv_compile.cpp | 4 +- 13 files changed, 1895 insertions(+), 293 deletions(-) diff --git a/renderdoc/driver/gl/gl_common.cpp b/renderdoc/driver/gl/gl_common.cpp index 57a598980..8a1871c10 100644 --- a/renderdoc/driver/gl/gl_common.cpp +++ b/renderdoc/driver/gl/gl_common.cpp @@ -115,9 +115,6 @@ bool CheckReplayContext() // we require the below extensions on top of a 3.2 context. Some of these we could in theory // do without, but support for them is so widespread it's not worthwhile - // for program introspection, needed for shader reflection. - // Possible to remove by compiling shaders to SPIR-V and reflecting ourselves. - REQUIRE_EXTENSION(ARB_program_interface_query); // needed for program pipelines, glProgramUniform*, and reflecting shaders on their own // Possible to remove this with self-compiled SPIR-V for reflection - see above. Likewise // convenience for our own pipelines when replacing single shaders or such. @@ -2594,6 +2591,8 @@ TEST_CASE("GL formats", "[format][gl]") CHECK(size == GetByteSize(123, 456, 1, GetBaseFormat(f), GetDataType(f))); } }; + + GL = GLDispatchTable(); }; #endif // ENABLED(ENABLE_UNIT_TESTS) diff --git a/renderdoc/driver/gl/gl_common.h b/renderdoc/driver/gl/gl_common.h index 1c7ae5038..e728ff35f 100644 --- a/renderdoc/driver/gl/gl_common.h +++ b/renderdoc/driver/gl/gl_common.h @@ -293,6 +293,32 @@ struct GLPlatform virtual void *GetReplayFunction(const char *funcname) = 0; }; +class GLDummyPlatform : public GLPlatform +{ + virtual GLWindowingData CloneTemporaryContext(GLWindowingData share) { return GLWindowingData(); } + virtual void DeleteClonedContext(GLWindowingData context) {} + virtual void DeleteReplayContext(GLWindowingData context) {} + virtual bool MakeContextCurrent(GLWindowingData data) { return true; } + virtual void SwapBuffers(GLWindowingData context) {} + virtual void WindowResized(GLWindowingData context) {} + virtual void GetOutputWindowDimensions(GLWindowingData context, int32_t &w, int32_t &h) {} + virtual bool IsOutputWindowVisible(GLWindowingData context) { return false; } + virtual GLWindowingData MakeOutputWindow(WindowingData window, bool depth, + GLWindowingData share_context) + { + return GLWindowingData(); + } + virtual void DrawQuads(float width, float height, const std::vector &vertices) {} + virtual void *GetReplayFunction(const char *funcname) { return NULL; } + // for initialisation at replay time + virtual bool CanCreateGLESContext() { return true; } + virtual bool PopulateForReplay() { return true; } + virtual ReplayStatus InitialiseAPI(GLWindowingData &replayContext, RDCDriver api) + { + return ReplayStatus::Succeeded; + } +}; + struct GLVersion { int major; diff --git a/renderdoc/driver/gl/gl_debug.cpp b/renderdoc/driver/gl/gl_debug.cpp index 96cf01a22..6d58bd79a 100644 --- a/renderdoc/driver/gl/gl_debug.cpp +++ b/renderdoc/driver/gl/gl_debug.cpp @@ -288,6 +288,9 @@ void GLReplay::InitDebugData() if(m_pDriver == NULL) return; + // don't reflect any shaders or programs we make + m_pDriver->PushInternalShader(); + m_HighlightCache.driver = m_pDriver->GetReplay(); RenderDoc::Inst().SetProgress(LoadProgress::DebugManagerInit, 0.0f); @@ -1048,6 +1051,8 @@ void GLReplay::InitDebugData() "Don't have shader image load/store or compute shaders, functionality will be degraded."); m_Degraded = true; } + + m_pDriver->PopInternalShader(); } void GLReplay::DeleteDebugData() diff --git a/renderdoc/driver/gl/gl_driver.h b/renderdoc/driver/gl/gl_driver.h index 10ae6e2a7..4c77dcb0a 100644 --- a/renderdoc/driver/gl/gl_driver.h +++ b/renderdoc/driver/gl/gl_driver.h @@ -140,6 +140,8 @@ private: uintptr_t m_ShareGroupID; + uint32_t m_InternalShader = 0; + std::vector m_LastContexts; std::set m_AcceptedCtx; @@ -497,6 +499,9 @@ public: ContextPair &GetCtx(); GLResourceRecord *GetContextRecord(); + void PushInternalShader() { m_InternalShader++; } + void PopInternalShader() { m_InternalShader--; } + bool IsInternalShader() { return m_InternalShader > 0; } void *ShareCtx(void *ctx) { return ctx ? m_ContextData[ctx].shareGroup : NULL; } void SetStructuredExport(uint64_t sectionVersion) { diff --git a/renderdoc/driver/gl/gl_initstate.cpp b/renderdoc/driver/gl/gl_initstate.cpp index a43728e8c..06cb61db2 100644 --- a/renderdoc/driver/gl/gl_initstate.cpp +++ b/renderdoc/driver/gl/gl_initstate.cpp @@ -1254,6 +1254,8 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r } else if(Type == eResProgram) { + WrappedOpenGL &drv = *m_Driver; + GLuint bindingsProgram = 0, uniformsProgram = 0; std::map *translationTable = NULL; @@ -1261,7 +1263,7 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r { WrappedOpenGL::ProgramData &details = m_Driver->m_Programs[GetLiveID(Id)]; - GLuint initProg = GL.glCreateProgram(); + GLuint initProg = drv.glCreateProgram(); uint32_t numShaders = 0; @@ -1275,7 +1277,7 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r const auto &shadDetails = m_Driver->m_Shaders[details.stageShaders[i]]; - GLuint shad = GL.glCreateShader(shadDetails.type); + GLuint shad = drv.glCreateShader(shadDetails.type); if(shadDetails.type == eGL_VERTEX_SHADER) { @@ -1301,24 +1303,24 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r char **srcs = new char *[shadDetails.sources.size()]; for(size_t s = 0; s < shadDetails.sources.size(); s++) srcs[s] = (char *)shadDetails.sources[s].c_str(); - GL.glShaderSource(shad, (GLsizei)shadDetails.sources.size(), srcs, NULL); + drv.glShaderSource(shad, (GLsizei)shadDetails.sources.size(), srcs, NULL); SAFE_DELETE_ARRAY(srcs); - GL.glCompileShader(shad); - GL.glAttachShader(initProg, shad); - GL.glDeleteShader(shad); + drv.glCompileShader(shad); + drv.glAttachShader(initProg, shad); + drv.glDeleteShader(shad); } else if(!shadDetails.spirvWords.empty()) { - GL.glShaderBinary(1, &shad, eGL_SHADER_BINARY_FORMAT_SPIR_V, shadDetails.spirvWords.data(), - (GLsizei)shadDetails.spirvWords.size() * sizeof(uint32_t)); + drv.glShaderBinary(1, &shad, eGL_SHADER_BINARY_FORMAT_SPIR_V, shadDetails.spirvWords.data(), + (GLsizei)shadDetails.spirvWords.size() * sizeof(uint32_t)); - GL.glSpecializeShader(shad, shadDetails.entryPoint.c_str(), - (GLuint)shadDetails.specIDs.size(), shadDetails.specIDs.data(), - shadDetails.specValues.data()); + drv.glSpecializeShader(shad, shadDetails.entryPoint.c_str(), + (GLuint)shadDetails.specIDs.size(), shadDetails.specIDs.data(), + shadDetails.specValues.data()); - GL.glAttachShader(initProg, shad); - GL.glDeleteShader(shad); + drv.glAttachShader(initProg, shad); + drv.glDeleteShader(shad); } else { @@ -1335,21 +1337,21 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r vertexOutputsPtr.resize(vertexOutputs.size()); for(size_t i = 0; i < vertexOutputs.size(); i++) vertexOutputsPtr[i] = vertexOutputs[i].c_str(); - GL.glTransformFeedbackVaryings(initProg, (GLsizei)vertexOutputsPtr.size(), - &vertexOutputsPtr[0], eGL_INTERLEAVED_ATTRIBS); - GL.glLinkProgram(initProg); + drv.glTransformFeedbackVaryings(initProg, (GLsizei)vertexOutputsPtr.size(), + &vertexOutputsPtr[0], eGL_INTERLEAVED_ATTRIBS); + drv.glLinkProgram(initProg); GLint status = 0; - GL.glGetProgramiv(initProg, eGL_LINK_STATUS, &status); + drv.glGetProgramiv(initProg, eGL_LINK_STATUS, &status); // if it failed to link, first remove the varyings hack above as maybe the driver is barfing // on trying to make some output a varying if(status == 0) { - GL.glTransformFeedbackVaryings(initProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS); - GL.glLinkProgram(initProg); + drv.glTransformFeedbackVaryings(initProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS); + drv.glLinkProgram(initProg); - GL.glGetProgramiv(initProg, eGL_LINK_STATUS, &status); + drv.glGetProgramiv(initProg, eGL_LINK_STATUS, &status); } // if it failed to link, try again as a separable program. @@ -1357,10 +1359,10 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r // shaders need fixup to be separable-compatible. if(status == 0) { - GL.glProgramParameteri(initProg, eGL_PROGRAM_SEPARABLE, 1); - GL.glLinkProgram(initProg); + drv.glProgramParameteri(initProg, eGL_PROGRAM_SEPARABLE, 1); + drv.glLinkProgram(initProg); - GL.glGetProgramiv(initProg, eGL_LINK_STATUS, &status); + drv.glGetProgramiv(initProg, eGL_LINK_STATUS, &status); } if(status == 0) @@ -1372,7 +1374,7 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r else { char buffer[1025] = {0}; - GL.glGetProgramInfoLog(initProg, 1024, NULL, buffer); + drv.glGetProgramInfoLog(initProg, 1024, NULL, buffer); RDCERR("Link error: %s", buffer); } } diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index 5631ff8a4..395f075d6 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -3491,32 +3491,6 @@ ReplayStatus CreateReplayDevice(RDCDriver rdcdriver, RDCFile *rdc, GLPlatform &p return ReplayStatus::Succeeded; } -class GLDummyPlatform : public GLPlatform -{ - virtual GLWindowingData CloneTemporaryContext(GLWindowingData share) { return GLWindowingData(); } - virtual void DeleteClonedContext(GLWindowingData context) {} - virtual void DeleteReplayContext(GLWindowingData context) {} - virtual bool MakeContextCurrent(GLWindowingData data) { return true; } - virtual void SwapBuffers(GLWindowingData context) {} - virtual void WindowResized(GLWindowingData context) {} - virtual void GetOutputWindowDimensions(GLWindowingData context, int32_t &w, int32_t &h) {} - virtual bool IsOutputWindowVisible(GLWindowingData context) { return false; } - virtual GLWindowingData MakeOutputWindow(WindowingData window, bool depth, - GLWindowingData share_context) - { - return GLWindowingData(); - } - virtual void DrawQuads(float width, float height, const std::vector &vertices) {} - virtual void *GetReplayFunction(const char *funcname) { return NULL; } - // for initialisation at replay time - virtual bool CanCreateGLESContext() { return true; } - virtual bool PopulateForReplay() { return true; } - virtual ReplayStatus InitialiseAPI(GLWindowingData &replayContext, RDCDriver api) - { - return ReplayStatus::Succeeded; - } -}; - void GL_ProcessStructured(RDCFile *rdc, SDFile &output) { GLDummyPlatform dummy; diff --git a/renderdoc/driver/gl/gl_shader_refl.cpp b/renderdoc/driver/gl/gl_shader_refl.cpp index ca4d6e7cd..b24a7a780 100644 --- a/renderdoc/driver/gl/gl_shader_refl.cpp +++ b/renderdoc/driver/gl/gl_shader_refl.cpp @@ -28,6 +28,41 @@ #include "3rdparty/glslang/glslang/Public/ShaderLang.h" #include "gl_driver.h" +template <> +std::string DoStringise(const FFVertexOutput &el) +{ + BEGIN_ENUM_STRINGISE(FFVertexOutput); + { + STRINGISE_ENUM_CLASS_NAMED(PointSize, "gl_PointSize"); + STRINGISE_ENUM_CLASS_NAMED(ClipDistance, "gl_ClipDistance"); + STRINGISE_ENUM_CLASS_NAMED(ClipVertex, "gl_ClipVertex"); + STRINGISE_ENUM_CLASS_NAMED(FrontColor, "gl_FrontColor"); + STRINGISE_ENUM_CLASS_NAMED(BackColor, "gl_BackColor"); + STRINGISE_ENUM_CLASS_NAMED(FrontSecondaryColor, "gl_FrontSecondaryColor"); + STRINGISE_ENUM_CLASS_NAMED(BackSecondaryColor, "gl_BackSecondaryColor"); + STRINGISE_ENUM_CLASS_NAMED(TexCoord, "gl_TexCoord"); + STRINGISE_ENUM_CLASS_NAMED(FogFragCoord, "gl_FogFragCoord"); + STRINGISE_ENUM_CLASS_NAMED(Count, "gl_Count"); + } + END_ENUM_STRINGISE(); +} + +void namesort(rdcarray &vars) +{ + if(vars.empty()) + return; + + struct name_sort + { + bool operator()(const ShaderConstant &a, const ShaderConstant &b) { return a.name < b.name; } + }; + + std::sort(vars.begin(), vars.end(), name_sort()); + + for(size_t i = 0; i < vars.size(); i++) + namesort(vars[i].type.members); +} + void sort(rdcarray &vars) { if(vars.empty()) @@ -41,64 +76,42 @@ void sort(rdcarray &vars) sort(vars[i].type.members); } -void CheckVertexOutputUses(const vector &sources, bool &pointSizeUsed, bool &clipDistanceUsed) +void CheckVertexOutputUses(const std::vector &sources, + FixedFunctionVertexOutputs &outputUsage) { - pointSizeUsed = false; - clipDistanceUsed = false; + outputUsage = FixedFunctionVertexOutputs(); - for(size_t i = 0; i < sources.size(); i++) + for(FFVertexOutput output : values()) { - const string &s = sources[i]; + // we consider an output used if we encounter a '=' before either a ';' or the end of the string + std::string name = ToStr(output); - size_t offs = 0; - - for(;;) + for(size_t i = 0; i < sources.size(); i++) { - offs = s.find("gl_PointSize", offs); + const std::string &s = sources[i]; - if(offs == string::npos) - break; + size_t offs = 0; - // consider gl_PointSize used if we encounter a '=' before a ';' or the end of the string - - while(offs < s.length()) + for(;;) { - if(s[offs] == '=') + offs = s.find(name, offs); + + if(offs == string::npos) + break; + + while(offs < s.length()) { - pointSizeUsed = true; - break; + if(s[offs] == '=') + { + outputUsage.used[(int)output] = true; + break; + } + + if(s[offs] == ';') + break; + + offs++; } - - if(s[offs] == ';') - break; - - offs++; - } - } - - offs = 0; - - for(;;) - { - offs = s.find("gl_ClipDistance", offs); - - if(offs == string::npos) - break; - - // consider gl_ClipDistance used if we encounter a '=' before a ';' or the end of the string - - while(offs < s.length()) - { - if(s[offs] == '=') - { - clipDistanceUsed = true; - break; - } - - if(s[offs] == ';') - break; - - offs++; } } } @@ -116,35 +129,35 @@ static GLuint CreateSepProgram(WrappedOpenGL &driver, GLenum type, GLsizei numSo GLuint program = 0; // definition of glCreateShaderProgramv from the spec - GLuint shader = GL.glCreateShader(type); + GLuint shader = driver.glCreateShader(type); if(shader) { - GL.glShaderSource(shader, numSources, sources, NULL); + driver.glShaderSource(shader, numSources, sources, NULL); if(paths == NULL) - GL.glCompileShader(shader); + driver.glCompileShader(shader); else - GL.glCompileShaderIncludeARB(shader, numPaths, paths, NULL); + driver.glCompileShaderIncludeARB(shader, numPaths, paths, NULL); - program = GL.glCreateProgram(); + program = driver.glCreateProgram(); if(program) { GLint compiled = 0; - GL.glGetShaderiv(shader, eGL_COMPILE_STATUS, &compiled); - GL.glProgramParameteri(program, eGL_PROGRAM_SEPARABLE, GL_TRUE); + driver.glGetShaderiv(shader, eGL_COMPILE_STATUS, &compiled); + driver.glProgramParameteri(program, eGL_PROGRAM_SEPARABLE, GL_TRUE); if(compiled) { - GL.glAttachShader(program, shader); - GL.glLinkProgram(program); + driver.glAttachShader(program, shader); + driver.glLinkProgram(program); // we deliberately leave the shaders attached so this program can be re-linked. // they will be cleaned up when the program is deleted // driver.glDetachShader(program, shader); } } - GL.glDeleteShader(shader); + driver.glDeleteShader(shader); } driver.SuppressDebugMessages(false); @@ -1037,8 +1050,50 @@ int ParseVersionStatement(const char *version) return ret; } +static void AddSigParameter(vector &sigs, uint32_t ®Index, const SigParameter &sig, + const char *nm, int rows, int arrayIdx) +{ + if(rows == 1) + { + SigParameter s = sig; + + if(s.regIndex == ~0U) + s.regIndex = regIndex++; + + if(arrayIdx >= 0) + { + s.arrayIndex = arrayIdx; + s.varName = StringFormat::Fmt("%s[%d]", nm, arrayIdx); + } + + sigs.push_back(s); + } + else + { + for(int r = 0; r < rows; r++) + { + SigParameter s = sig; + + if(s.regIndex == ~0U) + s.regIndex = regIndex++; + + if(arrayIdx >= 0) + { + s.arrayIndex = arrayIdx; + s.varName = StringFormat::Fmt("%s[%d]:row%d", nm, arrayIdx, r); + } + else + { + s.varName = StringFormat::Fmt("%s:row%d", nm, r); + } + + sigs.push_back(s); + } + } +} + void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &refl, - bool pointSizeUsed, bool clipDistanceUsed) + const FixedFunctionVertexOutputs &outputUsage) { if(shadType == eGL_COMPUTE_SHADER) { @@ -1616,7 +1671,7 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref res.resType = TextureType::Buffer; res.variableType.descriptor.rows = 0; res.variableType.descriptor.columns = 0; - res.variableType.descriptor.elements = len; + res.variableType.descriptor.elements = 0; res.variableType.descriptor.rowMajorStorage = false; res.variableType.descriptor.arrayByteStride = 0; res.variableType.descriptor.matrixByteStride = 0; @@ -1625,13 +1680,15 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref res.bindPoint = (int32_t)rwresources.size(); res.name = nm; + GLint numMembers = 0; + propName = eGL_NUM_ACTIVE_VARIABLES; GL.glGetProgramResourceiv(sepProg, eGL_SHADER_STORAGE_BLOCK, u, 1, &propName, 1, NULL, - (GLint *)&res.variableType.descriptor.elements); + (GLint *)&numMembers); rwresources.push_back(res); ssbos.push_back(res.bindPoint); - ssboMembers += res.variableType.descriptor.elements; + ssboMembers += numMembers; delete[] nm; } @@ -1739,7 +1796,9 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref globals.bufferBacked = false; globals.bindPoint = (int32_t)refl.constantBlocks.size(); - sort(globalUniforms); + // global uniforms have no defined order, location will be per implementation, so sort instead + // alphabetically + namesort(globalUniforms); std::swap(globals.variables, globalUniforms); refl.constantBlocks.push_back(globals); @@ -1759,16 +1818,20 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref { vector sigs; sigs.reserve(numInputs); + + uint32_t regIndex = 0; + for(GLint i = 0; i < numInputs; i++) { - GLenum props[] = {eGL_NAME_LENGTH, eGL_TYPE, eGL_LOCATION, eGL_LOCATION_COMPONENT}; - GLint values[] = {0, 0, 0, 0}; + GLenum props[] = {eGL_NAME_LENGTH, eGL_TYPE, eGL_LOCATION, eGL_ARRAY_SIZE, + eGL_LOCATION_COMPONENT}; + GLint values[] = {0, 0, 0, 0, 0}; GLsizei numSigProps = (GLsizei)ARRAY_COUNT(props); // GL_LOCATION_COMPONENT not supported on core <4.4 (or without GL_ARB_enhanced_layouts) - // and on GLES, either - if(!HasExt[ARB_enhanced_layouts]) + // on GLES, or when we don't have native program interface query + if(!HasExt[ARB_enhanced_layouts] || !HasExt[ARB_program_interface_query]) numSigProps--; GL.glGetProgramResourceiv(sepProg, sigEnum, i, numSigProps, props, numSigProps, NULL, values); @@ -1924,19 +1987,35 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref break; } - sig.regChannelMask <<= values[3]; - - sig.channelUsedMask = sig.regChannelMask; - sig.systemValue = ShaderBuiltin::Undefined; -#define IS_BUILTIN(builtin) !strncmp(nm, builtin, sizeof(builtin) - 1) + const char *varname = nm; - // if these weren't used, they were probably added just to make a separable program - // (either by us or the program originally). Skip them from the output signature - if(IS_BUILTIN("gl_PointSize") && !pointSizeUsed) - continue; - if(IS_BUILTIN("gl_ClipDistance") && !clipDistanceUsed) + if(!strncmp(varname, "gl_PerVertex.", 13)) + varname += 13; + +#define IS_BUILTIN(builtin) !strncmp(varname, builtin, sizeof(builtin) - 1) + + // some vertex outputs can be reflected (especially by glslang) if they're just declared and + // not used, which is quite common with redeclaring outputs for separable programs - either + // by the program or by us. So instead use our manual quick-and-dirty usage check to skip + // potential false-positives. + bool unused = false; + for(FFVertexOutput ffoutput : ::values()) + { + // we consider an output used if we encounter a '=' before either a ';' or the end of the + // string + std::string outName = ToStr(ffoutput); + + // we do a substring search so that gl_ClipDistance matches gl_ClipDistance[0] + if(strstr(varname, outName.c_str())) + { + unused = !outputUsage.used[(int)ffoutput]; + break; + } + } + + if(unused) continue; // VS built-in inputs @@ -2026,27 +2105,39 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref sig.systemValue = ShaderBuiltin::GroupFlatIndex; #undef IS_BUILTIN + if(sig.systemValue == ShaderBuiltin::Undefined) + sig.regIndex = values[2] >= 0 ? values[2] : ~0U; + else + sig.regIndex = 0; + if(shadType == eGL_FRAGMENT_SHADER && sigEnum == eGL_PROGRAM_OUTPUT && sig.systemValue == ShaderBuiltin::Undefined) sig.systemValue = ShaderBuiltin::ColorOutput; + // don't apply location component for built-ins if(sig.systemValue == ShaderBuiltin::Undefined) - sig.regIndex = values[2] >= 0 ? values[2] : i; - else - sig.regIndex = values[2] >= 0 ? values[2] : 0; + sig.regChannelMask <<= values[4]; - if(rows == 1) + sig.channelUsedMask = sig.regChannelMask; + + if(values[3] <= 1) { - sigs.push_back(sig); + AddSigParameter(sigs, regIndex, sig, nm, rows, -1); } else { - for(int r = 0; r < rows; r++) + std::string basename = nm; + if(basename[basename.size() - 3] == '[' && basename[basename.size() - 2] == '0' && + basename[basename.size() - 1] == ']') { - SigParameter s = sig; - s.varName = StringFormat::Fmt("%s:row%d", nm, r); - s.regIndex += r; - sigs.push_back(s); + basename.erase(basename.size() - 3); + for(int a = 0; a < values[3]; a++) + AddSigParameter(sigs, regIndex, sig, basename.c_str(), rows, a); + } + else + { + RDCWARN("Got signature parameter %s with array size %d but no [0] suffix", nm, values[3]); + AddSigParameter(sigs, regIndex, sig, nm, rows, -1); } } diff --git a/renderdoc/driver/gl/gl_shader_refl.h b/renderdoc/driver/gl/gl_shader_refl.h index 35d96bd84..93f7308e5 100644 --- a/renderdoc/driver/gl/gl_shader_refl.h +++ b/renderdoc/driver/gl/gl_shader_refl.h @@ -27,10 +27,36 @@ class WrappedOpenGL; +enum class FFVertexOutput : uint32_t +{ + // Core members of gl_PerVertex + PointSize, + First = PointSize, + ClipDistance, + + // Compatibility implicit varyings, generally only comes back from glslang's reflection + ClipVertex, + FrontColor, + BackColor, + FrontSecondaryColor, + BackSecondaryColor, + TexCoord, + FogFragCoord, + Count, +}; + +DECLARE_REFLECTION_ENUM(FFVertexOutput); +ITERABLE_OPERATORS(FFVertexOutput); + +struct FixedFunctionVertexOutputs +{ + bool used[arraydim()] = {}; +}; + int ParseVersionStatement(const char *version); void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &refl, - bool pointSizeUsed, bool clipDistanceUsed); + const FixedFunctionVertexOutputs &outputUsage); GLuint MakeSeparableShaderProgram(WrappedOpenGL &drv, GLenum type, std::vector sources, vector *includepaths); -void CheckVertexOutputUses(const std::vector &sources, bool &pointSizeUsed, - bool &clipDistanceUsed); +void CheckVertexOutputUses(const std::vector &sources, + FixedFunctionVertexOutputs &outputUsage); diff --git a/renderdoc/driver/gl/wrappers/gl_emulated.cpp b/renderdoc/driver/gl/wrappers/gl_emulated.cpp index 9c4c11523..0d625da15 100644 --- a/renderdoc/driver/gl/wrappers/gl_emulated.cpp +++ b/renderdoc/driver/gl/wrappers/gl_emulated.cpp @@ -1831,7 +1831,7 @@ void APIENTRY _glClearBufferData(GLenum target, GLenum internalformat, GLenum fo #pragma endregion -#pragma region GLES Compatibility +#pragma region ARB_program_interface_query static ReflectionInterface ConvertInterface(GLenum programInterface) { @@ -1839,14 +1839,16 @@ static ReflectionInterface ConvertInterface(GLenum programInterface) switch(programInterface) { - case eGL_PROGRAM_INPUT: ret = ReflectionInterface::Input; break; - case eGL_PROGRAM_OUTPUT: ret = ReflectionInterface::Output; break; case eGL_UNIFORM: ret = ReflectionInterface::Uniform; break; case eGL_UNIFORM_BLOCK: ret = ReflectionInterface::UniformBlock; break; + case eGL_PROGRAM_INPUT: ret = ReflectionInterface::Input; break; + case eGL_PROGRAM_OUTPUT: ret = ReflectionInterface::Output; break; case eGL_SHADER_STORAGE_BLOCK: ret = ReflectionInterface::ShaderStorageBlock; break; case eGL_ATOMIC_COUNTER_BUFFER: ret = ReflectionInterface::AtomicCounterBuffer; break; + case eGL_BUFFER_VARIABLE: ret = ReflectionInterface::BufferVariable; break; default: - RDCERR("Unexpected program interface being queried: %s", ToStr(programInterface).c_str()); + RDCERR("Unexpected/unsupported program interface being queried: %s", + ToStr(programInterface).c_str()); break; } @@ -1859,6 +1861,10 @@ static ReflectionProperty ConvertProperty(GLenum prop) switch(prop) { + // internal query used for testing + case eGL_UNIFORM: + case eGL_UNIFORM_BLOCK_BINDING: ret = ReflectionProperty::Internal_Binding; break; + case eGL_ACTIVE_RESOURCES: ret = ReflectionProperty::ActiveResources; break; case eGL_BUFFER_BINDING: ret = ReflectionProperty::BufferBinding; break; case eGL_TOP_LEVEL_ARRAY_STRIDE: ret = ReflectionProperty::TopLevelArrayStride; break; @@ -1870,6 +1876,7 @@ static ReflectionProperty ConvertProperty(GLenum prop) case eGL_NAME_LENGTH: ret = ReflectionProperty::NameLength; break; case eGL_TYPE: ret = ReflectionProperty::Type; break; case eGL_LOCATION_COMPONENT: ret = ReflectionProperty::LocationComponent; break; + case eGL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER: case eGL_REFERENCED_BY_VERTEX_SHADER: ret = ReflectionProperty::ReferencedByVertexShader; break; case eGL_REFERENCED_BY_TESS_CONTROL_SHADER: ret = ReflectionProperty::ReferencedByTessControlShader; @@ -1888,8 +1895,8 @@ static ReflectionProperty ConvertProperty(GLenum prop) break; case eGL_ATOMIC_COUNTER_BUFFER_INDEX: ret = ReflectionProperty::AtomicCounterBufferIndex; break; case eGL_OFFSET: ret = ReflectionProperty::Offset; break; - case eGL_MATRIX_STRIDE: ret = ReflectionProperty::MatrixStride; break; case eGL_ARRAY_STRIDE: ret = ReflectionProperty::ArrayStride; break; + case eGL_MATRIX_STRIDE: ret = ReflectionProperty::MatrixStride; break; case eGL_LOCATION: ret = ReflectionProperty::Location; break; default: RDCERR("Unexpected program property being queried: %s", ToStr(prop).c_str()); break; } @@ -1897,27 +1904,36 @@ static ReflectionProperty ConvertProperty(GLenum prop) return ret; } -void APIENTRY _glGetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, - GLint *params) +static glslang::TProgram *GetGlslangProgram(GLuint program) { if(driver == NULL) { - RDCERR("No driver available, can't emulate glGetProgramInterfaceiv"); - *params = 0; - return; + RDCERR("No driver available, can't emulate ARB_program_interface_query"); + return NULL; } ResourceId id = driver->GetResourceManager()->GetID(ProgramRes(driver->GetCtx(), program)); - WrappedOpenGL::ProgramData &details = driver->m_Programs[id]; + if(!driver->m_Programs[id].glslangProgram) + { + RDCERR("Don't have glslang program for reflecting program %u = %s", program, ToStr(id).c_str()); + } - if(!details.glslangProgram) + return driver->m_Programs[id].glslangProgram; +} + +void APIENTRY _glGetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, + GLint *params) +{ + glslang::TProgram *glslangProgram = GetGlslangProgram(program); + + if(!glslangProgram) { *params = 0; return; } - glslangGetProgramInterfaceiv(details.glslangProgram, ConvertInterface(programInterface), + glslangGetProgramInterfaceiv(glslangProgram, ConvertInterface(programInterface), ConvertProperty(pname), params); } @@ -1925,21 +1941,9 @@ void APIENTRY _glGetProgramResourceiv(GLuint program, GLenum programInterface, G GLsizei propCount, const GLenum *props, GLsizei bufSize, GLsizei *length, GLint *params) { - if(driver == NULL) - { - RDCERR("No driver available, can't emulate glGetProgramResourceiv"); - if(length) - *length = 0; - if(params) - memset(params, 0, sizeof(GLint) * bufSize); - return; - } + glslang::TProgram *glslangProgram = GetGlslangProgram(program); - ResourceId id = driver->GetResourceManager()->GetID(ProgramRes(driver->GetCtx(), program)); - - WrappedOpenGL::ProgramData &details = driver->m_Programs[id]; - - if(!details.glslangProgram) + if(!glslangProgram) { if(length) *length = 0; @@ -1953,48 +1957,77 @@ void APIENTRY _glGetProgramResourceiv(GLuint program, GLenum programInterface, G for(GLsizei i = 0; i < propCount; i++) properties[i] = ConvertProperty(props[i]); - glslangGetProgramResourceiv(details.glslangProgram, ConvertInterface(programInterface), index, - properties, bufSize, length, params); + glslangGetProgramResourceiv(glslangProgram, ConvertInterface(programInterface), index, properties, + bufSize, length, params); // fetch locations by hand from the driver for(GLsizei i = 0; i < propCount; i++) { if(props[i] == eGL_LOCATION) { - if(params[i] >= 0) + if(programInterface == eGL_UNIFORM && params[i] >= 0) { - const char *name = glslangGetProgramResourceName(details.glslangProgram, - ConvertInterface(programInterface), index); + const char *name = + glslangGetProgramResourceName(glslangProgram, ConvertInterface(programInterface), index); - if(programInterface == eGL_UNIFORM) + if(GL.glGetUniformLocation) params[i] = GL.glGetUniformLocation(program, name); - else if(programInterface == eGL_PROGRAM_INPUT) + } + else if(programInterface == eGL_PROGRAM_INPUT && params[i] < 0) + { + const char *name = + glslangGetProgramResourceName(glslangProgram, ConvertInterface(programInterface), index); + + if(GL.glGetAttribLocation) params[i] = GL.glGetAttribLocation(program, name); - else - params[i] = index; + } + else if(programInterface == eGL_PROGRAM_OUTPUT && params[i] < 0) + { + const char *name = + glslangGetProgramResourceName(glslangProgram, ConvertInterface(programInterface), index); + + if(GL.glGetFragDataLocation) + params[i] = GL.glGetFragDataLocation(program, name); + } + } + else if(props[i] == eGL_BUFFER_BINDING) + { + if(programInterface == eGL_UNIFORM_BLOCK) + { + const char *name = + glslangGetProgramResourceName(glslangProgram, ConvertInterface(programInterface), index); + + if(GL.glGetUniformBlockIndex) + { + GLuint blockIndex = GL.glGetUniformBlockIndex(program, name); + if(blockIndex != GL_INVALID_INDEX && GL.glGetActiveUniformBlockiv) + { + GL.glGetActiveUniformBlockiv(program, blockIndex, eGL_UNIFORM_BLOCK_BINDING, ¶ms[i]); + } + } } } } } +GLuint APIENTRY _glGetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar *name) +{ + glslang::TProgram *glslangProgram = GetGlslangProgram(program); + + if(!glslangProgram) + { + return 0; + } + + return glslangGetProgramResourceIndex(glslangProgram, name); +} + void APIENTRY _glGetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name) { - if(driver == NULL) - { - RDCERR("No driver available, can't emulate glGetProgramResourceName"); - if(length) - *length = 0; - if(name && bufSize) - memset(name, 0, bufSize); - return; - } + glslang::TProgram *glslangProgram = GetGlslangProgram(program); - ResourceId id = driver->GetResourceManager()->GetID(ProgramRes(driver->GetCtx(), program)); - - WrappedOpenGL::ProgramData &details = driver->m_Programs[id]; - - if(!details.glslangProgram) + if(!glslangProgram) { if(length) *length = 0; @@ -2003,8 +2036,8 @@ void APIENTRY _glGetProgramResourceName(GLuint program, GLenum programInterface, return; } - const char *fetchedName = glslangGetProgramResourceName( - details.glslangProgram, ConvertInterface(programInterface), index); + const char *fetchedName = + glslangGetProgramResourceName(glslangProgram, ConvertInterface(programInterface), index); if(fetchedName) { @@ -2026,6 +2059,10 @@ void APIENTRY _glGetProgramResourceName(GLuint program, GLenum programInterface, } } +#pragma endregion + +#pragma region GLES Compatibility + void APIENTRY _glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) { if(driver == NULL) @@ -2536,8 +2573,9 @@ void GLDispatchTable::EmulateRequiredExtensions() if(!HasExt[ARB_program_interface_query]) { EMULATE_FUNC(glGetProgramInterfaceiv); - EMULATE_FUNC(glGetProgramResourceiv); + EMULATE_FUNC(glGetProgramResourceIndex); EMULATE_FUNC(glGetProgramResourceName); + EMULATE_FUNC(glGetProgramResourceiv); } // only emulate ARB_vertex_attrib_binding on replay @@ -2720,3 +2758,1184 @@ void GLDispatchTable::DriverForEmulation(WrappedOpenGL *driver) { glEmulate::driver = driver; } + +#if ENABLED(ENABLE_UNIT_TESTS) + +#undef None + +#include "../gl_shader_refl.h" +#include "3rdparty/catch/catch.hpp" +#include "strings/string_utils.h" + +GLint APIENTRY _testStub_GetUniformLocation(GLuint program, const GLchar *name) +{ + // use existing ARB_program_interface_query to get value + GLuint index = GL.glGetProgramResourceIndex(program, eGL_UNIFORM, name); + RDCASSERT(index != GL_INVALID_INDEX); + + return index; +} + +void APIENTRY _testStub_GetUniformiv(GLuint program, GLint location, GLint *params) +{ + // abuse this query which returns the right value for uniform bindings also + GLenum prop = eGL_UNIFORM; + GL.glGetProgramResourceiv(program, eGL_UNIFORM, location, 1, &prop, 1, NULL, params); +} + +void APIENTRY _testStub_GetIntegerv(GLenum pname, GLint *params) +{ + // fixed definition + if(pname == eGL_MAX_VERTEX_ATTRIBS) + *params = 16; + else + RDCERR("Unexpected pname in test stub: %s", ToStr(pname).c_str()); +} + +void APIENTRY _testStub_GetActiveAtomicCounterBufferiv(GLuint program, GLuint bufferIndex, + GLenum pname, GLint *params) +{ + if(pname == eGL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER) + { + GLenum prop = eGL_REFERENCED_BY_VERTEX_SHADER; + GL.glGetProgramResourceiv(program, eGL_ATOMIC_COUNTER_BUFFER, bufferIndex, 1, &prop, 1, NULL, + params); + } + else if(pname == eGL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER) + { + GLenum prop = eGL_REFERENCED_BY_TESS_CONTROL_SHADER; + GL.glGetProgramResourceiv(program, eGL_ATOMIC_COUNTER_BUFFER, bufferIndex, 1, &prop, 1, NULL, + params); + } + else if(pname == eGL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER) + { + GLenum prop = eGL_REFERENCED_BY_TESS_EVALUATION_SHADER; + GL.glGetProgramResourceiv(program, eGL_ATOMIC_COUNTER_BUFFER, bufferIndex, 1, &prop, 1, NULL, + params); + } + else if(pname == eGL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER) + { + GLenum prop = eGL_REFERENCED_BY_GEOMETRY_SHADER; + GL.glGetProgramResourceiv(program, eGL_ATOMIC_COUNTER_BUFFER, bufferIndex, 1, &prop, 1, NULL, + params); + } + else if(pname == eGL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER) + { + GLenum prop = eGL_REFERENCED_BY_FRAGMENT_SHADER; + GL.glGetProgramResourceiv(program, eGL_ATOMIC_COUNTER_BUFFER, bufferIndex, 1, &prop, 1, NULL, + params); + } + else if(pname == eGL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_COMPUTE_SHADER) + { + GLenum prop = eGL_REFERENCED_BY_COMPUTE_SHADER; + GL.glGetProgramResourceiv(program, eGL_ATOMIC_COUNTER_BUFFER, bufferIndex, 1, &prop, 1, NULL, + params); + } + else if(pname == eGL_ATOMIC_COUNTER_BUFFER_BINDING) + { + GLenum prop = eGL_ATOMIC_COUNTER_BUFFER_INDEX; + GL.glGetProgramResourceiv(program, eGL_ATOMIC_COUNTER_BUFFER, bufferIndex, 1, &prop, 1, NULL, + params); + } + else + { + RDCERR("Unexpected pname in test stub: %s", ToStr(pname).c_str()); + } +} + +GLuint APIENTRY _testStub_GetUniformBlockIndex(GLuint program, const GLchar *uniformBlockName) +{ + return GL.glGetProgramResourceIndex(program, eGL_UNIFORM_BLOCK, uniformBlockName); +} + +void APIENTRY _testStub_GetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, + GLenum pname, GLint *params) +{ + if(pname == eGL_UNIFORM_BLOCK_BINDING) + { + // use this internal query which returns the default binding for uniform block bindings + GLenum prop = eGL_UNIFORM_BLOCK_BINDING; + GL.glGetProgramResourceiv(program, eGL_UNIFORM_BLOCK, uniformBlockIndex, 1, &prop, 1, NULL, + params); + } + else + { + RDCERR("Unexpected pname in test stub: %s", ToStr(pname).c_str()); + } +} + +GLint APIENTRY _testStub_AttribLocation(GLuint program, const GLchar *name) +{ + GLuint index = GL.glGetProgramResourceIndex(program, eGL_UNIFORM_BLOCK, name); + RDCASSERT(index != GL_INVALID_INDEX); + GLenum prop = eGL_LOCATION; + GLint value = -1; + RDCASSERT(GL.glGetAttribLocation == &_testStub_AttribLocation); + GL.glGetAttribLocation = NULL; + GL.glGetProgramResourceiv(program, eGL_PROGRAM_INPUT, index, 1, &prop, 1, NULL, &value); + GL.glGetAttribLocation = &_testStub_AttribLocation; + return value; +} + +void MakeOfflineShaderReflection(ShaderStage stage, const std::string &source, + ShaderReflection &refl, ShaderBindpointMapping &mapping) +{ + InitSPIRVCompiler(); + RenderDoc::Inst().RegisterShutdownFunction(&ShutdownSPIRVCompiler); + + // as a hack, create a local 'driver' and just populate m_Programs with what we want. + GLDummyPlatform dummy; + WrappedOpenGL driver(dummy); + + GL.DriverForEmulation(&driver); + + RDCEraseEl(HasExt); + // need to pretend to have SSBO extension so we reflect SSBOs + HasExt[ARB_shader_storage_buffer_object] = true; + GL = GLDispatchTable(); + GL.EmulateRequiredExtensions(); + + glslang::TShader *sh = CompileShaderForReflection(SPIRVShaderStage(stage), {source}); + + REQUIRE(sh); + + glslang::TProgram *prog = LinkProgramForReflection({sh}); + + REQUIRE(prog); + + // the lookup won't get a valid Id, so set the program to the ResourceId() + driver.m_Programs[ResourceId()].glslangProgram = prog; + + GLuint fakeProg = 0; + + FixedFunctionVertexOutputs outputUsage; + CheckVertexOutputUses({source}, outputUsage); + + MakeShaderReflection(ShaderEnum((size_t)stage), fakeProg, refl, outputUsage); + + // implement some stubs for testing + GL.glGetUniformLocation = &_testStub_GetUniformLocation; + GL.glGetUniformiv = &_testStub_GetUniformiv; + GL.glGetActiveAtomicCounterBufferiv = &_testStub_GetActiveAtomicCounterBufferiv; + GL.glGetUniformBlockIndex = &_testStub_GetUniformBlockIndex; + GL.glGetActiveUniformBlockiv = &_testStub_GetActiveUniformBlockiv; + GL.glGetIntegerv = &_testStub_GetIntegerv; + GL.glGetAttribLocation = &_testStub_AttribLocation; + + GetBindpointMapping(fakeProg, (int)stage, &refl, mapping); + + RDCEraseEl(HasExt); + GL = GLDispatchTable(); + GL.DriverForEmulation(NULL); +} + +// helper function that uses the replay proxy system to compile and reflect the shader using the +// current driver. Unused by default but you can change the unit test below to call this function +// instead of MakeOfflineShaderReflection. +// +// Note that we can't fill out ShaderBindpointMapping easily on the actual driver +void MakeOnlineShaderReflection(ShaderStage stage, const std::string &source, + ShaderReflection &refl, ShaderBindpointMapping &mapping) +{ + ReplayStatus status = ReplayStatus::UnknownError; + IReplayDriver *driver = NULL; + + std::map replays = RenderDoc::Inst().GetReplayDrivers(); + + if(replays.find(RDCDriver::OpenGL) != replays.end()) + status = RenderDoc::Inst().CreateProxyReplayDriver(RDCDriver::OpenGL, &driver); + + if(status != ReplayStatus::Succeeded) + { + RDCERR("No GL support locally, couldn't create proxy GL driver for reflection"); + return; + } + + ResourceId id; + std::string errors; + driver->BuildCustomShader(source, "main", ShaderCompileFlags(), stage, &id, &errors); + + if(id == ResourceId()) + { + RDCERR("Couldn't build shader for reflection:\n%s", errors.c_str()); + return; + } + + refl = *driver->GetShader(id, ShaderEntryPoint("main", ShaderStage::Fragment)); + + driver->FreeCustomShader(id); + + driver->Shutdown(); +} + +TEST_CASE("Validate ARB_program_interface_query emulation", "[opengl][glslang]") +{ + SECTION("Single shader deep dive") + { + std::string source = R"( +#version 450 core + +struct glstruct +{ + float a; + int b; + mat2x2 c; +}; + +layout(binding = 8, std140) uniform ubo_block { + float ubo_a; + layout(column_major) mat4x3 ubo_b; + layout(row_major) mat4x3 ubo_c; + ivec2 ubo_d; + vec2 ubo_e[3]; + glstruct ubo_f; + layout(offset = 256) vec4 ubo_z; +} ubo_root; + +layout(binding = 2, std430) buffer ssbo +{ + uint ssbo_a[10]; + glstruct ssbo_b[3]; + float ssbo_c; +} ssbo_root; + +layout(binding = 0) uniform atomic_uint atom; + +layout(location = 3) in vec2 a_input; +layout(location = 6) flat in uvec3 z_input; + +uniform vec3 global_var[5]; +uniform mat3x2 global_var2[3]; + +layout(binding = 3) uniform sampler2D tex2D; +layout(binding = 5) uniform isampler3D tex3D; + +layout(location = 0) out vec4 a_output; +layout(location = 1) out vec3 z_output; +layout(location = 2) out int b_output; + +void main() { + float a = ubo_root.ubo_a + global_var2[2][0][1]; + a_output = vec4(sin(float(a) + gl_FragCoord.x), 0, 0, 1); + z_output = textureLod(tex2D, a_output.xy, a_output.z).xyz + a_input.xyx + global_var[4]; + ssbo_root.ssbo_a[5] = 4 + atomicCounter(atom) + z_input.y; + b_output = ssbo_root.ssbo_b[2].b + texelFetch(tex3D, ivec3(z_input), 0).x; + gl_FragDepth = z_output.y; +} + +)"; + +#define REQUIRE_ARRAY_SIZE(size, min) \ + REQUIRE(size >= min); \ + CHECK(size == min); + + ShaderReflection refl; + ShaderBindpointMapping mapping; + MakeOfflineShaderReflection(ShaderStage::Fragment, source, refl, mapping); + + REQUIRE_ARRAY_SIZE(refl.inputSignature.size(), 3); + { + CHECK(refl.inputSignature[0].varName == "a_input"); + { + const SigParameter &sig = refl.inputSignature[0]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 3); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 2); + CHECK(sig.regChannelMask == 0x3); + CHECK(sig.channelUsedMask == 0x3); + } + + CHECK(refl.inputSignature[1].varName == "z_input"); + { + const SigParameter &sig = refl.inputSignature[1]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 6); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::UInt); + CHECK(sig.compCount == 3); + CHECK(sig.regChannelMask == 0x7); + CHECK(sig.channelUsedMask == 0x7); + } + + CHECK(refl.inputSignature[2].varName == "gl_FragCoord"); + { + const SigParameter &sig = refl.inputSignature[2]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Position); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 4); + CHECK(sig.regChannelMask == 0xf); + CHECK(sig.channelUsedMask == 0xf); + } + } + + REQUIRE_ARRAY_SIZE(refl.outputSignature.size(), 4); + { + CHECK(refl.outputSignature[0].varName == "a_output"); + { + const SigParameter &sig = refl.outputSignature[0]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::ColorOutput); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 4); + CHECK(sig.regChannelMask == 0xf); + CHECK(sig.channelUsedMask == 0xf); + } + + CHECK(refl.outputSignature[1].varName == "z_output"); + { + const SigParameter &sig = refl.outputSignature[1]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 1); + CHECK(sig.systemValue == ShaderBuiltin::ColorOutput); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 3); + CHECK(sig.regChannelMask == 0x7); + CHECK(sig.channelUsedMask == 0x7); + } + + CHECK(refl.outputSignature[2].varName == "b_output"); + { + const SigParameter &sig = refl.outputSignature[2]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 2); + CHECK(sig.systemValue == ShaderBuiltin::ColorOutput); + CHECK(sig.compType == CompType::SInt); + CHECK(sig.compCount == 1); + CHECK(sig.regChannelMask == 0x1); + CHECK(sig.channelUsedMask == 0x1); + } + + CHECK(refl.outputSignature[3].varName == "gl_FragDepth"); + { + const SigParameter &sig = refl.outputSignature[3]; + INFO("signature element: " << sig.varName.c_str()); + + // when not running with a driver we default to just using the index instead of looking up + // the location of outputs, so this will be wrong + // CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::DepthOutput); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 1); + CHECK(sig.regChannelMask == 0x1); + CHECK(sig.channelUsedMask == 0x1); + } + } + + REQUIRE_ARRAY_SIZE(refl.readOnlyResources.size(), 2); + { + CHECK(refl.readOnlyResources[0].name == "tex2D"); + { + const ShaderResource &res = refl.readOnlyResources[0]; + INFO("read-only resource: " << res.name.c_str()); + + CHECK(res.bindPoint == 0); + CHECK(res.resType == TextureType::Texture2D); + CHECK(res.variableType.members.empty()); + CHECK(res.variableType.descriptor.type == VarType::Float); + CHECK(res.variableType.descriptor.rows == 1); + CHECK(res.variableType.descriptor.columns == 4); + CHECK(res.variableType.descriptor.name == "sampler2D"); + } + + CHECK(refl.readOnlyResources[1].name == "tex3D"); + { + const ShaderResource &res = refl.readOnlyResources[1]; + INFO("read-only resource: " << res.name.c_str()); + + CHECK(res.bindPoint == 1); + CHECK(res.resType == TextureType::Texture3D); + CHECK(res.variableType.members.empty()); + CHECK(res.variableType.descriptor.type == VarType::SInt); + CHECK(res.variableType.descriptor.rows == 1); + CHECK(res.variableType.descriptor.columns == 4); + CHECK(res.variableType.descriptor.name == "isampler3D"); + } + } + + REQUIRE_ARRAY_SIZE(refl.readWriteResources.size(), 2); + { + CHECK(refl.readWriteResources[0].name == "atom"); + { + const ShaderResource &res = refl.readWriteResources[0]; + INFO("read-write resource: " << res.name.c_str()); + + CHECK(res.bindPoint == 0); + CHECK(res.resType == TextureType::Buffer); + CHECK(res.variableType.members.empty()); + CHECK(res.variableType.descriptor.type == VarType::UInt); + CHECK(res.variableType.descriptor.rows == 1); + CHECK(res.variableType.descriptor.columns == 1); + CHECK(res.variableType.descriptor.name == "atomic_uint"); + } + + CHECK(refl.readWriteResources[1].name == "ssbo"); + { + const ShaderResource &res = refl.readWriteResources[1]; + INFO("read-write resource: " << res.name.c_str()); + + CHECK(res.bindPoint == 1); + CHECK(res.resType == TextureType::Buffer); + CHECK(res.variableType.descriptor.type == VarType::UInt); + CHECK(res.variableType.descriptor.rows == 0); + CHECK(res.variableType.descriptor.columns == 0); + CHECK(res.variableType.descriptor.name == "buffer"); + + REQUIRE_ARRAY_SIZE(res.variableType.members.size(), 3); + { + CHECK(res.variableType.members[0].name == "ssbo_a"); + { + const ShaderConstant &member = res.variableType.members[0]; + INFO("SSBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 0); + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::UInt); + CHECK(member.type.descriptor.rows == 1); + CHECK(member.type.descriptor.columns == 1); + CHECK(member.type.descriptor.elements == 10); + CHECK(member.type.descriptor.arrayByteStride == 4); + CHECK(member.type.descriptor.name == "uint"); + } + + CHECK(res.variableType.members[1].name == "ssbo_b"); + { + const ShaderConstant &member = res.variableType.members[1]; + INFO("SSBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 40); + // this doesn't reflect in native introspection, so we skip it + // CHECK(member.type.descriptor.elements == 3); + CHECK(member.type.descriptor.name == "struct"); + CHECK(member.type.descriptor.arrayByteStride == 24); + + REQUIRE_ARRAY_SIZE(member.type.members.size(), 3); + { + CHECK(member.type.members[0].name == "a"); + { + const ShaderConstant &submember = member.type.members[0]; + INFO("SSBO submember: " << submember.name.c_str()); + + CHECK(submember.byteOffset == 40); + CHECK(submember.type.members.empty()); + CHECK(submember.type.descriptor.type == VarType::Float); + CHECK(submember.type.descriptor.rows == 1); + CHECK(submember.type.descriptor.columns == 1); + CHECK(submember.type.descriptor.name == "float"); + } + + CHECK(member.type.members[1].name == "b"); + { + const ShaderConstant &submember = member.type.members[1]; + INFO("SSBO submember: " << submember.name.c_str()); + + CHECK(submember.byteOffset == 44); + CHECK(submember.type.members.empty()); + CHECK(submember.type.descriptor.type == VarType::SInt); + CHECK(submember.type.descriptor.rows == 1); + CHECK(submember.type.descriptor.columns == 1); + CHECK(submember.type.descriptor.name == "int"); + } + + CHECK(member.type.members[2].name == "c"); + { + const ShaderConstant &submember = member.type.members[2]; + INFO("SSBO submember: " << submember.name.c_str()); + + CHECK(submember.byteOffset == 48); + CHECK(submember.type.members.empty()); + CHECK(submember.type.descriptor.type == VarType::Float); + CHECK(submember.type.descriptor.rows == 2); + CHECK(submember.type.descriptor.columns == 2); + CHECK(submember.type.descriptor.rowMajorStorage == false); + CHECK(submember.type.descriptor.name == "mat2"); + } + } + } + + CHECK(res.variableType.members[2].name == "ssbo_c"); + { + const ShaderConstant &member = res.variableType.members[2]; + INFO("SSBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 112); + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::Float); + CHECK(member.type.descriptor.rows == 1); + CHECK(member.type.descriptor.columns == 1); + CHECK(member.type.descriptor.name == "float"); + } + } + } + } + + REQUIRE_ARRAY_SIZE(refl.constantBlocks.size(), 2); + { + CHECK(refl.constantBlocks[0].name == "ubo_block"); + { + const ConstantBlock &cblock = refl.constantBlocks[0]; + INFO("UBO: " << cblock.name.c_str()); + + CHECK(cblock.bindPoint == 0); + CHECK(cblock.bufferBacked); + CHECK(cblock.byteSize == 272); + + REQUIRE_ARRAY_SIZE(cblock.variables.size(), 1); + + CHECK(cblock.variables[0].name == "ubo_block"); + const ShaderConstant &ubo_root = cblock.variables[0]; + + CHECK(ubo_root.byteOffset == 0); + CHECK(ubo_root.type.descriptor.name == "struct"); + + REQUIRE_ARRAY_SIZE(ubo_root.type.members.size(), 7); + { + CHECK(ubo_root.type.members[0].name == "ubo_a"); + { + const ShaderConstant &member = ubo_root.type.members[0]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 0); + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::Float); + CHECK(member.type.descriptor.rows == 1); + CHECK(member.type.descriptor.columns == 1); + CHECK(member.type.descriptor.name == "float"); + } + + CHECK(ubo_root.type.members[1].name == "ubo_b"); + { + const ShaderConstant &member = ubo_root.type.members[1]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 16); + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::Float); + CHECK(member.type.descriptor.rows == 3); + CHECK(member.type.descriptor.columns == 4); + CHECK(member.type.descriptor.rowMajorStorage == false); + CHECK(member.type.descriptor.name == "mat4x3"); + } + + CHECK(ubo_root.type.members[2].name == "ubo_c"); + { + const ShaderConstant &member = ubo_root.type.members[2]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 80); + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::Float); + CHECK(member.type.descriptor.rows == 3); + CHECK(member.type.descriptor.columns == 4); + CHECK(member.type.descriptor.rowMajorStorage == true); + CHECK(member.type.descriptor.name == "mat4x3"); + } + + CHECK(ubo_root.type.members[3].name == "ubo_d"); + { + const ShaderConstant &member = ubo_root.type.members[3]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 128); + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::SInt); + CHECK(member.type.descriptor.rows == 1); + CHECK(member.type.descriptor.columns == 2); + CHECK(member.type.descriptor.name == "ivec2"); + } + + CHECK(ubo_root.type.members[4].name == "ubo_e"); + { + const ShaderConstant &member = ubo_root.type.members[4]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 144); + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::Float); + CHECK(member.type.descriptor.rows == 1); + CHECK(member.type.descriptor.columns == 2); + CHECK(member.type.descriptor.elements == 3); + CHECK(member.type.descriptor.arrayByteStride == 16); + CHECK(member.type.descriptor.name == "vec2"); + } + + CHECK(ubo_root.type.members[5].name == "ubo_f"); + { + const ShaderConstant &member = ubo_root.type.members[5]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 192); + // this doesn't reflect in native introspection, so we skip it + // CHECK(member.type.descriptor.elements == 3); + CHECK(member.type.descriptor.name == "struct"); + + REQUIRE_ARRAY_SIZE(member.type.members.size(), 3); + { + CHECK(member.type.members[0].name == "a"); + { + const ShaderConstant &submember = member.type.members[0]; + INFO("UBO submember: " << submember.name.c_str()); + + CHECK(submember.byteOffset == 192); + CHECK(submember.type.members.empty()); + CHECK(submember.type.descriptor.type == VarType::Float); + CHECK(submember.type.descriptor.rows == 1); + CHECK(submember.type.descriptor.columns == 1); + CHECK(submember.type.descriptor.name == "float"); + } + + CHECK(member.type.members[1].name == "b"); + { + const ShaderConstant &submember = member.type.members[1]; + INFO("UBO submember: " << submember.name.c_str()); + + CHECK(submember.byteOffset == 196); + CHECK(submember.type.members.empty()); + CHECK(submember.type.descriptor.type == VarType::SInt); + CHECK(submember.type.descriptor.rows == 1); + CHECK(submember.type.descriptor.columns == 1); + CHECK(submember.type.descriptor.name == "int"); + } + + CHECK(member.type.members[2].name == "c"); + { + const ShaderConstant &submember = member.type.members[2]; + INFO("UBO submember: " << submember.name.c_str()); + + CHECK(submember.byteOffset == 208); + CHECK(submember.type.members.empty()); + CHECK(submember.type.descriptor.type == VarType::Float); + CHECK(submember.type.descriptor.rows == 2); + CHECK(submember.type.descriptor.columns == 2); + CHECK(submember.type.descriptor.rowMajorStorage == false); + CHECK(submember.type.descriptor.name == "mat2"); + } + } + } + + CHECK(ubo_root.type.members[6].name == "ubo_z"); + { + const ShaderConstant &member = ubo_root.type.members[6]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.byteOffset == 256); + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::Float); + CHECK(member.type.descriptor.rows == 1); + CHECK(member.type.descriptor.columns == 4); + CHECK(member.type.descriptor.name == "vec4"); + } + } + } + + CHECK(refl.constantBlocks[1].name == "$Globals"); + { + const ConstantBlock &cblock = refl.constantBlocks[1]; + INFO("UBO: " << cblock.name.c_str()); + + CHECK(cblock.bindPoint == 1); + CHECK(!cblock.bufferBacked); + + REQUIRE_ARRAY_SIZE(cblock.variables.size(), 2); + { + CHECK(cblock.variables[0].name == "global_var"); + { + const ShaderConstant &member = cblock.variables[0]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::Float); + CHECK(member.type.descriptor.rows == 1); + CHECK(member.type.descriptor.columns == 3); + CHECK(member.type.descriptor.elements == 5); + CHECK(member.type.descriptor.name == "vec3"); + } + + CHECK(cblock.variables[1].name == "global_var2"); + { + const ShaderConstant &member = cblock.variables[1]; + INFO("UBO member: " << member.name.c_str()); + + CHECK(member.type.members.empty()); + CHECK(member.type.descriptor.type == VarType::Float); + CHECK(member.type.descriptor.rows == 2); + CHECK(member.type.descriptor.columns == 3); + CHECK(member.type.descriptor.elements == 3); + CHECK(member.type.descriptor.rowMajorStorage == false); + CHECK(member.type.descriptor.name == "mat3x2"); + } + } + } + } + + REQUIRE(refl.samplers.empty()); + REQUIRE(refl.interfaces.empty()); + + REQUIRE_ARRAY_SIZE(mapping.inputAttributes.size(), 16); + for(size_t i = 0; i < mapping.inputAttributes.size(); i++) + { + CHECK(mapping.inputAttributes[i] == -1); + } + + REQUIRE_ARRAY_SIZE(mapping.readOnlyResources.size(), 2); + { + // tex2d + CHECK(mapping.readOnlyResources[0].bindset == 0); + CHECK(mapping.readOnlyResources[0].bind == 3); + CHECK(mapping.readOnlyResources[0].arraySize == 1); + CHECK(mapping.readOnlyResources[0].used); + + // tex3d + CHECK(mapping.readOnlyResources[1].bindset == 0); + CHECK(mapping.readOnlyResources[1].bind == 5); + CHECK(mapping.readOnlyResources[1].arraySize == 1); + CHECK(mapping.readOnlyResources[1].used); + } + + REQUIRE_ARRAY_SIZE(mapping.readWriteResources.size(), 2); + { + // atom + CHECK(mapping.readWriteResources[0].bindset == 0); + CHECK(mapping.readWriteResources[0].bind == 0); + CHECK(mapping.readWriteResources[0].arraySize == 1); + CHECK(mapping.readWriteResources[0].used); + + // ssbo + CHECK(mapping.readWriteResources[1].bindset == 0); + CHECK(mapping.readWriteResources[1].bind == 2); + CHECK(mapping.readWriteResources[1].arraySize == 1); + CHECK(mapping.readWriteResources[1].used); + } + + REQUIRE_ARRAY_SIZE(mapping.constantBlocks.size(), 2); + { + // ubo + CHECK(mapping.constantBlocks[0].bindset == 0); + CHECK(mapping.constantBlocks[0].bind == 8); + CHECK(mapping.constantBlocks[0].arraySize == 1); + CHECK(mapping.constantBlocks[0].used); + + // $Globals + CHECK(mapping.constantBlocks[1].bindset == -1); + CHECK(mapping.constantBlocks[1].bind == -1); + CHECK(mapping.constantBlocks[1].arraySize == 1); + CHECK(mapping.constantBlocks[1].used); + } + + REQUIRE(mapping.samplers.empty()); + }; + + SECTION("vertex shader fixed function outputs") + { + std::string source = R"( +#version 150 core + +void main() { + gl_Position = vec4(0, 1, 0, 1); +} + +)"; + + ShaderReflection refl; + ShaderBindpointMapping mapping; + MakeOfflineShaderReflection(ShaderStage::Vertex, source, refl, mapping); + + REQUIRE_ARRAY_SIZE(refl.outputSignature.size(), 1); + { + CHECK(refl.outputSignature[0].varName == "gl_Position"); + { + const SigParameter &sig = refl.outputSignature[0]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Position); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 4); + CHECK(sig.regChannelMask == 0xf); + CHECK(sig.channelUsedMask == 0xf); + } + } + + std::string source2 = R"( +#version 150 core + +void main() { + gl_Position = vec4(0, 1, 0, 1); + gl_PointSize = 1.5f; +} + +)"; + + refl = ShaderReflection(); + mapping = ShaderBindpointMapping(); + MakeOfflineShaderReflection(ShaderStage::Vertex, source2, refl, mapping); + + REQUIRE_ARRAY_SIZE(refl.outputSignature.size(), 2); + { + CHECK(refl.outputSignature[0].varName == "gl_Position"); + { + const SigParameter &sig = refl.outputSignature[0]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Position); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 4); + CHECK(sig.regChannelMask == 0xf); + CHECK(sig.channelUsedMask == 0xf); + } + + CHECK(refl.outputSignature[1].varName == "gl_PointSize"); + { + const SigParameter &sig = refl.outputSignature[1]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::PointSize); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 1); + CHECK(sig.regChannelMask == 0x1); + CHECK(sig.channelUsedMask == 0x1); + } + } + }; + + SECTION("shader input/output blocks") + { + std::string source = R"( +#version 420 core + +layout(triangles) in; +layout(triangle_strip, max_vertices = 4) out; + +in gl_PerVertex +{ + vec4 gl_Position; +} gl_in[]; + +in block +{ + vec2 Texcoord; +} In[]; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +out block +{ + vec2 Texcoord; +} Out; + +void main() +{ + for(int i = 0; i < gl_in.length(); ++i) + { + gl_Position = gl_in[i].gl_Position; + Out.Texcoord = In[i].Texcoord; + EmitVertex(); + } + EndPrimitive(); +} + +)"; + + ShaderReflection refl; + ShaderBindpointMapping mapping; + MakeOfflineShaderReflection(ShaderStage::Geometry, source, refl, mapping); + + REQUIRE_ARRAY_SIZE(refl.inputSignature.size(), 2); + { + CHECK(refl.inputSignature[0].varName == "block.Texcoord"); + { + const SigParameter &sig = refl.inputSignature[0]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 2); + CHECK(sig.regChannelMask == 0x3); + CHECK(sig.channelUsedMask == 0x3); + } + + CHECK(refl.inputSignature[1].varName == "gl_PerVertex.gl_Position"); + { + const SigParameter &sig = refl.inputSignature[1]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Position); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 4); + CHECK(sig.regChannelMask == 0xf); + CHECK(sig.channelUsedMask == 0xf); + } + } + + REQUIRE_ARRAY_SIZE(refl.outputSignature.size(), 2); + { + CHECK(refl.outputSignature[0].varName == "block.Texcoord"); + { + const SigParameter &sig = refl.outputSignature[0]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 2); + CHECK(sig.regChannelMask == 0x3); + CHECK(sig.channelUsedMask == 0x3); + } + + CHECK(refl.outputSignature[1].varName == "gl_Position"); + { + const SigParameter &sig = refl.outputSignature[1]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Position); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 4); + CHECK(sig.regChannelMask == 0xf); + CHECK(sig.channelUsedMask == 0xf); + } + } + }; + + SECTION("matrix and array outputs") + { + std::string source = R"( +#version 150 core + +out vec3 outarr[3]; +out mat2 outmat; + +void main() +{ + gl_Position = vec4(0, 0, 0, 1); + outarr[0] = gl_Position.xyz; + outarr[1] = gl_Position.xyz; + outarr[2] = gl_Position.xyz; + outmat = mat2(0, 0, 0, 0); +} + +)"; + + ShaderReflection refl; + ShaderBindpointMapping mapping; + MakeOfflineShaderReflection(ShaderStage::Vertex, source, refl, mapping); + + REQUIRE_ARRAY_SIZE(refl.outputSignature.size(), 6); + { + CHECK(refl.outputSignature[0].varName == "outarr[0]"); + { + const SigParameter &sig = refl.outputSignature[0]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.arrayIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 3); + CHECK(sig.regChannelMask == 0x7); + CHECK(sig.channelUsedMask == 0x7); + } + + CHECK(refl.outputSignature[1].varName == "outarr[1]"); + { + const SigParameter &sig = refl.outputSignature[1]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 1); + CHECK(sig.arrayIndex == 1); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 3); + CHECK(sig.regChannelMask == 0x7); + CHECK(sig.channelUsedMask == 0x7); + } + + CHECK(refl.outputSignature[2].varName == "outarr[2]"); + { + const SigParameter &sig = refl.outputSignature[2]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 2); + CHECK(sig.arrayIndex == 2); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 3); + CHECK(sig.regChannelMask == 0x7); + CHECK(sig.channelUsedMask == 0x7); + } + + CHECK(refl.outputSignature[3].varName == "outmat:row0"); + { + const SigParameter &sig = refl.outputSignature[3]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 3); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 2); + CHECK(sig.regChannelMask == 0x3); + CHECK(sig.channelUsedMask == 0x3); + } + + CHECK(refl.outputSignature[4].varName == "outmat:row1"); + { + const SigParameter &sig = refl.outputSignature[4]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 4); + CHECK(sig.systemValue == ShaderBuiltin::Undefined); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 2); + CHECK(sig.regChannelMask == 0x3); + CHECK(sig.channelUsedMask == 0x3); + } + + CHECK(refl.outputSignature[5].varName == "gl_Position"); + { + const SigParameter &sig = refl.outputSignature[5]; + INFO("signature element: " << sig.varName.c_str()); + + CHECK(sig.regIndex == 0); + CHECK(sig.systemValue == ShaderBuiltin::Position); + CHECK(sig.compType == CompType::Float); + CHECK(sig.compCount == 4); + CHECK(sig.regChannelMask == 0xf); + CHECK(sig.channelUsedMask == 0xf); + } + } + }; + + SECTION("shader stage references") + { + std::string vssource = R"( +#version 450 core + +uniform float unused_uniform; // declared in both, used in neither +uniform float vsonly_uniform; // declared and used only in VS +uniform float shared_uniform; // declared and used in both +uniform float vsshared_uniform; // declared in both, used only in VS +uniform float fsshared_uniform; // declared in both, used only in FS + +out vec4 vsout; + +void main() { + vsout = vec4(vsonly_uniform, shared_uniform, vsshared_uniform, 1); +} + +)"; + + std::string fssource = R"( +#version 450 core + +uniform float unused_uniform; // declared in both, used in neither +uniform float shared_uniform; // declared and used in both +uniform float vsshared_uniform; // declared in both, used only in VS +uniform float fsshared_uniform; // declared in both, used only in FS +uniform float fsonly_uniform; // declared and used only in FS + +in vec4 vsout; + +out vec4 col; + +void main() { + vec4 tmp = vsout; + tmp.w = fsonly_uniform * shared_uniform + fsshared_uniform; + col = tmp; +} + +)"; + + InitSPIRVCompiler(); + RenderDoc::Inst().RegisterShutdownFunction(&ShutdownSPIRVCompiler); + + // as a hack, create a local 'driver' and just populate m_Programs with what we want. + GLDummyPlatform dummy; + WrappedOpenGL driver(dummy); + + GL.DriverForEmulation(&driver); + + RDCEraseEl(HasExt); + GL = GLDispatchTable(); + GL.EmulateRequiredExtensions(); + + glslang::TProgram *prog = LinkProgramForReflection( + {CompileShaderForReflection(SPIRVShaderStage::Vertex, {vssource}), + CompileShaderForReflection(SPIRVShaderStage::Fragment, {fssource})}); + + REQUIRE(prog); + + // the lookup won't get a valid Id, so set the program to the ResourceId() + driver.m_Programs[ResourceId()].glslangProgram = prog; + + GLint numUniforms = 0; + GL.glGetProgramInterfaceiv(0, eGL_UNIFORM, eGL_ACTIVE_RESOURCES, &numUniforms); + + for(GLint i = 0; i < numUniforms; i++) + { + GLenum props[2] = {eGL_REFERENCED_BY_VERTEX_SHADER, eGL_REFERENCED_BY_FRAGMENT_SHADER}; + GLint values[2] = {}; + GL.glGetProgramResourceiv(0, eGL_UNIFORM, i, 2, props, 2, NULL, values); + + GLchar name[1024] = {}; + GL.glGetProgramResourceName(0, eGL_UNIFORM, i, 1024, NULL, name); + + if(!strcmp(name, "unused_uniform")) + { + FAIL_CHECK("Didn't expect to see unused_uniform"); + } + else if(!strcmp(name, "vsonly_uniform")) + { + CHECK(values[0] == 1); + CHECK(values[1] == 0); + } + else if(!strcmp(name, "shared_uniform")) + { + CHECK(values[0] == 1); + CHECK(values[1] == 1); + } + else if(!strcmp(name, "vsshared_uniform")) + { + CHECK(values[0] == 1); + CHECK(values[1] == 0); + } + else if(!strcmp(name, "fsshared_uniform")) + { + CHECK(values[0] == 0); + CHECK(values[1] == 1); + } + else if(!strcmp(name, "fsonly_uniform")) + { + CHECK(values[0] == 0); + CHECK(values[1] == 1); + } + else + { + INFO("uniform name: " << name); + FAIL_CHECK("Unexpected uniform"); + } + } + + RDCEraseEl(HasExt); + GL = GLDispatchTable(); + GL.DriverForEmulation(NULL); + }; +} + +#endif \ No newline at end of file diff --git a/renderdoc/driver/gl/wrappers/gl_shader_funcs.cpp b/renderdoc/driver/gl/wrappers/gl_shader_funcs.cpp index fdd975929..035303918 100644 --- a/renderdoc/driver/gl/wrappers/gl_shader_funcs.cpp +++ b/renderdoc/driver/gl/wrappers/gl_shader_funcs.cpp @@ -120,9 +120,9 @@ void WrappedOpenGL::ShaderData::ProcessSPIRVCompilation(WrappedOpenGL &drv, Reso void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceId id, GLuint realShader) { - bool pointSizeUsed = false, clipDistanceUsed = false; + FixedFunctionVertexOutputs outputUsage = {}; if(type == eGL_VERTEX_SHADER) - CheckVertexOutputUses(sources, pointSizeUsed, clipDistanceUsed); + CheckVertexOutputUses(sources, outputUsage); entryPoint = "main"; @@ -224,9 +224,6 @@ void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceI if(version == 0) version = 100; - reflection.encoding = ShaderEncoding::GLSL; - reflection.rawBytes.assign((byte *)concatenated.c_str(), concatenated.size()); - GLuint sepProg = prog; GLint status = 0; @@ -235,12 +232,17 @@ void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceI else drv.glGetShaderiv(realShader, eGL_COMPILE_STATUS, &status); - if(IsCaptureMode(drv.GetState())) - { + // if we don't have program_interface_query, need to compile the shader with glslang to be able + // to reflect with. This is needed on capture or replay + if(!HasExt[ARB_program_interface_query] && status == 1) glslangShader = CompileShaderForReflection(SPIRVShaderStage(ShaderIdx(type)), sources); - } - else + + if(IsReplayMode(drv.GetState()) && !drv.IsInternalShader()) { + // no shaders made under this point should be reflected themselves, they're only used for + // reflection + drv.PushInternalShader(); + if(sepProg == 0 && status == 1) sepProg = MakeSeparableShaderProgram(drv, type, sources, NULL); @@ -257,7 +259,7 @@ void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceI else { prog = sepProg; - MakeShaderReflection(type, sepProg, reflection, pointSizeUsed, clipDistanceUsed); + MakeShaderReflection(type, sepProg, reflection, outputUsage); vector spirvwords; @@ -275,12 +277,17 @@ void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceI reflection.stage = MakeShaderStage(type); + reflection.encoding = ShaderEncoding::GLSL; + reflection.rawBytes.assign((byte *)concatenated.c_str(), concatenated.size()); + reflection.debugInfo.encoding = ShaderEncoding::GLSL; reflection.debugInfo.files.resize(1); reflection.debugInfo.files[0].filename = "main.glsl"; reflection.debugInfo.files[0].contents = concatenated; } + + drv.PopInternalShader(); } } @@ -395,7 +402,7 @@ bool WrappedOpenGL::Serialise_glShaderSource(SerialiserType &ser, GLuint shaderH // so people who do that should be moderately ashamed. if(m_Shaders[liveId].prog) { - GL.glDeleteProgram(m_Shaders[liveId].prog); + glDeleteProgram(m_Shaders[liveId].prog); m_Shaders[liveId].prog = 0; m_Shaders[liveId].spirv = SPVModule(); m_Shaders[liveId].reflection = ShaderReflection(); @@ -855,6 +862,28 @@ bool WrappedOpenGL::Serialise_glLinkProgram(SerialiserType &ser, GLuint programH } } + if(!HasExt[ARB_program_interface_query]) + { + std::vector glslangShaders; + + for(ResourceId id : progDetails.stageShaders) + { + if(id == ResourceId()) + continue; + + glslang::TShader *s = m_Shaders[id].glslangShader; + if(s == NULL) + { + RDCERR("Shader attached with no compiled glslang reflection shader!"); + continue; + } + + glslangShaders.push_back(m_Shaders[id].glslangShader); + } + + progDetails.glslangProgram = LinkProgramForReflection(glslangShaders); + } + GL.glLinkProgram(program.name); AddResourceInitChunk(program); @@ -898,12 +927,15 @@ void WrappedOpenGL::glLinkProgram(GLuint program) } } - if(IsCaptureMode(m_State) && !HasExt[ARB_program_interface_query]) + if(!HasExt[ARB_program_interface_query]) { std::vector glslangShaders; - for(ResourceId id : progDetails.shaders) + for(ResourceId id : progDetails.stageShaders) { + if(id == ResourceId()) + continue; + glslang::TShader *s = m_Shaders[id].glslangShader; if(s == NULL) { @@ -1988,4 +2020,4 @@ INSTANTIATE_FUNCTION_SERIALISED(void, glShaderBinary, GLsizei count, const GLuin GLenum binaryformat, const void *binary, GLsizei length); INSTANTIATE_FUNCTION_SERIALISED(void, glSpecializeShader, GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, - const GLuint *pConstantValue); \ No newline at end of file + const GLuint *pConstantValue); diff --git a/renderdoc/driver/shaders/spirv/spirv_common.cpp b/renderdoc/driver/shaders/spirv/spirv_common.cpp index b26b85464..e5cfdb19a 100644 --- a/renderdoc/driver/shaders/spirv/spirv_common.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_common.cpp @@ -30,6 +30,7 @@ #undef min #undef max +#include "3rdparty/glslang/glslang/Include/Types.h" #include "3rdparty/glslang/glslang/Public/ShaderLang.h" static bool inited = false; @@ -100,20 +101,14 @@ void glslangGetProgramInterfaceiv(glslang::TProgram *program, ReflectionInterfac { switch(programInterface) { - case ReflectionInterface::Input: *params = program->getNumLiveAttributes(); break; - case ReflectionInterface::Output: - // unsupported - *params = 0; - break; - case ReflectionInterface::Uniform: *params = program->getNumLiveUniformVariables(); break; - case ReflectionInterface::UniformBlock: *params = program->getNumLiveUniformBlocks(); break; - case ReflectionInterface::ShaderStorageBlock: - // unsupported - *params = 0; - break; + case ReflectionInterface::Input: *params = program->getNumPipeInputs(); break; + case ReflectionInterface::Output: *params = program->getNumPipeOutputs(); break; + case ReflectionInterface::Uniform: *params = program->getNumUniformVariables(); break; + case ReflectionInterface::UniformBlock: *params = program->getNumUniformBlocks(); break; + case ReflectionInterface::BufferVariable: *params = program->getNumBufferVariables(); break; + case ReflectionInterface::ShaderStorageBlock: *params = program->getNumBufferBlocks(); break; case ReflectionInterface::AtomicCounterBuffer: - // unsupported - *params = 0; + *params = program->getNumAtomicCounters(); break; } } @@ -127,13 +122,6 @@ void glslangGetProgramResourceiv(glslang::TProgram *program, ReflectionInterface uint32_t index, const std::vector &props, int32_t bufSize, int32_t *length, int32_t *params) { - if(programInterface == ReflectionInterface::Output || - programInterface == ReflectionInterface::ShaderStorageBlock || - programInterface == ReflectionInterface::AtomicCounterBuffer) - { - RDCWARN("unsupported program interface"); - } - // all of our properties are single-element values, so we just loop up to buffer size or number of // properties, whichever comes first. for(size_t i = 0; i < RDCMIN((size_t)bufSize, props.size()); i++) @@ -145,99 +133,324 @@ void glslangGetProgramResourceiv(glslang::TProgram *program, ReflectionInterface params[i] = 0; break; case ReflectionProperty::BufferBinding: - RDCASSERT(programInterface == ReflectionInterface::UniformBlock); - params[i] = program->getUniformBlockBinding(index); - break; - case ReflectionProperty::TopLevelArrayStride: - // TODO glslang doesn't give us this - params[i] = 16; - break; - case ReflectionProperty::BlockIndex: - RDCASSERT(programInterface == ReflectionInterface::Uniform); - params[i] = program->getUniformBlockIndex(index); - break; - case ReflectionProperty::ArraySize: - if(programInterface == ReflectionInterface::Uniform) - params[i] = program->getUniformArraySize(index); - else if(programInterface == ReflectionInterface::Input) - // TODO assuming all inputs are non-arrayed - params[i] = 1; + { + if(programInterface == ReflectionInterface::UniformBlock) + params[i] = program->getUniformBlock(index).getBinding(); + else if(programInterface == ReflectionInterface::ShaderStorageBlock) + params[i] = program->getBufferBlock(index).getBinding(); else + RDCERR("Unsupported interface for BufferBinding query"); + break; + } + case ReflectionProperty::BlockIndex: + { + if(programInterface == ReflectionInterface::Uniform) + params[i] = program->getUniform(index).index; + else if(programInterface == ReflectionInterface::BufferVariable) + params[i] = program->getBufferVariable(index).index; + else + RDCERR("Unsupported interface for BlockIndex query"); + break; + } + case ReflectionProperty::ArraySize: + { + if(programInterface == ReflectionInterface::Uniform) + { + params[i] = program->getUniform(index).size; + } + else if(programInterface == ReflectionInterface::BufferVariable) + { + params[i] = program->getBufferVariable(index).size; + } + else if(programInterface == ReflectionInterface::Input) + { + const glslang::TType *type = program->getPipeInput(index).getType(); + if(type->isArray()) + params[i] = type->getOuterArraySize(); + else + params[i] = 1; + } + else if(programInterface == ReflectionInterface::Output) + { + const glslang::TType *type = program->getPipeOutput(index).getType(); + if(type->isArray()) + params[i] = type->getOuterArraySize(); + else + params[i] = 1; + } + else + { RDCERR("Unsupported interface for ArraySize query"); + } break; + } case ReflectionProperty::IsRowMajor: - // TODO glslang doesn't expose this, assume column major. - params[i] = 0; + { + const glslang::TType *ttype = NULL; + + if(programInterface == ReflectionInterface::Uniform) + ttype = program->getUniform(index).getType(); + else if(programInterface == ReflectionInterface::BufferVariable) + ttype = program->getBufferVariable(index).getType(); + else + RDCERR("Unsupported interface for RowMajor query"); + + if(ttype) + params[i] = (ttype->getQualifier().layoutMatrix == glslang::ElmRowMajor); + else + params[i] = 0; break; + } + case ReflectionProperty::MatrixStride: + { + // From documentation of std140: + // + // 5. "If the member is a column-major matrix with C columns and R rows, the matrix is + // stored identically to an array of C column vectors with R components each, according to + // rule (4)." + // 7. "If the member is a row-major matrix with C columns and R rows, the matrix is stored + // identically to an array of R row vectors with C components each, according to rule (4)." + // + // So in std140 the matrix stride is always at least 16-bytes unless the matrix is doubles. + // In std430, because the rule (4) array alignment is relaxed, it can be less. + if(programInterface == ReflectionInterface::Uniform) + { + params[i] = 16; + } + else if(programInterface == ReflectionInterface::BufferVariable) + { + const glslang::TType *ttype = program->getBufferVariable(index).getType(); + + if(ttype->getQualifier().layoutMatrix == glslang::ElmRowMajor) + params[i] = ttype->getMatrixCols() * sizeof(float); + else + params[i] = ttype->getMatrixRows() * sizeof(float); + } + else + { + RDCERR("Unsupported interface for RowMajor query"); + } + + break; + } case ReflectionProperty::NumActiveVariables: - // TODO glslang doesn't give us this - params[i] = 1; + { + if(programInterface == ReflectionInterface::UniformBlock) + params[i] = program->getUniformBlock(index).numMembers; + else if(programInterface == ReflectionInterface::ShaderStorageBlock) + params[i] = program->getBufferBlock(index).numMembers; + else + RDCERR("Unsupported interface for NumActiveVariables query"); break; + } case ReflectionProperty::BufferDataSize: RDCASSERT(programInterface == ReflectionInterface::UniformBlock); - params[i] = program->getUniformBlockSize(index); + params[i] = program->getUniformBlock(index).size; break; case ReflectionProperty::NameLength: + { // The name length includes a terminating null character. if(programInterface == ReflectionInterface::Uniform) - params[i] = (int32_t)strlen(program->getUniformName(index)) + 1; + params[i] = (int32_t)program->getUniform(index).name.size() + 1; + else if(programInterface == ReflectionInterface::BufferVariable) + params[i] = (int32_t)program->getBufferVariable(index).name.size() + 1; else if(programInterface == ReflectionInterface::UniformBlock) - params[i] = (int32_t)strlen(program->getUniformBlockName(index)) + 1; + params[i] = (int32_t)program->getUniformBlock(index).name.size() + 1; else if(programInterface == ReflectionInterface::Input) - params[i] = (int32_t)strlen(program->getAttributeName(index)) + 1; + params[i] = (int32_t)program->getPipeInput(index).name.size() + 1; + else if(programInterface == ReflectionInterface::Output) + params[i] = (int32_t)program->getPipeOutput(index).name.size() + 1; + else if(programInterface == ReflectionInterface::AtomicCounterBuffer) + params[i] = (int32_t)program->getAtomicCounter(index).name.size() + 1; + else if(programInterface == ReflectionInterface::ShaderStorageBlock) + params[i] = (int32_t)program->getBufferBlock(index).name.size() + 1; else - RDCERR("Unsupported interface for NameLEngth query"); + RDCERR("Unsupported interface for NameLength query"); break; + } case ReflectionProperty::Type: + { if(programInterface == ReflectionInterface::Uniform) - params[i] = program->getUniformType(index); + params[i] = program->getUniform(index).glDefineType; + else if(programInterface == ReflectionInterface::BufferVariable) + params[i] = program->getBufferVariable(index).glDefineType; else if(programInterface == ReflectionInterface::Input) - params[i] = program->getAttributeType(index); + params[i] = program->getPipeInput(index).glDefineType; + else if(programInterface == ReflectionInterface::Output) + params[i] = program->getPipeOutput(index).glDefineType; else RDCERR("Unsupported interface for Type query"); + + if(params[i] == 0) + params[i] = 0x1406; // GL_FLOAT + break; + } case ReflectionProperty::LocationComponent: - // TODO glslang doesn't give us this information - params[i] = 0; + { + if(programInterface == ReflectionInterface::Uniform) + params[i] = program->getUniform(index).getType()->getQualifier().layoutComponent; + else if(programInterface == ReflectionInterface::BufferVariable) + params[i] = program->getBufferVariable(index).getType()->getQualifier().layoutComponent; + else if(programInterface == ReflectionInterface::Input) + params[i] = program->getPipeInput(index).getType()->getQualifier().layoutComponent; + else if(programInterface == ReflectionInterface::Output) + params[i] = program->getPipeOutput(index).getType()->getQualifier().layoutComponent; + else + RDCERR("Unsupported interface for LocationComponent query"); + + if(params[i] == glslang::TQualifier::layoutComponentEnd) + params[i] = 0; break; + } case ReflectionProperty::ReferencedByVertexShader: case ReflectionProperty::ReferencedByTessControlShader: case ReflectionProperty::ReferencedByTessEvaluationShader: case ReflectionProperty::ReferencedByGeometryShader: case ReflectionProperty::ReferencedByFragmentShader: case ReflectionProperty::ReferencedByComputeShader: - // TODO glslang doesn't give us this information - params[i] = 1; - break; - case ReflectionProperty::AtomicCounterBufferIndex: - RDCERR("Atomic counters not supported"); - break; - case ReflectionProperty::Offset: - RDCASSERT(programInterface == ReflectionInterface::Uniform); - params[i] = program->getUniformBufferOffset(index); - break; - case ReflectionProperty::MatrixStride: - RDCASSERT(programInterface == ReflectionInterface::Uniform); - // TODO glslang doesn't give us this information - params[i] = 64; - break; - case ReflectionProperty::ArrayStride: - RDCASSERT(programInterface == ReflectionInterface::Uniform); - // TODO glslang doesn't give us this information - params[i] = 64; - break; - case ReflectionProperty::Location: - // have to query the actual implementation, which is handled elsewhere. We return either -1 - // for uniforms that don't have a location (i.e. are in a block) or 0 for bare uniforms + { + EShLanguageMask mask = {}; + switch(props[i]) + { + case ReflectionProperty::ReferencedByVertexShader: mask = EShLangVertexMask; break; + case ReflectionProperty::ReferencedByTessControlShader: + mask = EShLangTessControlMask; + break; + case ReflectionProperty::ReferencedByTessEvaluationShader: + mask = EShLangTessEvaluationMask; + break; + case ReflectionProperty::ReferencedByGeometryShader: mask = EShLangGeometryMask; break; + case ReflectionProperty::ReferencedByFragmentShader: mask = EShLangFragmentMask; break; + case ReflectionProperty::ReferencedByComputeShader: mask = EShLangComputeMask; break; + default: break; + } + if(programInterface == ReflectionInterface::Uniform) - params[i] = program->getUniformBlockIndex(index) >= 0 ? -1 : 0; + params[i] = (program->getUniform(index).stages & mask) != 0; + else if(programInterface == ReflectionInterface::UniformBlock) + params[i] = (program->getUniformBlock(index).stages & mask) != 0; + else if(programInterface == ReflectionInterface::BufferVariable) + params[i] = (program->getBufferVariable(index).stages & mask) != 0; + else if(programInterface == ReflectionInterface::ShaderStorageBlock) + params[i] = (program->getBufferBlock(index).stages & mask) != 0; else if(programInterface == ReflectionInterface::Input) - params[i] = index; + params[i] = (program->getPipeInput(index).stages & mask) != 0; + else if(programInterface == ReflectionInterface::Output) + params[i] = (program->getPipeOutput(index).stages & mask) != 0; + else if(programInterface == ReflectionInterface::AtomicCounterBuffer) + params[i] = (program->getAtomicCounter(index).stages & mask) != 0; + else + RDCERR("Unexpected interface being queried for referenced-by"); + break; + } + case ReflectionProperty::Internal_Binding: + { + if(programInterface == ReflectionInterface::UniformBlock) + { + params[i] = program->getUniformBlock(index).getType()->getQualifier().layoutBinding; + break; + } + + // deliberate fall-through + } + case ReflectionProperty::AtomicCounterBufferIndex: + { + if(programInterface == ReflectionInterface::Uniform) + params[i] = program->getUniform(index).getType()->getQualifier().layoutBinding; + else if(programInterface == ReflectionInterface::AtomicCounterBuffer) + params[i] = program->getAtomicCounter(index).getType()->getQualifier().layoutBinding; + else + RDCERR("Unexpected interface being queried for AtomicCounterBufferIndex"); + break; + } + case ReflectionProperty::Offset: + { + if(programInterface == ReflectionInterface::Uniform) + params[i] = program->getUniform(index).offset; + else if(programInterface == ReflectionInterface::BufferVariable) + params[i] = program->getBufferVariable(index).offset; + else + RDCERR("Unsupported interface for Offset query"); + break; + } + case ReflectionProperty::TopLevelArrayStride: + { + if(programInterface == ReflectionInterface::Uniform) + params[i] = program->getUniform(index).topLevelArrayStride; + else if(programInterface == ReflectionInterface::BufferVariable) + params[i] = program->getBufferVariable(index).topLevelArrayStride; + else + RDCERR("Unsupported interface for ArrayStride query"); + break; + } + case ReflectionProperty::ArrayStride: + { + if(programInterface == ReflectionInterface::Uniform) + params[i] = program->getUniform(index).arrayStride; + else if(programInterface == ReflectionInterface::BufferVariable) + params[i] = program->getBufferVariable(index).arrayStride; + else + RDCERR("Unsupported interface for ArrayStride query"); + break; + } + case ReflectionProperty::Location: + { + // want to query the actual implementation for bare uniform locations, which is handled + // elsewhere. So we always return either -1 for uniforms that don't have a location (i.e. + // are in a block) or 0 for bare uniforms + if(programInterface == ReflectionInterface::Uniform) + { + params[i] = program->getUniform(index).index >= 0 ? -1 : 0; + } + // for program inputs/outputs for a vertex/fragment shader respectively, we want to do the + // same as above and always query when possible, however for fragment inputs e.g. we want to + // keep the locations that might be present in the shader. So we do the reverse - return -1 + // when it's a vertex input to force a query, and otherwise return the layout set. + else if(programInterface == ReflectionInterface::Input) + { + params[i] = program->getPipeInput(index).getType()->getQualifier().layoutLocation; + + if(params[i] == glslang::TQualifier::layoutLocationEnd) + params[i] = -1; + + if(program->getPipeInput(index).stages == EShLangVertexMask) + params[i] = -1; + } + else if(programInterface == ReflectionInterface::Output) + { + params[i] = program->getPipeOutput(index).getType()->getQualifier().layoutLocation; + + if(params[i] == glslang::TQualifier::layoutLocationEnd) + params[i] = -1; + + if(program->getPipeOutput(index).stages == EShLangFragmentMask) + params[i] = -1; + } + break; + } } } } +uint32_t glslangGetProgramResourceIndex(glslang::TProgram *program, const char *name) +{ + uint32_t idx = program->getReflectionIndex(name); + + // Additionally, if would exactly match the name string of an active + // resource if "[0]" were appended to , the index of the matched + // resource is returned. + if(idx == ~0U) + { + std::string arraysuffixed = name; + arraysuffixed += "[0]"; + idx = program->getReflectionIndex(arraysuffixed.c_str()); + } + + return idx; +} + const char *glslangGetProgramResourceName(glslang::TProgram *program, ReflectionInterface programInterface, uint32_t index) { @@ -245,17 +458,22 @@ const char *glslangGetProgramResourceName(glslang::TProgram *program, switch(programInterface) { - case ReflectionInterface::Input: fetchedName = program->getAttributeName(index); break; - case ReflectionInterface::Output: RDCWARN("Output attributes unsupported"); break; - case ReflectionInterface::Uniform: fetchedName = program->getUniformName(index); break; + case ReflectionInterface::Input: fetchedName = program->getPipeInput(index).name.c_str(); break; + case ReflectionInterface::Output: + fetchedName = program->getPipeOutput(index).name.c_str(); + break; + case ReflectionInterface::Uniform: fetchedName = program->getUniform(index).name.c_str(); break; case ReflectionInterface::UniformBlock: - fetchedName = program->getUniformBlockName(index); + fetchedName = program->getUniformBlock(index).name.c_str(); + break; + case ReflectionInterface::BufferVariable: + fetchedName = program->getBufferVariable(index).name.c_str(); break; case ReflectionInterface::ShaderStorageBlock: - RDCWARN("shader storage blocks unsupported"); + fetchedName = program->getBufferBlock(index).name.c_str(); break; case ReflectionInterface::AtomicCounterBuffer: - RDCWARN("atomic counter buffers unsupported"); + fetchedName = program->getAtomicCounter(index).name.c_str(); break; } diff --git a/renderdoc/driver/shaders/spirv/spirv_common.h b/renderdoc/driver/shaders/spirv/spirv_common.h index fed8e5c7d..50e9c518e 100644 --- a/renderdoc/driver/shaders/spirv/spirv_common.h +++ b/renderdoc/driver/shaders/spirv/spirv_common.h @@ -187,6 +187,7 @@ enum class ReflectionInterface UniformBlock, ShaderStorageBlock, AtomicCounterBuffer, + BufferVariable, }; enum class ReflectionProperty @@ -208,10 +209,11 @@ enum class ReflectionProperty ReferencedByGeometryShader, ReferencedByFragmentShader, ReferencedByComputeShader, + Internal_Binding, AtomicCounterBufferIndex, Offset, - MatrixStride, ArrayStride, + MatrixStride, Location, }; @@ -221,6 +223,7 @@ void glslangGetProgramInterfaceiv(glslang::TProgram *program, ReflectionInterfac void glslangGetProgramResourceiv(glslang::TProgram *program, ReflectionInterface programInterface, uint32_t index, const std::vector &props, int32_t bufSize, int32_t *length, int32_t *params); +uint32_t glslangGetProgramResourceIndex(glslang::TProgram *program, const char *name); const char *glslangGetProgramResourceName(glslang::TProgram *program, ReflectionInterface programInterface, uint32_t index); \ No newline at end of file diff --git a/renderdoc/driver/shaders/spirv/spirv_compile.cpp b/renderdoc/driver/shaders/spirv/spirv_compile.cpp index 6cfd82f00..a0310255b 100644 --- a/renderdoc/driver/shaders/spirv/spirv_compile.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_compile.cpp @@ -264,7 +264,9 @@ glslang::TProgram *LinkProgramForReflection(const std::vectorlink(EShMsgDefault)) { - program->buildReflection(); + program->buildReflection(EShReflectionStrictArraySuffix | EShReflectionBasicArraySuffix | + EShReflectionIntermediateIO | EShReflectionSeparateBuffers | + EShReflectionAllBlockVariables | EShReflectionUnwrapIOBlocks); allocatedPrograms.push_back(program); return program; }