From f09c40b8c9d7031f76143a68bf643b74fa469023 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 7 Jun 2019 18:53:02 +0100 Subject: [PATCH] Fix broken cases with GL_ARB_gl_spirv. Closes #1405 * We can't rely on the driver's reflection, since name information might be stripped and the driver is within its rights to not reflect anything even if we have names. Similarly queries by names etc will not work. * Also we can't try to change bindings that are immutable on SPIR-V shaders - UBO/SSBO bindings, transform feedback varyings, attrib and fragdata locations. * Programs can't mix and match SPIR-V and GLSL, so when including our own shaders in the overlay program make sure they match what the user's shaders are. * For mesh output, we need to patch the SPIR-V if it's a SPIR-V shader instead of trying to use the GL XFB varyings set. --- renderdoc/api/replay/shader_types.h | 2 +- renderdoc/driver/gl/gl_common.h | 19 +- renderdoc/driver/gl/gl_driver.cpp | 8 +- renderdoc/driver/gl/gl_driver.h | 20 + renderdoc/driver/gl/gl_initstate.cpp | 63 +- renderdoc/driver/gl/gl_overlay.cpp | 120 ++- renderdoc/driver/gl/gl_postvs.cpp | 715 +++++++++++------- renderdoc/driver/gl/gl_program_iterate.cpp | 563 ++++++++++++-- renderdoc/driver/gl/gl_replay.h | 3 +- .../driver/gl/wrappers/gl_shader_funcs.cpp | 41 +- .../shaders/spirv/spirv_disassemble.cpp | 1 + util/test/demos/gl/gl_spirv_shader.cpp | 25 +- 12 files changed, 1204 insertions(+), 376 deletions(-) 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);