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.
This commit is contained in:
baldurk
2019-06-07 18:53:02 +01:00
parent 48d854365d
commit f09c40b8c9
12 changed files with 1204 additions and 376 deletions
+1 -1
View File
@@ -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`.
+14 -5
View File
@@ -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 <typename SerialiserType>
void SerialiseProgramUniforms(SerialiserType &ser, CaptureState state, GLuint prog,
void SerialiseProgramUniforms(SerialiserType &ser, CaptureState state,
const PerStageReflections &stages, GLuint prog,
std::map<GLint, GLint> *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 <typename SerialiserType>
void SerialiseProgramBindings(SerialiserType &ser, CaptureState state, GLuint prog);
bool SerialiseProgramBindings(SerialiserType &ser, CaptureState state,
const PerStageReflections &stages, GLuint prog);
struct DrawElementsIndirectCommand
{
+7 -1
View File
@@ -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);
+20
View File
@@ -621,6 +621,7 @@ public:
// used for if the application actually uploaded SPIR-V
std::vector<uint32_t> spirvWords;
SPIRVPatchData patchData;
// the parameters passed to glSpecializeShader
std::string entryPoint;
@@ -680,6 +681,25 @@ public:
std::map<ResourceId, ProgramData> m_Programs;
std::map<ResourceId, PipelineData> 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()
+46 -17
View File
@@ -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<GLint, GLint> *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)
{
+91 -29
View File
@@ -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<const char *> 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,
+452 -263
View File
@@ -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<const char *> 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<const char *> 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<std::string> matrixVaryings; // matrices need some fixup
std::vector<const char *> 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<uint32_t> 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<std::string> matrixVaryings;
std::vector<const char *> 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<uint32_t> 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<std::string> matrixVaryings;
std::vector<const char *> 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);
+519 -44
View File
@@ -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<UnrolledSPIRVConstant> &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<UnrolledSPIRVConstant> &unrolled, const ShaderConstant &var)
{
uint32_t location;
UnrollConstant(unrolled, var, rdcstr(), location);
}
static void UnrollConstants(const PerStageReflections &stages,
rdcarray<UnrolledSPIRVConstant> &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 <typename SerialiserType>
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 <const bool CopyUniforms, const bool SerialiseUniforms, typename SerialiserType>
static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state, GLuint progSrc,
GLuint progDst, std::map<GLint, GLint> *locTranslate)
static void ForAllProgramUniforms(SerialiserType *ser, CaptureState state,
const PerStageReflections &srcStages, GLuint progSrc,
const PerStageReflections &dstStages, GLuint progDst,
std::map<GLint, GLint> *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<UnrolledSPIRVConstant> 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<UnrolledSPIRVConstant> 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<CopyUniforms, SerialiseUniforms, ReadSerialiser>(
NULL, CaptureState::ActiveReplaying, progSrc, progDst, NULL);
NULL, CaptureState::ActiveReplaying, srcStages, progSrc, dstStages, progDst, NULL);
}
template <typename SerialiserType>
void SerialiseProgramUniforms(SerialiserType &ser, CaptureState state, GLuint prog,
void SerialiseProgramUniforms(SerialiserType &ser, CaptureState state,
const PerStageReflections &stages, GLuint prog,
std::map<GLint, GLint> *locTranslate)
{
const bool CopyUniforms = false;
const bool SerialiseUniforms = true;
ForAllProgramUniforms<CopyUniforms, SerialiseUniforms>(&ser, state, prog, prog, locTranslate);
ForAllProgramUniforms<CopyUniforms, SerialiseUniforms>(&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<GLint, GLint> *locTranslate);
template void SerialiseProgramUniforms(WriteSerialiser &ser, CaptureState state, GLuint prog,
template void SerialiseProgramUniforms(WriteSerialiser &ser, CaptureState state,
const PerStageReflections &stages, GLuint prog,
std::map<GLint, GLint> *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 <typename SerialiserType>
void SerialiseProgramBindings(SerialiserType &ser, CaptureState state, GLuint prog)
bool SerialiseProgramBindings(SerialiserType &ser, CaptureState state,
const PerStageReflections &stages, GLuint prog)
{
std::vector<ProgramBinding> InputBindings;
std::vector<ProgramBinding> 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);
+2 -1
View File
@@ -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);
@@ -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);
@@ -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;
+19 -6
View File
@@ -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);