diff --git a/renderdoc/api/replay/shader_types.h b/renderdoc/api/replay/shader_types.h index 9e60fcd63..432fbabba 100644 --- a/renderdoc/api/replay/shader_types.h +++ b/renderdoc/api/replay/shader_types.h @@ -1007,7 +1007,7 @@ struct ShaderReflection ShaderDebugInfo debugInfo; DOCUMENT("The :class:`ShaderEncoding` of this shader. See :data:`rawBytes`."); - ShaderEncoding encoding; + ShaderEncoding encoding = ShaderEncoding::Unknown; DOCUMENT(R"(A raw ``bytes`` dump of the original shader, encoded in the form denoted by :data:`encoding`. diff --git a/renderdoc/driver/gl/gl_common.h b/renderdoc/driver/gl/gl_common.h index 01c1f6619..48a074d29 100644 --- a/renderdoc/driver/gl/gl_common.h +++ b/renderdoc/driver/gl/gl_common.h @@ -839,14 +839,23 @@ bool ValidateFunctionPointers(); struct ShaderReflection; -void CopyProgramUniforms(GLuint progSrc, GLuint progDst); +struct PerStageReflections +{ + const ShaderReflection *refls[6] = {}; + const ShaderBindpointMapping *mappings[6] = {}; +}; + +void CopyProgramUniforms(const PerStageReflections &srcStages, GLuint progSrc, + const PerStageReflections &dstStages, GLuint progDst); template -void SerialiseProgramUniforms(SerialiserType &ser, CaptureState state, GLuint prog, +void SerialiseProgramUniforms(SerialiserType &ser, CaptureState state, + const PerStageReflections &stages, GLuint prog, std::map *locTranslate); -void CopyProgramAttribBindings(GLuint progsrc, GLuint progdst, ShaderReflection *refl); -void CopyProgramFragDataBindings(GLuint progsrc, GLuint progdst, ShaderReflection *refl); +bool CopyProgramAttribBindings(GLuint progsrc, GLuint progdst, ShaderReflection *refl); +bool CopyProgramFragDataBindings(GLuint progsrc, GLuint progdst, ShaderReflection *refl); template -void SerialiseProgramBindings(SerialiserType &ser, CaptureState state, GLuint prog); +bool SerialiseProgramBindings(SerialiserType &ser, CaptureState state, + const PerStageReflections &stages, GLuint prog); struct DrawElementsIndirectCommand { diff --git a/renderdoc/driver/gl/gl_driver.cpp b/renderdoc/driver/gl/gl_driver.cpp index aa3d7ee94..f68ea89a7 100644 --- a/renderdoc/driver/gl/gl_driver.cpp +++ b/renderdoc/driver/gl/gl_driver.cpp @@ -1443,6 +1443,9 @@ void WrappedOpenGL::ReplaceResource(ResourceId from, ResourceId to) ResourceId progsrcid = it->first; ProgramData &progdata = it->second; + PerStageReflections stages; + FillReflectionArray(it->first, stages); + // see if the shader is used for(int i = 0; i < 6; i++) { @@ -1505,8 +1508,11 @@ void WrappedOpenGL::ReplaceResource(ResourceId from, ResourceId to) } else { + PerStageReflections dstStages; + FillReflectionArray(progdstid, dstStages); + // copy uniforms - CopyProgramUniforms(progsrc, progdst); + CopyProgramUniforms(stages, progsrc, dstStages, progdst); ResourceId origsrcid = GetResourceManager()->GetOriginalID(progsrcid); diff --git a/renderdoc/driver/gl/gl_driver.h b/renderdoc/driver/gl/gl_driver.h index 9cdf83e96..cba962269 100644 --- a/renderdoc/driver/gl/gl_driver.h +++ b/renderdoc/driver/gl/gl_driver.h @@ -621,6 +621,7 @@ public: // used for if the application actually uploaded SPIR-V std::vector spirvWords; + SPIRVPatchData patchData; // the parameters passed to glSpecializeShader std::string entryPoint; @@ -680,6 +681,25 @@ public: std::map m_Programs; std::map m_Pipelines; + void FillReflectionArray(ResourceId program, PerStageReflections &stages) + { + ProgramData &progdata = m_Programs[program]; + for(size_t i = 0; i < ARRAY_COUNT(progdata.stageShaders); i++) + { + ResourceId shadId = progdata.stageShaders[i]; + if(shadId != ResourceId()) + { + stages.refls[i] = &m_Shaders[shadId].reflection; + stages.mappings[i] = &m_Shaders[shadId].mapping; + } + } + } + + void FillReflectionArray(GLResource program, PerStageReflections &stages) + { + FillReflectionArray(GetResourceManager()->GetID(program), stages); + } + struct TextureData { TextureData() diff --git a/renderdoc/driver/gl/gl_initstate.cpp b/renderdoc/driver/gl/gl_initstate.cpp index 0c5ab6c60..211a965fb 100644 --- a/renderdoc/driver/gl/gl_initstate.cpp +++ b/renderdoc/driver/gl/gl_initstate.cpp @@ -238,8 +238,11 @@ void GLResourceManager::ContextPrepare_InitialState(GLResource res) SERIALISE_ELEMENT(id).TypedAs("GLResource"_lit); SERIALISE_ELEMENT(res.Namespace); - SerialiseProgramBindings(ser, CaptureState::ActiveCapturing, res.name); - SerialiseProgramUniforms(ser, CaptureState::ActiveCapturing, res.name, NULL); + PerStageReflections stages; + m_Driver->FillReflectionArray(id, stages); + + SerialiseProgramBindings(ser, CaptureState::ActiveCapturing, stages, res.name); + SerialiseProgramUniforms(ser, CaptureState::ActiveCapturing, stages, res.name, NULL); SetInitialChunk(id, scope.Get()); return; @@ -1074,8 +1077,11 @@ uint64_t GLResourceManager::GetSize_InitialState(ResourceId resid, const GLIniti SERIALISE_ELEMENT(resid).TypedAs("GLResource"_lit); SERIALISE_ELEMENT(res.Namespace); - SerialiseProgramBindings(ser, CaptureState::ActiveCapturing, res.name); - SerialiseProgramUniforms(ser, CaptureState::ActiveCapturing, res.name, NULL); + PerStageReflections stages; + m_Driver->FillReflectionArray(GetID(res), stages); + + SerialiseProgramBindings(ser, CaptureState::ActiveCapturing, stages, res.name); + SerialiseProgramUniforms(ser, CaptureState::ActiveCapturing, stages, res.name, NULL); return ser.GetWriter()->GetOffset() + 256; } @@ -1234,10 +1240,16 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId i GLuint bindingsProgram = 0, uniformsProgram = 0; std::map *translationTable = NULL; + PerStageReflections stages; + + bool IsProgramSPIRV = false; + if(IsReplayingAndReading()) { WrappedOpenGL::ProgramData &details = m_Driver->m_Programs[GetLiveID(id)]; + m_Driver->FillReflectionArray(GetLiveID(id), stages); + GLuint initProg = drv.glCreateProgram(); uint32_t numShaders = 0; @@ -1252,6 +1264,8 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId i const auto &shadDetails = m_Driver->m_Shaders[details.stageShaders[i]]; + IsProgramSPIRV |= shadDetails.reflection.encoding == ShaderEncoding::SPIRV; + GLuint shad = drv.glCreateShader(shadDetails.type); if(shadDetails.type == eGL_VERTEX_SHADER) @@ -1316,8 +1330,10 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId i vertexOutputsPtr.resize(vertexOutputs.size()); for(size_t i = 0; i < vertexOutputs.size(); i++) vertexOutputsPtr[i] = vertexOutputs[i].c_str(); - drv.glTransformFeedbackVaryings(initProg, (GLsizei)vertexOutputsPtr.size(), - &vertexOutputsPtr[0], eGL_INTERLEAVED_ATTRIBS); + + if(!IsProgramSPIRV) + drv.glTransformFeedbackVaryings(initProg, (GLsizei)vertexOutputsPtr.size(), + &vertexOutputsPtr[0], eGL_INTERLEAVED_ATTRIBS); drv.glLinkProgram(initProg); GLint status = 0; @@ -1325,7 +1341,7 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId i // 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) + if(status == 0 && !IsProgramSPIRV) { drv.glTransformFeedbackVaryings(initProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS); drv.glLinkProgram(initProg); @@ -1369,6 +1385,10 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId i translationTable = &details.locationTranslate; } + else + { + m_Driver->FillReflectionArray(id, stages); + } if(ser.IsWriting()) { @@ -1378,20 +1398,22 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId i bindingsProgram = uniformsProgram = GetCurrentResource(id).name; } - SerialiseProgramBindings(ser, m_State, bindingsProgram); + bool changedBindings = SerialiseProgramBindings(ser, m_State, stages, bindingsProgram); // re-link the program to set the new attrib bindings - if(IsReplayingAndReading() && !ser.IsErrored()) + if(IsReplayingAndReading() && !ser.IsErrored() && changedBindings) GL.glLinkProgram(bindingsProgram); - SerialiseProgramUniforms(ser, m_State, uniformsProgram, translationTable); + SerialiseProgramUniforms(ser, m_State, stages, uniformsProgram, translationTable); SERIALISE_CHECK_READ_ERRORS(); if(IsReplayingAndReading()) { // see above for why we're copying this back - CopyProgramUniforms(uniformsProgram, bindingsProgram); + // we can pass in the same stages array, it's the same program essentially (reflection is + // identical) + CopyProgramUniforms(stages, uniformsProgram, stages, bindingsProgram); SetInitialContents(id, GLInitialContents(ProgramRes(m_Driver->GetCtx(), bindingsProgram), 0)); } @@ -2125,20 +2147,27 @@ void GLResourceManager::Apply_InitialState(GLResource live, const GLInitialConte const WrappedOpenGL::ProgramData &prog = m_Driver->m_Programs[Id]; + bool changedBindings = false; + if(prog.stageShaders[0] != ResourceId()) - CopyProgramAttribBindings(initial.resource.name, live.name, - &m_Driver->m_Shaders[prog.stageShaders[0]].reflection); + changedBindings |= CopyProgramAttribBindings( + initial.resource.name, live.name, &m_Driver->m_Shaders[prog.stageShaders[0]].reflection); if(prog.stageShaders[4] != ResourceId()) - CopyProgramFragDataBindings(initial.resource.name, live.name, - &m_Driver->m_Shaders[prog.stageShaders[4]].reflection); + changedBindings |= CopyProgramFragDataBindings( + initial.resource.name, live.name, &m_Driver->m_Shaders[prog.stageShaders[4]].reflection); // we need to re-link the program to apply the bindings, as long as it's linkable. // See the comment on shaderProgramUnlinkable for more information. - if(!prog.shaderProgramUnlinkable) + if(!prog.shaderProgramUnlinkable && changedBindings) GL.glLinkProgram(live.name); - CopyProgramUniforms(initial.resource.name, live.name); + PerStageReflections stages; + m_Driver->FillReflectionArray(Id, stages); + + // we can pass in the same stages array, it's the same program essentially (reflection is + // identical) + CopyProgramUniforms(stages, initial.resource.name, stages, live.name); } else if(live.Namespace == eResFramebuffer) { diff --git a/renderdoc/driver/gl/gl_overlay.cpp b/renderdoc/driver/gl/gl_overlay.cpp index 1004ae43f..e2e69ff8b 100644 --- a/renderdoc/driver/gl/gl_overlay.cpp +++ b/renderdoc/driver/gl/gl_overlay.cpp @@ -37,7 +37,8 @@ #define OPENGL 1 #include "data/glsl/glsl_ubos_cpp.h" -void GLReplay::CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint fragShader) +bool GLReplay::CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint fragShader, + GLuint fragShaderSPIRV) { WrappedOpenGL &drv = *m_pDriver; @@ -60,11 +61,14 @@ void GLReplay::CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint frag // the reflection for the vertex shader, used to copy vertex bindings ShaderReflection *vsRefl = NULL; + bool HasSPIRVShaders = false; + bool HasGLSLShaders = false; + if(Program == 0) { if(Pipeline == 0) { - return; + return false; } else { @@ -76,6 +80,14 @@ void GLReplay::CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint frag { if(pipeDetails.stageShaders[i] != ResourceId()) { + const WrappedOpenGL::ShaderData &shadDetails = + m_pDriver->m_Shaders[pipeDetails.stageShaders[i]]; + + if(shadDetails.reflection.encoding == ShaderEncoding::SPIRV) + HasSPIRVShaders = true; + else + HasGLSLShaders = true; + programs[i] = m_pDriver->GetResourceManager()->GetCurrentResource(pipeDetails.stagePrograms[i]).name; shaders[i] = @@ -88,9 +100,6 @@ void GLReplay::CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint frag if(progDetails.shaderProgramUnlinkable) { - const WrappedOpenGL::ShaderData &shadDetails = - m_pDriver->m_Shaders[pipeDetails.stageShaders[i]]; - std::vector sources; sources.reserve(shadDetails.sources.size()); @@ -133,21 +142,40 @@ void GLReplay::CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint frag shaders[i] = m_pDriver->GetResourceManager()->GetCurrentResource(progDetails.stageShaders[i]).name; + const WrappedOpenGL::ShaderData &shadDetails = + m_pDriver->m_Shaders[progDetails.stageShaders[i]]; + + if(shadDetails.reflection.encoding == ShaderEncoding::SPIRV) + HasSPIRVShaders = true; + else + HasGLSLShaders = true; + if(i == 0) vsRefl = GetShader(progDetails.stageShaders[0], ShaderEntryPoint()); } } } + if(HasGLSLShaders && HasSPIRVShaders) + RDCERR("Unsupported - mixed GLSL and SPIR-V shaders in pipeline"); + // attach the shaders for(size_t i = 0; i < 4; i++) if(shaders[i]) drv.glAttachShader(DebugData.overlayProg, shaders[i]); - drv.glAttachShader(DebugData.overlayProg, fragShader); + if(HasSPIRVShaders) + { + RDCASSERT(fragShaderSPIRV); + drv.glAttachShader(DebugData.overlayProg, fragShaderSPIRV); + } + else + { + drv.glAttachShader(DebugData.overlayProg, fragShader); + } // copy the vertex attribs over from the source program - if(vsRefl && programs[0]) + if(vsRefl && programs[0] && !HasSPIRVShaders) CopyProgramAttribBindings(programs[0], DebugData.overlayProg, vsRefl); // link the overlay program @@ -158,7 +186,10 @@ void GLReplay::CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint frag if(shaders[i]) drv.glDetachShader(DebugData.overlayProg, shaders[i]); - drv.glDetachShader(DebugData.overlayProg, fragShader); + if(HasSPIRVShaders) + drv.glDetachShader(DebugData.overlayProg, fragShaderSPIRV); + else + drv.glDetachShader(DebugData.overlayProg, fragShader); // delete any temporaries for(size_t i = 0; i < 4; i++) @@ -173,14 +204,28 @@ void GLReplay::CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint frag { drv.glGetProgramInfoLog(DebugData.overlayProg, 1024, NULL, buffer); RDCERR("Error linking overlay program: %s", buffer); - return; + return false; } // copy the uniform values over from the source program. This is redundant but harmless if the // same program is bound to multiple stages. It's just inefficient - for(size_t i = 0; i < 4; i++) - if(programs[i]) - CopyProgramUniforms(programs[i], DebugData.overlayProg); + { + PerStageReflections dstStages; + m_pDriver->FillReflectionArray(ProgramRes(ctx, DebugData.overlayProg), dstStages); + + for(size_t i = 0; i < 4; i++) + { + if(programs[i]) + { + PerStageReflections stages; + m_pDriver->FillReflectionArray(ProgramRes(ctx, programs[i]), stages); + + CopyProgramUniforms(stages, programs[i], dstStages, DebugData.overlayProg); + } + } + } + + return HasSPIRVShaders; } ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOverlay overlay, @@ -276,6 +321,12 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve std::string source = GenerateGLSLShader(GetEmbeddedResource(glsl_quadwrite_frag), shaderType, glslVer, defines); DebugData.quadoverdrawFragShader = CreateShader(eGL_FRAGMENT_SHADER, source); + + // we expect if the SPIR-V extension is present then we've compiled this variant. + if(HasExt[ARB_gl_spirv]) + { + RDCASSERT(DebugData.quadoverdrawFragShaderSPIRV); + } } } else @@ -287,9 +338,20 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve // we bind the separable program created for each shader, and copy // uniforms and attrib bindings from the 'real' programs, wherever // they are. - CreateOverlayProgram(rs.Program.name, rs.Pipeline.name, DebugData.fixedcolFragShader); + bool spirvOverlay = + CreateOverlayProgram(rs.Program.name, rs.Pipeline.name, DebugData.fixedcolFragShader, + DebugData.fixedcolFragShaderSPIRV); drv.glUseProgram(DebugData.overlayProg); + GLint overlayFixedColLocation = 0; + + // on SPIR-V overlays we don't query the location, it's baked into the shader + if(spirvOverlay) + overlayFixedColLocation = 99; + else + overlayFixedColLocation = + drv.glGetUniformLocation(DebugData.overlayProg, "RENDERDOC_Fixed_Color"); + auto &texDetails = m_pDriver->m_Textures[texid]; GLenum texBindingEnum = eGL_TEXTURE_2D; @@ -386,9 +448,8 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve float black[] = {0.0f, 0.0f, 0.0f, 0.5f}; drv.glClearBufferfv(eGL_COLOR, 0, black); - GLint colLoc = drv.glGetUniformLocation(DebugData.overlayProg, "RENDERDOC_Fixed_Color"); float colVal[] = {0.8f, 0.1f, 0.8f, 1.0f}; - drv.glProgramUniform4fv(DebugData.overlayProg, colLoc, 1, colVal); + drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, colVal); ReplayLog(eventId, eReplay_OnlyDraw); } @@ -397,9 +458,8 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve float wireCol[] = {200.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f, 0.0f}; drv.glClearBufferfv(eGL_COLOR, 0, wireCol); - GLint colLoc = drv.glGetUniformLocation(DebugData.overlayProg, "RENDERDOC_Fixed_Color"); wireCol[3] = 1.0f; - drv.glProgramUniform4fv(DebugData.overlayProg, colLoc, 1, wireCol); + drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, wireCol); if(!IsGLES) { @@ -591,9 +651,8 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve float black[] = {0.0f, 0.0f, 0.0f, 0.0f}; drv.glClearBufferfv(eGL_COLOR, 0, black); - GLint colLoc = drv.glGetUniformLocation(DebugData.overlayProg, "RENDERDOC_Fixed_Color"); float red[] = {1.0f, 0.0f, 0.0f, 1.0f}; - drv.glProgramUniform4fv(DebugData.overlayProg, colLoc, 1, red); + drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, red); ReplayLog(eventId, eReplay_OnlyDraw); @@ -776,7 +835,7 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, rs.DrawFBO.name); float green[] = {0.0f, 1.0f, 0.0f, 1.0f}; - drv.glProgramUniform4fv(DebugData.overlayProg, colLoc, 1, green); + drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, green); if(overlay == DebugOverlay::Depth) { @@ -834,8 +893,7 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve col[0] = 1.0f; col[3] = 1.0f; - GLint colLoc = drv.glGetUniformLocation(DebugData.overlayProg, "RENDERDOC_Fixed_Color"); - drv.glProgramUniform4fv(DebugData.overlayProg, colLoc, 1, col); + drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, col); ReplayLog(eventId, eReplay_OnlyDraw); @@ -847,7 +905,7 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve col[0] = 0.0f; col[1] = 1.0f; - drv.glProgramUniform4fv(DebugData.overlayProg, colLoc, 1, col); + drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, col); ReplayLog(eventId, eReplay_OnlyDraw); } @@ -1429,15 +1487,19 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve // replace fragment shader. This is exactly what we did // at the start of this function for the single-event case, but now we have // to do it for every event - CreateOverlayProgram(prog, pipe, DebugData.quadoverdrawFragShader); + spirvOverlay = CreateOverlayProgram(prog, pipe, DebugData.quadoverdrawFragShader, + DebugData.quadoverdrawFragShaderSPIRV); drv.glUseProgram(DebugData.overlayProg); drv.glBindProgramPipeline(0); - GLint loc = drv.glGetUniformLocation(DebugData.overlayProg, "overdrawImage"); - if(loc != -1) - drv.glUniform1i(loc, 0); - else - RDCERR("Couldn't get location of overdrawImage"); + if(!spirvOverlay) + { + GLint loc = drv.glGetUniformLocation(DebugData.overlayProg, "overdrawImage"); + if(loc != -1) + drv.glUniform1i(loc, 0); + else + RDCERR("Couldn't get location of overdrawImage"); + } drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curdrawfbo); SafeBlitFramebuffer(0, 0, texDetails.width, texDetails.height, 0, 0, texDetails.width, diff --git a/renderdoc/driver/gl/gl_postvs.cpp b/renderdoc/driver/gl/gl_postvs.cpp index 14fd47c5b..cb9ae7f7f 100644 --- a/renderdoc/driver/gl/gl_postvs.cpp +++ b/renderdoc/driver/gl/gl_postvs.cpp @@ -76,6 +76,7 @@ void GLReplay::InitPostVSBuffers(uint32_t eventId) ShaderReflection *vsRefl = NULL; ShaderReflection *tesRefl = NULL; ShaderReflection *gsRefl = NULL; + SPIRVPatchData vsPatch, tesPatch, gsPatch; // the program we'll be binding, that we attach shaders to GLuint feedbackProg = drv.glCreateProgram(); @@ -124,14 +125,17 @@ void GLReplay::InitPostVSBuffers(uint32_t eventId) { vsRefl = GetShader(pipeDetails.stageShaders[i], ShaderEntryPoint()); glslVer = m_pDriver->m_Shaders[pipeDetails.stageShaders[0]].version; + vsPatch = m_pDriver->m_Shaders[pipeDetails.stageShaders[0]].patchData; } else if(i == 2) { tesRefl = GetShader(pipeDetails.stageShaders[2], ShaderEntryPoint()); + tesPatch = m_pDriver->m_Shaders[pipeDetails.stageShaders[2]].patchData; } else if(i == 3) { gsRefl = GetShader(pipeDetails.stageShaders[3], ShaderEntryPoint()); + gsPatch = m_pDriver->m_Shaders[pipeDetails.stageShaders[3]].patchData; } stageShaders[i] = rm->GetCurrentResource(pipeDetails.stageShaders[i]).name; @@ -147,15 +151,29 @@ void GLReplay::InitPostVSBuffers(uint32_t eventId) const WrappedOpenGL::ShaderData &shadDetails = m_pDriver->m_Shaders[pipeDetails.stageShaders[i]]; - std::vector sources; - sources.reserve(shadDetails.sources.size()); - - for(const std::string &s : shadDetails.sources) - sources.push_back(s.c_str()); - stageShaders[i] = tmpShaders[i] = drv.glCreateShader(ShaderEnum(i)); - drv.glShaderSource(tmpShaders[i], (GLsizei)sources.size(), sources.data(), NULL); - drv.glCompileShader(tmpShaders[i]); + + if(!shadDetails.sources.empty()) + { + std::vector sources; + sources.reserve(shadDetails.sources.size()); + + for(const std::string &s : shadDetails.sources) + sources.push_back(s.c_str()); + + drv.glShaderSource(tmpShaders[i], (GLsizei)sources.size(), sources.data(), NULL); + drv.glCompileShader(tmpShaders[i]); + } + else if(!shadDetails.spirvWords.empty()) + { + drv.glShaderBinary(1, &tmpShaders[i], eGL_SHADER_BINARY_FORMAT_SPIR_V, + shadDetails.spirvWords.data(), + (GLsizei)shadDetails.spirvWords.size() * sizeof(uint32_t)); + + drv.glSpecializeShader(tmpShaders[i], shadDetails.entryPoint.c_str(), + (GLuint)shadDetails.specIDs.size(), + shadDetails.specIDs.data(), shadDetails.specValues.data()); + } GLint status = 0; drv.glGetShaderiv(tmpShaders[i], eGL_COMPILE_STATUS, &status); @@ -184,14 +202,17 @@ void GLReplay::InitPostVSBuffers(uint32_t eventId) { vsRefl = GetShader(progDetails.stageShaders[0], ShaderEntryPoint()); glslVer = m_pDriver->m_Shaders[progDetails.stageShaders[0]].version; + vsPatch = m_pDriver->m_Shaders[progDetails.stageShaders[0]].patchData; } else if(i == 2 && progDetails.stageShaders[2] != ResourceId()) { tesRefl = GetShader(progDetails.stageShaders[2], ShaderEntryPoint()); + tesPatch = m_pDriver->m_Shaders[progDetails.stageShaders[2]].patchData; } else if(i == 3 && progDetails.stageShaders[3] != ResourceId()) { gsRefl = GetShader(progDetails.stageShaders[3], ShaderEntryPoint()); + gsPatch = m_pDriver->m_Shaders[progDetails.stageShaders[3]].patchData; } stageShaders[i] = rm->GetCurrentResource(progDetails.stageShaders[i]).name; @@ -257,203 +278,267 @@ void GLReplay::InitPostVSBuffers(uint32_t eventId) } } - // attach the vertex shader - drv.glAttachShader(feedbackProg, stageShaders[0]); - - // attach the dummy fragment shader, if it exists - if(dummyFrag) - drv.glAttachShader(feedbackProg, dummyFrag); - - std::list matrixVaryings; // matrices need some fixup - std::vector varyings; - - CopyProgramAttribBindings(stageSrcPrograms[0], feedbackProg, vsRefl); - - varyings.clear(); - uint32_t stride = 0; + GLuint vsOrigShader = 0; + int32_t posidx = -1; - for(const SigParameter &sig : vsRefl->outputSignature) + if(vsRefl->encoding == ShaderEncoding::SPIRV) { - const char *name = sig.varName.c_str(); - size_t len = sig.varName.size(); + // SPIR-V path + vsOrigShader = stageShaders[0]; - bool include = true; + stageShaders[0] = tmpShaders[0] = drv.glCreateShader(eGL_VERTEX_SHADER); - // for matrices with names including :row1, :row2 etc we only include :row0 - // as a varying (but increment the stride for all rows to account for the space) - // and modify the name to remove the :row0 part - const char *colon = strchr(name, ':'); - if(colon) + for(const SigParameter &sig : vsRefl->outputSignature) { - if(name[len - 1] != '0') + if(sig.systemValue == ShaderBuiltin::Position) { - include = false; - } - else - { - matrixVaryings.push_back(std::string(name, colon)); - name = matrixVaryings.back().c_str(); + posidx = 0; + break; } } - if(include) - varyings.push_back(name); + std::vector spirv; + spirv.resize(vsRefl->rawBytes.size() / sizeof(uint32_t)); + memcpy(spirv.data(), vsRefl->rawBytes.data(), vsRefl->rawBytes.size()); - if(sig.systemValue == ShaderBuiltin::Position) - posidx = int32_t(varyings.size()) - 1; + AddXFBAnnotations(*vsRefl, vsPatch, vsRefl->entryPoint.c_str(), spirv, stride); - if(sig.compType == CompType::Double) - stride += sizeof(double) * sig.compCount; - else - stride += sizeof(float) * sig.compCount; - } + drv.glShaderBinary(1, &stageShaders[0], eGL_SHADER_BINARY_FORMAT_SPIR_V, spirv.data(), + (GLsizei)spirv.size() * 4); - // shift position attribute up to first, keeping order otherwise - // the same - if(posidx > 0) - { - const char *pos = varyings[posidx]; - varyings.erase(varyings.begin() + posidx); - varyings.insert(varyings.begin(), pos); - } + drv.glSpecializeShader(stageShaders[0], vsRefl->entryPoint.c_str(), 0, NULL, NULL); - // this is REALLY ugly, but I've seen problems with varying specification, so we try and - // do some fixup by removing prefixes from the results we got from PROGRAM_OUTPUT. - // - // the problem I've seen is: - // - // struct vertex - // { - // vec4 Color; - // }; - // - // layout(location = 0) out vertex Out; - // - // (from g_truc gl-410-primitive-tessellation-2). On AMD the varyings are what you might expect - // (from - // the PROGRAM_OUTPUT interface names reflected out): "Out.Color", "gl_Position" - // however nvidia complains unless you use "Color", "gl_Position". This holds even if you add - // other - // variables to the vertex struct. - // - // strangely another sample that in-lines the output block like so: - // - // out block - // { - // vec2 Texcoord; - // } Out; - // - // uses "block.Texcoord" (reflected name from PROGRAM_OUTPUT and accepted by varyings string on - // both - // vendors). This is inconsistent as it's type.member not structname.member as move. - // - // The spec is very vague on exactly what these names should be, so I can't say which is correct - // out of these three possibilities. - // - // So our 'fix' is to loop while we have problems linking with the varyings (since we know - // otherwise - // linking should succeed, as we only get here with a successfully linked separable program - if - // it fails - // to link, it's assigned 0 earlier) and remove any prefixes from variables seen in the link error - // string. - // The error string is something like: - // "error: Varying (named Out.Color) specified but not present in the program object." - // - // Yeh. Ugly. Not guaranteed to work at all, but hopefully the common case will just be a single - // block - // without any nesting so this might work. - // At least we don't have to reallocate strings all over, since the memory is - // already owned elsewhere, we just need to modify pointers to trim prefixes. Bright side? + char buffer[1024] = {}; + GLint status = 0; + GL.glGetShaderiv(stageShaders[0], eGL_COMPILE_STATUS, &status); + if(status == 0) + { + GL.glGetShaderInfoLog(stageShaders[0], 1024, NULL, buffer); + RDCERR("SPIR-V post-vs patched shader compile error: %s", buffer); + return; + } + // attach the vertex shader + drv.glAttachShader(feedbackProg, stageShaders[0]); + + // attach the dummy fragment shader, if it exists + if(dummyFrag) + drv.glAttachShader(feedbackProg, dummyFrag); - GLint status = 0; - bool finished = false; - for(;;) - { - // specify current varyings & relink - drv.glTransformFeedbackVaryings(feedbackProg, (GLsizei)varyings.size(), &varyings[0], - eGL_INTERLEAVED_ATTRIBS); drv.glLinkProgram(feedbackProg); drv.glGetProgramiv(feedbackProg, eGL_LINK_STATUS, &status); - // all good! Hopefully we'll mostly hit this - if(status == 1) - break; - - // if finished is true, this was our last attempt - there are no - // more fixups possible - if(finished) - break; - - char buffer[1025] = {0}; - drv.glGetProgramInfoLog(feedbackProg, 1024, NULL, buffer); - - // assume we're finished and can't retry any more after this. - // if we find a potential 'fixup' we'll set this back to false - finished = true; - - // see if any of our current varyings are present in the buffer string - for(size_t i = 0; i < varyings.size(); i++) + if(status == 0) { - if(strstr(buffer, varyings[i])) + drv.glGetProgramInfoLog(feedbackProg, 1024, NULL, buffer); + RDCERR("SPIR-V post-vs patched program link error: %s", buffer); + return; + } + } + else + { + // non-SPIRV path + + // attach the vertex shader + drv.glAttachShader(feedbackProg, stageShaders[0]); + + // attach the dummy fragment shader, if it exists + if(dummyFrag) + drv.glAttachShader(feedbackProg, dummyFrag); + + std::list matrixVaryings; + std::vector varyings; + + CopyProgramAttribBindings(stageSrcPrograms[0], feedbackProg, vsRefl); + + for(const SigParameter &sig : vsRefl->outputSignature) + { + const char *name = sig.varName.c_str(); + size_t len = sig.varName.size(); + + bool include = true; + + // for matrices with names including :row1, :row2 etc we only include :row0 + // as a varying (but increment the stride for all rows to account for the space) + // and modify the name to remove the :row0 part + const char *colon = strchr(name, ':'); + if(colon) { - const char *prefix_removed = strchr(varyings[i], '.'); - - // does it contain a prefix? - if(prefix_removed) + if(name[len - 1] != '0') { - prefix_removed++; // now this is our string without the prefix + include = false; + } + else + { + matrixVaryings.push_back(std::string(name, colon)); + name = matrixVaryings.back().c_str(); + } + } - // first check this won't cause a duplicate - if it does, we have to try something else - bool duplicate = false; - for(size_t j = 0; j < varyings.size(); j++) + if(include) + varyings.push_back(name); + + if(sig.systemValue == ShaderBuiltin::Position) + posidx = int32_t(varyings.size()) - 1; + + if(sig.compType == CompType::Double) + stride += sizeof(double) * sig.compCount; + else + stride += sizeof(float) * sig.compCount; + } + + // shift position attribute up to first, keeping order otherwise + // the same + if(posidx > 0) + { + const char *pos = varyings[posidx]; + varyings.erase(varyings.begin() + posidx); + varyings.insert(varyings.begin(), pos); + } + + // this is REALLY ugly, but I've seen problems with varying specification, so we try and + // do some fixup by removing prefixes from the results we got from PROGRAM_OUTPUT. + // + // the problem I've seen is: + // + // struct vertex + // { + // vec4 Color; + // }; + // + // layout(location = 0) out vertex Out; + // + // (from g_truc gl-410-primitive-tessellation-2). On AMD the varyings are what you might expect + // (from the PROGRAM_OUTPUT interface names reflected out): "Out.Color", "gl_Position" + // however nvidia complains unless you use "Color", "gl_Position". This holds even if you add + // other variables to the vertex struct. + // + // strangely another sample that in-lines the output block like so: + // + // out block + // { + // vec2 Texcoord; + // } Out; + // + // uses "block.Texcoord" (reflected name from PROGRAM_OUTPUT and accepted by varyings string on + // both vendors). This is inconsistent as it's type.member not structname.member as move. + // + // The spec is very vague on exactly what these names should be, so I can't say which is correct + // out of these three possibilities. + // + // So our 'fix' is to loop while we have problems linking with the varyings (since we know + // otherwise linking should succeed, as we only get here with a successfully linked separable + // program - if it fails to link, it's assigned 0 earlier) and remove any prefixes from + // variables seen in the link error string. + // The error string is something like: + // "error: Varying (named Out.Color) specified but not present in the program object." + // + // Yeh. Ugly. Not guaranteed to work at all, but hopefully the common case will just be a single + // block without any nesting so this might work. + // At least we don't have to reallocate strings all over, since the memory is + // already owned elsewhere, we just need to modify pointers to trim prefixes. Bright side? + + GLint status = 0; + bool finished = false; + for(;;) + { + // specify current varyings & relink + drv.glTransformFeedbackVaryings(feedbackProg, (GLsizei)varyings.size(), &varyings[0], + eGL_INTERLEAVED_ATTRIBS); + drv.glLinkProgram(feedbackProg); + + drv.glGetProgramiv(feedbackProg, eGL_LINK_STATUS, &status); + + // all good! Hopefully we'll mostly hit this + if(status == 1) + break; + + // if finished is true, this was our last attempt - there are no + // more fixups possible + if(finished) + break; + + char buffer[1025] = {0}; + drv.glGetProgramInfoLog(feedbackProg, 1024, NULL, buffer); + + // assume we're finished and can't retry any more after this. + // if we find a potential 'fixup' we'll set this back to false + finished = true; + + // see if any of our current varyings are present in the buffer string + for(size_t i = 0; i < varyings.size(); i++) + { + if(strstr(buffer, varyings[i])) + { + const char *prefix_removed = strchr(varyings[i], '.'); + + // does it contain a prefix? + if(prefix_removed) { - if(!strcmp(varyings[j], prefix_removed)) + prefix_removed++; // now this is our string without the prefix + + // first check this won't cause a duplicate - if it does, we have to try something else + bool duplicate = false; + for(size_t j = 0; j < varyings.size(); j++) { - duplicate = true; + if(!strcmp(varyings[j], prefix_removed)) + { + duplicate = true; + break; + } + } + + if(!duplicate) + { + // we'll attempt this fixup + RDCWARN("Attempting XFB varying fixup, subst '%s' for '%s'", varyings[i], + prefix_removed); + varyings[i] = prefix_removed; + finished = false; + + // don't try more than one at once (just in case) break; } } - - if(!duplicate) - { - // we'll attempt this fixup - RDCWARN("Attempting XFB varying fixup, subst '%s' for '%s'", varyings[i], prefix_removed); - varyings[i] = prefix_removed; - finished = false; - - // don't try more than one at once (just in case) - break; - } } } } + + if(status == 0) + { + char buffer[1025] = {0}; + drv.glGetProgramInfoLog(feedbackProg, 1024, NULL, buffer); + RDCERR("Failed to fix-up. Link error making xfb vs program: %s", buffer); + m_PostVSData[eventId] = GLPostVSData(); + + // delete any temporaries + for(size_t i = 0; i < 4; i++) + if(tmpShaders[i]) + drv.glDeleteShader(tmpShaders[i]); + + drv.glDeleteShader(dummyFrag); + + drv.glDeleteProgram(feedbackProg); + + return; + } } - if(status == 0) - { - char buffer[1025] = {0}; - drv.glGetProgramInfoLog(feedbackProg, 1024, NULL, buffer); - RDCERR("Failed to fix-up. Link error making xfb vs program: %s", buffer); - m_PostVSData[eventId] = GLPostVSData(); - - // delete any temporaries - for(size_t i = 0; i < 4; i++) - if(tmpShaders[i]) - drv.glDeleteShader(tmpShaders[i]); - - drv.glDeleteShader(dummyFrag); - - drv.glDeleteProgram(feedbackProg); - - return; - } + // here the SPIR-V and GLSL paths recombine. // copy across any uniform values, bindings etc from the real program containing // the vertex stage - CopyProgramUniforms(stageSrcPrograms[0], feedbackProg); + { + PerStageReflections stages; + m_pDriver->FillReflectionArray(ProgramRes(drv.GetCtx(), stageSrcPrograms[0]), stages); + + PerStageReflections dstStages; + m_pDriver->FillReflectionArray(ProgramRes(drv.GetCtx(), feedbackProg), dstStages); + + CopyProgramUniforms(stages, stageSrcPrograms[0], dstStages, feedbackProg); + } // we don't want to do any work, so just discard before rasterizing drv.glEnable(eGL_RASTERIZER_DISCARD); @@ -860,130 +945,210 @@ void GLReplay::InitPostVSBuffers(uint32_t eventId) if(tesRefl || gsRefl) { ShaderReflection *lastRefl = gsRefl; + SPIRVPatchData lastPatch = gsPatch; + int lastIndex = 3; if(!lastRefl) + { lastRefl = tesRefl; + lastPatch = tesPatch; + lastIndex = 2; + } + + bool lastSPIRV = (lastRefl->encoding == ShaderEncoding::SPIRV); RDCASSERT(lastRefl); + // if the vertex shader was SPIR-V we didn't attach it and instead attached a tmp one with + // patched SPIR-V. Detach it and attach the original one without any XFB annotations + if(vsOrigShader) + { + drv.glDetachShader(feedbackProg, stageShaders[0]); + stageShaders[0] = vsOrigShader; + drv.glAttachShader(feedbackProg, stageShaders[0]); + } + // attach the other non-vertex shaders for(int i = 1; i < 4; i++) - if(stageShaders[i]) - drv.glAttachShader(feedbackProg, stageShaders[i]); - - varyings.clear(); - matrixVaryings.clear(); - - stride = 0; - posidx = -1; - - for(const SigParameter &sig : lastRefl->outputSignature) { - const char *name = sig.varName.c_str(); - size_t len = sig.varName.size(); - - bool include = true; - - // for matrices with names including :row1, :row2 etc we only include :row0 - // as a varying (but increment the stride for all rows to account for the space) - // and modify the name to remove the :row0 part - const char *colon = strchr(name, ':'); - if(colon) + if(stageShaders[i]) { - if(name[len - 1] != '0') + // if the last shader is non-SPIR-V, don't attach it - we'll build our own + if(lastSPIRV && i == lastIndex) + continue; + + drv.glAttachShader(feedbackProg, stageShaders[i]); + } + } + + GLint status = 0; + + if(lastSPIRV) + { + // SPIR-V path + stageShaders[lastIndex] = tmpShaders[lastIndex] = drv.glCreateShader(ShaderEnum(lastIndex)); + + posidx = -1; + + for(const SigParameter &sig : lastRefl->outputSignature) + { + if(sig.systemValue == ShaderBuiltin::Position) { - include = false; - } - else - { - matrixVaryings.push_back(std::string(name, colon)); - name = matrixVaryings.back().c_str(); + posidx = 0; + break; } } - if(include) - varyings.push_back(name); + std::vector spirv; + spirv.resize(lastRefl->rawBytes.size() / sizeof(uint32_t)); + memcpy(spirv.data(), lastRefl->rawBytes.data(), lastRefl->rawBytes.size()); - if(sig.systemValue == ShaderBuiltin::Position) - posidx = int32_t(varyings.size()) - 1; + AddXFBAnnotations(*lastRefl, lastPatch, lastRefl->entryPoint.c_str(), spirv, stride); - uint32_t elemSize = sig.compType == CompType::Double ? sizeof(double) : sizeof(float); + drv.glShaderBinary(1, &stageShaders[lastIndex], eGL_SHADER_BINARY_FORMAT_SPIR_V, spirv.data(), + (GLsizei)spirv.size() * 4); - stride += elemSize * sig.compCount; - } + drv.glSpecializeShader(stageShaders[lastIndex], lastRefl->entryPoint.c_str(), 0, NULL, NULL); - // shift position attribute up to first, keeping order otherwise - // the same - if(posidx > 0) - { - const char *pos = varyings[posidx]; - varyings.erase(varyings.begin() + posidx); - varyings.insert(varyings.begin(), pos); - } + char buffer[1024] = {}; + GL.glGetShaderiv(stageShaders[lastIndex], eGL_COMPILE_STATUS, &status); + if(status == 0) + { + GL.glGetShaderInfoLog(stageShaders[lastIndex], 1024, NULL, buffer); + RDCERR("SPIR-V post-gs patched shader compile error: %s", buffer); + return; + } - // see above for the justification/explanation of this monstrosity. + // attach the last shader + drv.glAttachShader(feedbackProg, stageShaders[lastIndex]); - status = 0; - finished = false; - for(;;) - { - // specify current varyings & relink - drv.glTransformFeedbackVaryings(feedbackProg, (GLsizei)varyings.size(), &varyings[0], - eGL_INTERLEAVED_ATTRIBS); drv.glLinkProgram(feedbackProg); drv.glGetProgramiv(feedbackProg, eGL_LINK_STATUS, &status); - // all good! Hopefully we'll mostly hit this - if(status == 1) - break; - - // if finished is true, this was our last attempt - there are no - // more fixups possible - if(finished) - break; - - char buffer[1025] = {0}; - drv.glGetProgramInfoLog(feedbackProg, 1024, NULL, buffer); - - // assume we're finished and can't retry any more after this. - // if we find a potential 'fixup' we'll set this back to false - finished = true; - - // see if any of our current varyings are present in the buffer string - for(size_t i = 0; i < varyings.size(); i++) + if(status == 0) { - if(strstr(buffer, varyings[i])) + drv.glGetProgramInfoLog(feedbackProg, 1024, NULL, buffer); + RDCERR("SPIR-V post-gs patched program link error: %s", buffer); + return; + } + } + else + { + std::list matrixVaryings; + std::vector varyings; + + stride = 0; + posidx = -1; + + for(const SigParameter &sig : lastRefl->outputSignature) + { + const char *name = sig.varName.c_str(); + size_t len = sig.varName.size(); + + bool include = true; + + // for matrices with names including :row1, :row2 etc we only include :row0 + // as a varying (but increment the stride for all rows to account for the space) + // and modify the name to remove the :row0 part + const char *colon = strchr(name, ':'); + if(colon) { - const char *prefix_removed = strchr(varyings[i], '.'); - - // does it contain a prefix? - if(prefix_removed) + if(name[len - 1] != '0') { - prefix_removed++; // now this is our string without the prefix + include = false; + } + else + { + matrixVaryings.push_back(std::string(name, colon)); + name = matrixVaryings.back().c_str(); + } + } - // first check this won't cause a duplicate - if it does, we have to try something else - bool duplicate = false; - for(size_t j = 0; j < varyings.size(); j++) + if(include) + varyings.push_back(name); + + if(sig.systemValue == ShaderBuiltin::Position) + posidx = int32_t(varyings.size()) - 1; + + uint32_t elemSize = sig.compType == CompType::Double ? sizeof(double) : sizeof(float); + + stride += elemSize * sig.compCount; + } + + // shift position attribute up to first, keeping order otherwise + // the same + if(posidx > 0) + { + const char *pos = varyings[posidx]; + varyings.erase(varyings.begin() + posidx); + varyings.insert(varyings.begin(), pos); + } + + // see above for the justification/explanation of this monstrosity. + + bool finished = false; + for(;;) + { + // specify current varyings & relink + drv.glTransformFeedbackVaryings(feedbackProg, (GLsizei)varyings.size(), &varyings[0], + eGL_INTERLEAVED_ATTRIBS); + drv.glLinkProgram(feedbackProg); + + drv.glGetProgramiv(feedbackProg, eGL_LINK_STATUS, &status); + + // all good! Hopefully we'll mostly hit this + if(status == 1) + break; + + // if finished is true, this was our last attempt - there are no + // more fixups possible + if(finished) + break; + + char buffer[1025] = {0}; + drv.glGetProgramInfoLog(feedbackProg, 1024, NULL, buffer); + + // assume we're finished and can't retry any more after this. + // if we find a potential 'fixup' we'll set this back to false + finished = true; + + // see if any of our current varyings are present in the buffer string + for(size_t i = 0; i < varyings.size(); i++) + { + if(strstr(buffer, varyings[i])) + { + const char *prefix_removed = strchr(varyings[i], '.'); + + // does it contain a prefix? + if(prefix_removed) { - if(!strcmp(varyings[j], prefix_removed)) + prefix_removed++; // now this is our string without the prefix + + // first check this won't cause a duplicate - if it does, we have to try something + // else + bool duplicate = false; + for(size_t j = 0; j < varyings.size(); j++) { - duplicate = true; + if(!strcmp(varyings[j], prefix_removed)) + { + duplicate = true; + break; + } + } + + if(!duplicate) + { + // we'll attempt this fixup + RDCWARN("Attempting XFB varying fixup, subst '%s' for '%s'", varyings[i], + prefix_removed); + varyings[i] = prefix_removed; + finished = false; + + // don't try more than one at once (just in case) break; } } - - if(!duplicate) - { - // we'll attempt this fixup - RDCWARN("Attempting XFB varying fixup, subst '%s' for '%s'", varyings[i], - prefix_removed); - varyings[i] = prefix_removed; - finished = false; - - // don't try more than one at once (just in case) - break; - } } } } @@ -1002,20 +1167,44 @@ void GLReplay::InitPostVSBuffers(uint32_t eventId) } else { + PerStageReflections dstStages; + m_pDriver->FillReflectionArray(ProgramRes(drv.GetCtx(), feedbackProg), dstStages); + // copy across any uniform values, bindings etc from the real program containing // the vertex stage - CopyProgramUniforms(stageSrcPrograms[0], feedbackProg); + { + PerStageReflections stages; + m_pDriver->FillReflectionArray(ProgramRes(drv.GetCtx(), stageSrcPrograms[0]), stages); + + CopyProgramUniforms(stages, stageSrcPrograms[0], dstStages, feedbackProg); + } // if tessellation is enabled, bind & copy uniforms. Note, control shader is optional // independent of eval shader (default values are used for the tessellation levels). if(stageSrcPrograms[1]) - CopyProgramUniforms(stageSrcPrograms[1], feedbackProg); + { + PerStageReflections stages; + m_pDriver->FillReflectionArray(ProgramRes(drv.GetCtx(), stageSrcPrograms[1]), stages); + + CopyProgramUniforms(stages, stageSrcPrograms[1], dstStages, feedbackProg); + } + if(stageSrcPrograms[2]) - CopyProgramUniforms(stageSrcPrograms[2], feedbackProg); + { + PerStageReflections stages; + m_pDriver->FillReflectionArray(ProgramRes(drv.GetCtx(), stageSrcPrograms[2]), stages); + + CopyProgramUniforms(stages, stageSrcPrograms[2], dstStages, feedbackProg); + } // if we have a geometry shader, bind & copy uniforms if(stageSrcPrograms[3]) - CopyProgramUniforms(stageSrcPrograms[3], feedbackProg); + { + PerStageReflections stages; + m_pDriver->FillReflectionArray(ProgramRes(drv.GetCtx(), stageSrcPrograms[3]), stages); + + CopyProgramUniforms(stages, stageSrcPrograms[3], dstStages, feedbackProg); + } // bind our program and do the feedback draw drv.glUseProgram(feedbackProg); diff --git a/renderdoc/driver/gl/gl_program_iterate.cpp b/renderdoc/driver/gl/gl_program_iterate.cpp index 4ec795257..1773383c2 100644 --- a/renderdoc/driver/gl/gl_program_iterate.cpp +++ b/renderdoc/driver/gl/gl_program_iterate.cpp @@ -78,6 +78,343 @@ struct ProgramUniforms DECLARE_REFLECTION_STRUCT(ProgramUniforms); +struct UnrolledSPIRVConstant +{ + GLenum glType = eGL_NONE; + char name[1024] = {}; + uint32_t arraySize = 1; + int32_t location = -1; +}; + +static GLenum MakeGLType(const ShaderVariableType &type) +{ + if(type.descriptor.type == VarType::Double) + { + if(type.descriptor.columns == 4 && type.descriptor.rows == 4) + return eGL_DOUBLE_MAT4; + if(type.descriptor.columns == 4 && type.descriptor.rows == 3) + return eGL_DOUBLE_MAT4x3; + if(type.descriptor.columns == 4 && type.descriptor.rows == 2) + return eGL_DOUBLE_MAT4x2; + if(type.descriptor.columns == 4 && type.descriptor.rows == 1) + return eGL_DOUBLE_VEC4; + + if(type.descriptor.columns == 3 && type.descriptor.rows == 4) + return eGL_DOUBLE_MAT3x4; + if(type.descriptor.columns == 3 && type.descriptor.rows == 3) + return eGL_DOUBLE_MAT3; + if(type.descriptor.columns == 3 && type.descriptor.rows == 2) + return eGL_DOUBLE_MAT3x2; + if(type.descriptor.columns == 3 && type.descriptor.rows == 1) + return eGL_DOUBLE_VEC3; + + if(type.descriptor.columns == 2 && type.descriptor.rows == 4) + return eGL_DOUBLE_MAT2x4; + if(type.descriptor.columns == 2 && type.descriptor.rows == 3) + return eGL_DOUBLE_MAT2x4; + if(type.descriptor.columns == 2 && type.descriptor.rows == 2) + return eGL_DOUBLE_MAT2; + if(type.descriptor.columns == 2 && type.descriptor.rows == 1) + return eGL_DOUBLE_VEC2; + + if(type.descriptor.columns == 1 && type.descriptor.rows == 4) + return eGL_DOUBLE_VEC4; + if(type.descriptor.columns == 1 && type.descriptor.rows == 3) + return eGL_DOUBLE_VEC3; + if(type.descriptor.columns == 1 && type.descriptor.rows == 2) + return eGL_DOUBLE_VEC2; + + if(type.descriptor.rows == 1 && type.descriptor.columns == 4) + return eGL_DOUBLE_VEC4; + if(type.descriptor.rows == 1 && type.descriptor.columns == 3) + return eGL_DOUBLE_VEC3; + if(type.descriptor.rows == 1 && type.descriptor.columns == 2) + return eGL_DOUBLE_VEC2; + + return eGL_DOUBLE; + } + else if(type.descriptor.type == VarType::Float) + { + if(type.descriptor.columns == 4 && type.descriptor.rows == 4) + return eGL_FLOAT_MAT4; + if(type.descriptor.columns == 4 && type.descriptor.rows == 3) + return eGL_FLOAT_MAT4x3; + if(type.descriptor.columns == 4 && type.descriptor.rows == 2) + return eGL_FLOAT_MAT4x2; + if(type.descriptor.columns == 4 && type.descriptor.rows == 1) + return eGL_FLOAT_VEC4; + + if(type.descriptor.columns == 3 && type.descriptor.rows == 4) + return eGL_FLOAT_MAT3x4; + if(type.descriptor.columns == 3 && type.descriptor.rows == 3) + return eGL_FLOAT_MAT3; + if(type.descriptor.columns == 3 && type.descriptor.rows == 2) + return eGL_FLOAT_MAT3x2; + if(type.descriptor.columns == 3 && type.descriptor.rows == 1) + return eGL_FLOAT_VEC3; + + if(type.descriptor.columns == 2 && type.descriptor.rows == 4) + return eGL_FLOAT_MAT2x4; + if(type.descriptor.columns == 2 && type.descriptor.rows == 3) + return eGL_FLOAT_MAT2x4; + if(type.descriptor.columns == 2 && type.descriptor.rows == 2) + return eGL_FLOAT_MAT2; + if(type.descriptor.columns == 2 && type.descriptor.rows == 1) + return eGL_FLOAT_VEC2; + + if(type.descriptor.columns == 1 && type.descriptor.rows == 4) + return eGL_FLOAT_VEC4; + if(type.descriptor.columns == 1 && type.descriptor.rows == 3) + return eGL_FLOAT_VEC3; + if(type.descriptor.columns == 1 && type.descriptor.rows == 2) + return eGL_FLOAT_VEC2; + if(type.descriptor.columns == 1 && type.descriptor.rows == 1) + return eGL_FLOAT; + + if(type.descriptor.rows == 1 && type.descriptor.columns == 4) + return eGL_FLOAT_VEC4; + if(type.descriptor.rows == 1 && type.descriptor.columns == 3) + return eGL_FLOAT_VEC3; + if(type.descriptor.rows == 1 && type.descriptor.columns == 2) + return eGL_FLOAT_VEC2; + if(type.descriptor.rows == 1 && type.descriptor.columns == 1) + return eGL_FLOAT; + + return eGL_FLOAT; + } + else if(type.descriptor.type == VarType::SInt) + { + if(type.descriptor.columns == 1 && type.descriptor.rows == 4) + return eGL_INT_VEC4; + if(type.descriptor.columns == 1 && type.descriptor.rows == 3) + return eGL_INT_VEC3; + if(type.descriptor.columns == 1 && type.descriptor.rows == 2) + return eGL_INT_VEC2; + if(type.descriptor.columns == 1 && type.descriptor.rows == 1) + return eGL_INT; + + if(type.descriptor.rows == 1 && type.descriptor.columns == 4) + return eGL_INT_VEC4; + if(type.descriptor.rows == 1 && type.descriptor.columns == 3) + return eGL_INT_VEC3; + if(type.descriptor.rows == 1 && type.descriptor.columns == 2) + return eGL_INT_VEC2; + if(type.descriptor.rows == 1 && type.descriptor.columns == 1) + return eGL_INT; + + return eGL_INT; + } + else if(type.descriptor.type == VarType::UInt) + { + if(type.descriptor.columns == 1 && type.descriptor.rows == 4) + return eGL_UNSIGNED_INT_VEC4; + if(type.descriptor.columns == 1 && type.descriptor.rows == 3) + return eGL_UNSIGNED_INT_VEC3; + if(type.descriptor.columns == 1 && type.descriptor.rows == 2) + return eGL_UNSIGNED_INT_VEC2; + if(type.descriptor.columns == 1 && type.descriptor.rows == 1) + return eGL_UNSIGNED_INT; + + if(type.descriptor.rows == 1 && type.descriptor.columns == 4) + return eGL_UNSIGNED_INT_VEC4; + if(type.descriptor.rows == 1 && type.descriptor.columns == 3) + return eGL_UNSIGNED_INT_VEC3; + if(type.descriptor.rows == 1 && type.descriptor.columns == 2) + return eGL_UNSIGNED_INT_VEC2; + if(type.descriptor.rows == 1 && type.descriptor.columns == 1) + return eGL_UNSIGNED_INT; + + return eGL_UNSIGNED_INT; + } + + RDCERR("Unhandled GL type"); + + return eGL_FLOAT; +} + +static GLenum MakeGLType(const ShaderResource &res) +{ + if(res.variableType.descriptor.type == VarType::UInt) + { + switch(res.resType) + { + case TextureType::Buffer: return eGL_UNSIGNED_INT_SAMPLER_BUFFER; + case TextureType::Texture1D: return eGL_UNSIGNED_INT_SAMPLER_1D; + case TextureType::Texture1DArray: return eGL_UNSIGNED_INT_SAMPLER_1D_ARRAY; + case TextureType::Texture2D: return eGL_UNSIGNED_INT_SAMPLER_2D; + case TextureType::TextureRect: return eGL_UNSIGNED_INT_SAMPLER_2D_RECT; + case TextureType::Texture2DArray: return eGL_UNSIGNED_INT_SAMPLER_2D_ARRAY; + case TextureType::Texture2DMS: return eGL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE; + case TextureType::Texture2DMSArray: return eGL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY; + case TextureType::Texture3D: return eGL_UNSIGNED_INT_SAMPLER_3D; + case TextureType::TextureCube: return eGL_UNSIGNED_INT_SAMPLER_CUBE; + case TextureType::TextureCubeArray: return eGL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY; + } + } + else if(res.variableType.descriptor.type == VarType::SInt) + { + switch(res.resType) + { + case TextureType::Buffer: return eGL_INT_SAMPLER_BUFFER; + case TextureType::Texture1D: return eGL_INT_SAMPLER_1D; + case TextureType::Texture1DArray: return eGL_INT_SAMPLER_1D_ARRAY; + case TextureType::Texture2D: return eGL_INT_SAMPLER_2D; + case TextureType::TextureRect: return eGL_INT_SAMPLER_2D_RECT; + case TextureType::Texture2DArray: return eGL_INT_SAMPLER_2D_ARRAY; + case TextureType::Texture2DMS: return eGL_INT_SAMPLER_2D_MULTISAMPLE; + case TextureType::Texture2DMSArray: return eGL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY; + case TextureType::Texture3D: return eGL_INT_SAMPLER_3D; + case TextureType::TextureCube: return eGL_INT_SAMPLER_CUBE; + case TextureType::TextureCubeArray: return eGL_INT_SAMPLER_CUBE_MAP_ARRAY; + } + } + else + { + switch(res.resType) + { + case TextureType::Buffer: return eGL_SAMPLER_BUFFER; + case TextureType::Texture1D: return eGL_SAMPLER_1D; + case TextureType::Texture1DArray: return eGL_SAMPLER_1D_ARRAY; + case TextureType::Texture2D: return eGL_SAMPLER_2D; + case TextureType::TextureRect: return eGL_SAMPLER_2D_RECT; + case TextureType::Texture2DArray: return eGL_SAMPLER_2D_ARRAY; + case TextureType::Texture2DMS: return eGL_SAMPLER_2D_MULTISAMPLE; + case TextureType::Texture2DMSArray: return eGL_SAMPLER_2D_MULTISAMPLE_ARRAY; + case TextureType::Texture3D: return eGL_SAMPLER_3D; + case TextureType::TextureCube: return eGL_SAMPLER_CUBE; + case TextureType::TextureCubeArray: return eGL_SAMPLER_CUBE_MAP_ARRAY; + } + } + + RDCERR("Unhandled GL type"); + + return eGL_SAMPLER_2D; +} + +static void UnrollConstant(rdcarray &unrolled, const ShaderConstant &var, + const rdcstr &basename, uint32_t &location) +{ + rdcstr name = basename; + + if(!basename.empty()) + { + if(var.name[0] == '[') + name += var.name; + else + name += "." + var.name; + } + else + { + name = var.name; + } + + const uint32_t arraySize = RDCMAX(1U, var.type.descriptor.elements); + + if(var.type.members.empty()) + { + if(arraySize > 1) + name += "[0]"; + + UnrolledSPIRVConstant u; + u.glType = MakeGLType(var.type); + memcpy(u.name, name.c_str(), RDCMIN(name.size(), ARRAY_COUNT(u.name))); + u.arraySize = arraySize; + u.location = basename.empty() ? var.byteOffset : location; + + unrolled.push_back(u); + + location += arraySize; + } + else + { + if(basename.empty()) + location = var.byteOffset; + + for(uint32_t i = 0; i < arraySize; i++) + { + if(arraySize > 1) + name += StringFormat::Fmt("[%u]", i); + + for(const ShaderConstant &member : var.type.members) + UnrollConstant(unrolled, member, name, location); + } + } +} + +static void UnrollConstant(rdcarray &unrolled, const ShaderConstant &var) +{ + uint32_t location; + UnrollConstant(unrolled, var, rdcstr(), location); +} + +static void UnrollConstants(const PerStageReflections &stages, + rdcarray &globals) +{ + for(size_t s = 0; s < 6; s++) + { + if(!stages.refls[s]) + continue; + + // check if we have a non-buffer backed UBO, if so that's our globals. + for(const ConstantBlock &cb : stages.refls[s]->constantBlocks) + { + if(!cb.bufferBacked && cb.byteSize > 0) + { + for(const ShaderConstant &shaderConst : cb.variables) + { + // location is stored in the byteOffset. Search to see if we already have a global at + // this location since stages can share the same globals + int32_t location = (int32_t)shaderConst.byteOffset; + + bool already = false; + + for(const UnrolledSPIRVConstant &existing : globals) + { + if(existing.location == location) + { + already = true; + break; + } + } + + if(!already) + UnrollConstant(globals, shaderConst); + } + } + } + + // now include the samplers which can be bound + for(const ShaderResource &res : stages.refls[s]->readOnlyResources) + { + if(res.isTexture && res.bindPoint < stages.mappings[s]->readOnlyResources.count()) + { + int32_t location = -stages.mappings[s]->readOnlyResources[res.bindPoint].bind; + + bool already = false; + + for(const UnrolledSPIRVConstant &existing : globals) + { + if(existing.location == location) + { + already = true; + break; + } + } + + if(!already) + { + UnrolledSPIRVConstant u; + u.glType = MakeGLType(res); + memcpy(u.name, res.name.c_str(), RDCMIN(res.name.size(), ARRAY_COUNT(u.name))); + u.arraySize = 1; + u.location = location; + globals.push_back(u); + } + } + } + } +} + template void DoSerialise(SerialiserType &ser, ProgramUniformValue &el) { @@ -151,6 +488,7 @@ void DoSerialise(SerialiserType &ser, ProgramUniformValue &el) case eGL_UNSIGNED_INT_SAMPLER_2D: case eGL_UNSIGNED_INT_SAMPLER_3D: case eGL_UNSIGNED_INT_SAMPLER_CUBE: + case eGL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_1D_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_2D_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: @@ -295,8 +633,10 @@ void DoSerialise(SerialiserType &ser, ProgramUniforms &el) } template -static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuint progSrc, - GLuint progDst, std::map *locTranslate) +static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, + const PerStageReflections &srcStages, GLuint progSrc, + const PerStageReflections &dstStages, GLuint progDst, + std::map *locTranslate) { const bool ReadSourceProgram = CopyUniforms || (SerialiseUniforms && ser && ser->IsWriting()); const bool WriteDestProgram = CopyUniforms || (SerialiseUniforms && ser && ser->IsReading()); @@ -304,6 +644,21 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin RDCCOMPILE_ASSERT((CopyUniforms && !SerialiseUniforms) || (!CopyUniforms && SerialiseUniforms), "Invalid call to ForAllProgramUniforms"); + // When programs are SPIR-V we have to rely on our own reflection since the driver's reflection + // can't be trusted, or at least used in a normal way. Since SPIR-V is immutable for many things + // we only need to process uniform values - for compatibility we still serialise the same, but we + // skip fetching or applying UBO bindings etc. + bool IsSrcProgramSPIRV = false; + for(size_t i = 0; i < 6; i++) + IsSrcProgramSPIRV |= srcStages.refls[i] && srcStages.refls[i]->encoding == ShaderEncoding::SPIRV; + + bool IsDstProgramSPIRV = false; + for(size_t i = 0; i < 6; i++) + IsDstProgramSPIRV |= dstStages.refls[i] && dstStages.refls[i]->encoding == ShaderEncoding::SPIRV; + + RDCASSERTMSG("Expect both programs to be SPIR-V in ForAllProgramUniforms", + IsSrcProgramSPIRV == IsDstProgramSPIRV, IsSrcProgramSPIRV, IsDstProgramSPIRV); + // this struct will be serialised with the uniform binding data, or if we're just copying it will // be used to store the data fetched from the source program, before being applied to the // destination program. It's slightly redundant since we could unify the loops (as the code used @@ -314,14 +669,28 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin // if we're reading the source program, iterate over the interfaces and fetch the data. if(CheckConstParam(ReadSourceProgram)) { - const size_t numProps = 5; - GLenum resProps[numProps] = { + constexpr size_t numProps = 5; + constexpr GLenum resProps[numProps] = { eGL_BLOCK_INDEX, eGL_TYPE, eGL_NAME_LENGTH, eGL_ARRAY_SIZE, eGL_LOCATION, }; GLint values[numProps]; GLint NumUniforms = 0; - GL.glGetProgramInterfaceiv(progSrc, eGL_UNIFORM, eGL_ACTIVE_RESOURCES, &NumUniforms); + rdcarray spirvGlobals; + + if(IsSrcProgramSPIRV) + { + // Unfortunately since this is a program-global reflection we need to go through each shader + // and add its variables (if they don't already exist) to get a union of all shaders for the + // program. + UnrollConstants(srcStages, spirvGlobals); + + NumUniforms = (GLint)spirvGlobals.size(); + } + else + { + GL.glGetProgramInterfaceiv(progSrc, eGL_UNIFORM, eGL_ACTIVE_RESOURCES, &NumUniforms); + } // this is a very conservative figure - many uniforms will be in UBOs and so will be ignored serialisedUniforms.ValueUniforms.reserve(NumUniforms); @@ -334,7 +703,26 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin std::string basename; bool isArray = false; - GL.glGetProgramResourceiv(progSrc, eGL_UNIFORM, i, numProps, resProps, numProps, NULL, values); + if(IsSrcProgramSPIRV) + { + // hardcode manual reflection from SPIR-V constant. + RDCCOMPILE_ASSERT(numProps == 5 && resProps[0] == eGL_BLOCK_INDEX && + resProps[1] == eGL_TYPE && resProps[2] == eGL_NAME_LENGTH && + resProps[3] == eGL_ARRAY_SIZE && resProps[4] == eGL_LOCATION, + "reflection properties have changed - update manual SPIR-V reflection"); + + // these are implicitly globals + values[0] = -1; + values[1] = spirvGlobals[i].glType; + values[2] = 1; // unused + values[3] = spirvGlobals[i].arraySize; + values[4] = spirvGlobals[i].location; + } + else + { + GL.glGetProgramResourceiv(progSrc, eGL_UNIFORM, i, numProps, resProps, numProps, NULL, + values); + } // we don't need to consider uniforms within UBOs if(values[0] >= 0) @@ -346,7 +734,15 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin srcLocation = values[4]; char n[1024] = {0}; - GL.glGetProgramResourceName(progSrc, eGL_UNIFORM, i, values[2], NULL, n); + if(IsSrcProgramSPIRV) + { + RDCCOMPILE_ASSERT(sizeof(n) == sizeof(spirvGlobals[i].name), "Array sizes have changed"); + memcpy(n, spirvGlobals[i].name, sizeof(n)); + } + else + { + GL.glGetProgramResourceName(progSrc, eGL_UNIFORM, i, values[2], NULL, n); + } if(arraySize > 1) { @@ -372,6 +768,8 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin uniform.IsArray = isArray; uniform.Values.resize(arraySize); + GLuint baseLocation = srcLocation; + // loop over every element in the array (arraySize = 1 for non arrays) for(GLint arr = 0; arr < arraySize; arr++) { @@ -393,7 +791,10 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin { name += StringFormat::Fmt("[%d]", arr); - uniformVal.Location = srcLocation = GL.glGetUniformLocation(progSrc, name.c_str()); + if(IsSrcProgramSPIRV) + uniformVal.Location = srcLocation = baseLocation + arr; + else + uniformVal.Location = srcLocation = GL.glGetUniformLocation(progSrc, name.c_str()); if(srcLocation == -1) RDCWARN("Couldn't get srcLocation for %s", name.c_str()); @@ -470,6 +871,7 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin case eGL_UNSIGNED_INT_SAMPLER_2D: case eGL_UNSIGNED_INT_SAMPLER_3D: case eGL_UNSIGNED_INT_SAMPLER_CUBE: + case eGL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_1D_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_2D_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: @@ -530,7 +932,10 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin // now find how many UBOs we have, and store their binding indices GLint numUBOs = 0; - GL.glGetProgramInterfaceiv(progSrc, eGL_UNIFORM_BLOCK, eGL_ACTIVE_RESOURCES, &numUBOs); + + // SPIR-V shaders don't allow changing UBO values, so simply omit them entirely + if(!IsSrcProgramSPIRV) + GL.glGetProgramInterfaceiv(progSrc, eGL_UNIFORM_BLOCK, eGL_ACTIVE_RESOURCES, &numUBOs); serialisedUniforms.UBOBindings.reserve(numUBOs); @@ -549,7 +954,9 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin // finally, if SSBOs are supported on this implementation, fetch their bindings GLint numSSBOs = 0; - if(HasExt[ARB_shader_storage_buffer_object]) + + // SPIR-V shaders don't allow changing SSBO values, so simply omit them entirely + if(HasExt[ARB_shader_storage_buffer_object] && !IsSrcProgramSPIRV) GL.glGetProgramInterfaceiv(progSrc, eGL_SHADER_STORAGE_BLOCK, eGL_ACTIVE_RESOURCES, &numSSBOs); serialisedUniforms.SSBOBindings.reserve(numSSBOs); @@ -579,6 +986,10 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin // serialisedUniforms if(CheckConstParam(WriteDestProgram) && IsReplayMode(state)) { + rdcarray spirvGlobals; + if(IsDstProgramSPIRV) + UnrollConstants(dstStages, spirvGlobals); + // loop over the loose global uniforms, see if there is an equivalent, and apply it. for(const ProgramUniform &uniform : serialisedUniforms.ValueUniforms) { @@ -591,7 +1002,33 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin if(uniform.IsArray) name += StringFormat::Fmt("[%u]", (uint32_t)arr); - GLint dstLocation = GL.glGetUniformLocation(progDst, name.c_str()); + GLint dstLocation = -1; + + if(IsDstProgramSPIRV) + { + dstLocation = -1; + + int32_t baseLocation = val.Location - (int32_t)arr; + + RDCASSERT(baseLocation == uniform.Values[0].Location); + + // for SPIR-V the locations are fixed in the shader and are not mutable. We just check for + // existance of something with this location. If nothing is found, we return -1 + // (non-existant) which prevents us from trying to write to a bad location. + for(const UnrolledSPIRVConstant &var : spirvGlobals) + { + if(var.location == baseLocation) + { + dstLocation = val.Location; + break; + } + } + } + else + { + dstLocation = GL.glGetUniformLocation(progDst, name.c_str()); + } + if(locTranslate) (*locTranslate)[val.Location] = dstLocation; @@ -669,6 +1106,17 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin case eGL_DOUBLE_VEC2: GL.glProgramUniform2dv(progDst, dstLocation, 1, dv); break; case eGL_DOUBLE_VEC3: GL.glProgramUniform3dv(progDst, dstLocation, 1, dv); break; case eGL_DOUBLE_VEC4: GL.glProgramUniform4dv(progDst, dstLocation, 1, dv); break; + case eGL_INT_VEC2: GL.glProgramUniform2iv(progDst, dstLocation, 1, iv); break; + case eGL_INT_VEC3: GL.glProgramUniform3iv(progDst, dstLocation, 1, iv); break; + case eGL_INT_VEC4: GL.glProgramUniform4iv(progDst, dstLocation, 1, iv); break; + case eGL_UNSIGNED_INT: + case eGL_BOOL: GL.glProgramUniform1uiv(progDst, dstLocation, 1, uiv); break; + case eGL_UNSIGNED_INT_VEC2: + case eGL_BOOL_VEC2: GL.glProgramUniform2uiv(progDst, dstLocation, 1, uiv); break; + case eGL_UNSIGNED_INT_VEC3: + case eGL_BOOL_VEC3: GL.glProgramUniform3uiv(progDst, dstLocation, 1, uiv); break; + case eGL_UNSIGNED_INT_VEC4: + case eGL_BOOL_VEC4: GL.glProgramUniform4uiv(progDst, dstLocation, 1, uiv); break; case eGL_IMAGE_1D: case eGL_IMAGE_2D: @@ -703,8 +1151,8 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin case eGL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE: case eGL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY: case eGL_UNSIGNED_INT_ATOMIC_COUNTER: - if(IsGLES) - // Image uniforms cannot be re-assigned in GLES. + if(IsGLES || IsDstProgramSPIRV) + // Image uniforms cannot be re-assigned in GLES or with SPIR-V programs. break; // deliberate fall-through // treat all samplers as just an int (since they just store their binding value) @@ -741,41 +1189,37 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin case eGL_UNSIGNED_INT_SAMPLER_2D: case eGL_UNSIGNED_INT_SAMPLER_3D: case eGL_UNSIGNED_INT_SAMPLER_CUBE: + case eGL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_1D_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_2D_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: case eGL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: case eGL_UNSIGNED_INT_SAMPLER_BUFFER: case eGL_UNSIGNED_INT_SAMPLER_2D_RECT: - case eGL_INT: GL.glProgramUniform1iv(progDst, dstLocation, 1, iv); break; - case eGL_INT_VEC2: GL.glProgramUniform2iv(progDst, dstLocation, 1, iv); break; - case eGL_INT_VEC3: GL.glProgramUniform3iv(progDst, dstLocation, 1, iv); break; - case eGL_INT_VEC4: GL.glProgramUniform4iv(progDst, dstLocation, 1, iv); break; - case eGL_UNSIGNED_INT: - case eGL_BOOL: GL.glProgramUniform1uiv(progDst, dstLocation, 1, uiv); break; - case eGL_UNSIGNED_INT_VEC2: - case eGL_BOOL_VEC2: GL.glProgramUniform2uiv(progDst, dstLocation, 1, uiv); break; - case eGL_UNSIGNED_INT_VEC3: - case eGL_BOOL_VEC3: GL.glProgramUniform3uiv(progDst, dstLocation, 1, uiv); break; - case eGL_UNSIGNED_INT_VEC4: - case eGL_BOOL_VEC4: GL.glProgramUniform4uiv(progDst, dstLocation, 1, uiv); break; + case eGL_INT: + if(!IsDstProgramSPIRV) // SPIR-V shaders treat samplers as immutable + GL.glProgramUniform1iv(progDst, dstLocation, 1, iv); + break; default: RDCERR("Unhandled uniform type '%s'", ToStr(val.Type).c_str()); } } } - // apply UBO bindings - for(const ProgramBinding &bind : serialisedUniforms.UBOBindings) + if(!IsDstProgramSPIRV) { - GLuint idx = GL.glGetUniformBlockIndex(progDst, bind.Name.c_str()); - if(idx != GL_INVALID_INDEX) - GL.glUniformBlockBinding(progDst, idx, bind.Binding); + // apply UBO bindings + for(const ProgramBinding &bind : serialisedUniforms.UBOBindings) + { + GLuint idx = GL.glGetUniformBlockIndex(progDst, bind.Name.c_str()); + if(idx != GL_INVALID_INDEX) + GL.glUniformBlockBinding(progDst, idx, bind.Binding); + } } // apply SSBO bindings // GLES does not allow modification of SSBO bindings - which is good as we don't need to restore // them, since they're immutable. - if(!IsGLES) + if(!IsDstProgramSPIRV && !IsGLES) { for(const ProgramBinding &bind : serialisedUniforms.SSBOBindings) { @@ -797,30 +1241,40 @@ static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuin } } -void CopyProgramUniforms(GLuint progSrc, GLuint progDst) +void CopyProgramUniforms(const PerStageReflections &srcStages, GLuint progSrc, + const PerStageReflections &dstStages, GLuint progDst) { const bool CopyUniforms = true; const bool SerialiseUniforms = false; ForAllProgramUniforms( - NULL, CaptureState::ActiveReplaying, progSrc, progDst, NULL); + NULL, CaptureState::ActiveReplaying, srcStages, progSrc, dstStages, progDst, NULL); } template -void SerialiseProgramUniforms(SerialiserType &ser, CaptureState state, GLuint prog, +void SerialiseProgramUniforms(SerialiserType &ser, CaptureState state, + const PerStageReflections &stages, GLuint prog, std::map *locTranslate) { const bool CopyUniforms = false; const bool SerialiseUniforms = true; - ForAllProgramUniforms(&ser, state, prog, prog, locTranslate); + ForAllProgramUniforms(&ser, state, stages, prog, stages, prog, + locTranslate); } -template void SerialiseProgramUniforms(ReadSerialiser &ser, CaptureState state, GLuint prog, +template void SerialiseProgramUniforms(ReadSerialiser &ser, CaptureState state, + const PerStageReflections &stages, GLuint prog, std::map *locTranslate); -template void SerialiseProgramUniforms(WriteSerialiser &ser, CaptureState state, GLuint prog, +template void SerialiseProgramUniforms(WriteSerialiser &ser, CaptureState state, + const PerStageReflections &stages, GLuint prog, std::map *locTranslate); -void CopyProgramAttribBindings(GLuint progsrc, GLuint progdst, ShaderReflection *refl) +bool CopyProgramAttribBindings(GLuint progsrc, GLuint progdst, ShaderReflection *refl) { + // don't try to copy bindings for SPIR-V shaders. The queries by name may fail, and the bindings + // are immutable anyway + if(refl->encoding == ShaderEncoding::SPIRV) + return false; + // copy over attrib bindings for(const SigParameter &sig : refl->inputSignature) { @@ -832,10 +1286,17 @@ void CopyProgramAttribBindings(GLuint progsrc, GLuint progdst, ShaderReflection if(idx >= 0) GL.glBindAttribLocation(progdst, (GLuint)idx, sig.varName.c_str()); } + + return !refl->inputSignature.empty(); } -void CopyProgramFragDataBindings(GLuint progsrc, GLuint progdst, ShaderReflection *refl) +bool CopyProgramFragDataBindings(GLuint progsrc, GLuint progdst, ShaderReflection *refl) { + // don't try to copy bindings for SPIR-V shaders. The queries by name may fail, and the bindings + // are immutable anyway + if(refl->encoding == ShaderEncoding::SPIRV) + return false; + uint64_t used = 0; // copy over fragdata bindings @@ -874,15 +1335,25 @@ void CopyProgramFragDataBindings(GLuint progsrc, GLuint progdst, ShaderReflectio } } } + + return !refl->outputSignature.empty(); } template -void SerialiseProgramBindings(SerialiserType &ser, CaptureState state, GLuint prog) +bool SerialiseProgramBindings(SerialiserType &ser, CaptureState state, + const PerStageReflections &stages, GLuint prog) { std::vector InputBindings; std::vector OutputBindings; - if(ser.IsWriting()) + // technically we can completely skip this if the shaders are SPIR-V, but for compatibility we + // instead just skip the fetch & apply steps, so that we can still serialise in a backwards + // compatible way. + bool IsProgramSPIRV = false; + for(size_t i = 0; i < 6; i++) + IsProgramSPIRV |= stages.refls[i] && stages.refls[i]->encoding == ShaderEncoding::SPIRV; + + if(ser.IsWriting() && !IsProgramSPIRV) { char buf[128] = {}; @@ -915,7 +1386,7 @@ void SerialiseProgramBindings(SerialiserType &ser, CaptureState state, GLuint pr SERIALISE_ELEMENT(InputBindings); SERIALISE_ELEMENT(OutputBindings); - if(ser.IsReading() && IsReplayMode(state)) + if(ser.IsReading() && IsReplayMode(state) && !IsProgramSPIRV) { for(int sigType = 0; sigType < 2; sigType++) { @@ -963,7 +1434,11 @@ void SerialiseProgramBindings(SerialiserType &ser, CaptureState state, GLuint pr } } } + + return !IsProgramSPIRV && (!InputBindings.empty() || !OutputBindings.empty()); } -template void SerialiseProgramBindings(ReadSerialiser &ser, CaptureState state, GLuint prog); -template void SerialiseProgramBindings(WriteSerialiser &ser, CaptureState state, GLuint prog); +template bool SerialiseProgramBindings(ReadSerialiser &ser, CaptureState state, + const PerStageReflections &stages, GLuint prog); +template bool SerialiseProgramBindings(WriteSerialiser &ser, CaptureState state, + const PerStageReflections &stages, GLuint prog); diff --git a/renderdoc/driver/gl/gl_replay.h b/renderdoc/driver/gl/gl_replay.h index 7cac26080..cc1e70bb2 100644 --- a/renderdoc/driver/gl/gl_replay.h +++ b/renderdoc/driver/gl/gl_replay.h @@ -240,7 +240,8 @@ private: CompType typeHint, bool stencil, float *minval, float *maxval); void CreateCustomShaderTex(uint32_t w, uint32_t h); - void CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint fragShader); + bool CreateOverlayProgram(GLuint Program, GLuint Pipeline, GLuint fragShader, + GLuint fragShaderSPIRV); void CopyArrayToTex2DMS(GLuint destMS, GLuint srcArray, GLint width, GLint height, GLint arraySize, GLint samples, GLenum intFormat, uint32_t selectedSlice); diff --git a/renderdoc/driver/gl/wrappers/gl_shader_funcs.cpp b/renderdoc/driver/gl/wrappers/gl_shader_funcs.cpp index 924a609bd..d8fbf1e01 100644 --- a/renderdoc/driver/gl/wrappers/gl_shader_funcs.cpp +++ b/renderdoc/driver/gl/wrappers/gl_shader_funcs.cpp @@ -67,9 +67,6 @@ void WrappedOpenGL::ShaderData::ProcessSPIRVCompilation(WrappedOpenGL &drv, Reso reflection.encoding = ShaderEncoding::SPIRV; reflection.rawBytes.assign((byte *)spirv.spirv.data(), spirv.spirv.size() * sizeof(uint32_t)); - // we discard this too, because we don't need it - we don't do any SPIR-V patching in GL - SPIRVPatchData patchData; - spirv.MakeReflection(GraphicsAPI::OpenGL, ShaderStage(ShaderIdx(type)), pEntryPoint, reflection, mapping, patchData); @@ -568,9 +565,6 @@ void WrappedOpenGL::glAttachShader(GLuint program, GLuint shader) } } - // if we're capturing and don't have ARB_program_interface_query we're going to have to emulate - // it using glslang for compilation and reflection - if(IsReplayMode(m_State) || !HasExt[ARB_program_interface_query]) { ResourceId progid = GetResourceManager()->GetID(ProgramRes(GetCtx(), program)); ResourceId shadid = GetResourceManager()->GetID(ShaderRes(GetCtx(), shader)); @@ -640,9 +634,6 @@ void WrappedOpenGL::glDetachShader(GLuint program, GLuint shader) } } - // if we're capturing and don't have ARB_program_interface_query we're going to have to emulate - // it using glslang for compilation and reflection - if(IsReplayMode(m_State) || !HasExt[ARB_program_interface_query]) { ResourceId progid = GetResourceManager()->GetID(ProgramRes(GetCtx(), program)); ResourceId shadid = GetResourceManager()->GetID(ShaderRes(GetCtx(), shader)); @@ -1350,6 +1341,16 @@ void WrappedOpenGL::glShaderBinary(GLsizei count, const GLuint *shaders, GLenum if(IsReplayMode(m_State)) { GL.glShaderBinary(count, shaders, binaryformat, binary, length); + + if(binaryformat == eGL_SHADER_BINARY_FORMAT_SPIR_V) + { + for(GLsizei i = 0; i < count; i++) + { + ResourceId liveId = GetResourceManager()->GetID(ShaderRes(GetCtx(), shaders[i])); + m_Shaders[liveId].spirvWords.assign((uint32_t *)binary, + (uint32_t *)((byte *)binary + length)); + } + } } else if(IsCaptureMode(m_State) && binaryformat == eGL_SHADER_BINARY_FORMAT_SPIR_V) { @@ -1368,6 +1369,9 @@ void WrappedOpenGL::glShaderBinary(GLsizei count, const GLuint *shaders, GLenum Serialise_glShaderBinary(ser, 1, shaders + i, binaryformat, binary, length); record->AddChunk(scope.Get()); + + m_Shaders[record->GetResourceID()].spirvWords.assign((uint32_t *)binary, + (uint32_t *)((byte *)binary + length)); } } } @@ -1973,8 +1977,27 @@ void WrappedOpenGL::glSpecializeShader(GLuint shader, const GLchar *pEntryPoint, pConstantIndex, pConstantValue); record->AddChunk(scope.Get()); + + ResourceId id = record->GetResourceID(); + + ParseSPIRV(m_Shaders[id].spirvWords.data(), m_Shaders[id].spirvWords.size(), + m_Shaders[id].spirv); + + m_Shaders[id].ProcessSPIRVCompilation( + *this, id, shader, pEntryPoint, numSpecializationConstants, pConstantIndex, pConstantValue); } } + else + { + ResourceId liveId = GetResourceManager()->GetID(ShaderRes(GetCtx(), shader)); + + ParseSPIRV(m_Shaders[liveId].spirvWords.data(), m_Shaders[liveId].spirvWords.size(), + m_Shaders[liveId].spirv); + + m_Shaders[liveId].ProcessSPIRVCompilation(*this, liveId, shader, pEntryPoint, + numSpecializationConstants, pConstantIndex, + pConstantValue); + } } INSTANTIATE_FUNCTION_SERIALISED(void, glCreateShader, GLenum type, GLuint shader); diff --git a/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp b/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp index 4a18c6bd9..ed989e2bd 100644 --- a/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp @@ -4712,6 +4712,7 @@ void SPVModule::MakeReflection(GraphicsAPI sourceAPI, ShaderStage stage, { globalsblock.name = "$Globals"; globalsblock.bufferBacked = false; + globalsblock.byteSize = (uint32_t)globalsblock.variables.size(); globalsblock.bindPoint = (int)cblocks.size(); Bindpoint bindmap; diff --git a/util/test/demos/gl/gl_spirv_shader.cpp b/util/test/demos/gl/gl_spirv_shader.cpp index 679fa6484..b2016bfed 100644 --- a/util/test/demos/gl/gl_spirv_shader.cpp +++ b/util/test/demos/gl/gl_spirv_shader.cpp @@ -41,7 +41,7 @@ layout(location = 2) out vec2 oUV; layout(location = 2) uniform vec4 offset; layout(location = 8) uniform vec4 scale; -layout(location = 13) uniform vec4 UVscroll; +layout(location = 13) uniform vec2 UVscroll; void main() { @@ -221,6 +221,21 @@ void main() float col[] = {0.4f, 0.5f, 0.6f, 1.0f}; glClearBufferfv(GL_COLOR, 0, col); + GLsizei w = GLsizei(screenWidth) >> 1; + GLsizei h = GLsizei(screenHeight) >> 1; + + glBindVertexArray(vao); + + glViewport(0, 0, w, h); + + glUseProgram(glslprogram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(w, 0, w, h); + + glUseProgram(spirvprogram); + glDrawArrays(GL_TRIANGLES, 0, 3); + vsdata.UVscroll.x += 0.01f; vsdata.UVscroll.y += 0.02f; @@ -235,20 +250,18 @@ void main() // UVscroll location 13 glUniform4fv(2, 1, &vsdata.offset.x); glUniform4fv(8, 1, &vsdata.scale.x); - glUniform4fv(13, 1, &vsdata.UVscroll.x); + glUniform2fv(13, 1, &vsdata.UVscroll.x); // tint location 7 glUniform4fv(7, 1, &fsdata.x); } - glBindVertexArray(vao); - - glViewport(0, 0, GLsizei(screenWidth) >> 1, GLsizei(screenHeight)); + glViewport(0, h, w, h); glUseProgram(glslprogram); glDrawArrays(GL_TRIANGLES, 0, 3); - glViewport(GLsizei(screenWidth) >> 1, 0, GLsizei(screenWidth) >> 1, GLsizei(screenHeight)); + glViewport(w, h, w, h); glUseProgram(spirvprogram); glDrawArrays(GL_TRIANGLES, 0, 3);