Completely emulate ARB_program_interface_query using glslang

* We already did this partially, enough for capturing, but now we also do it on
  replay.
This commit is contained in:
baldurk
2019-02-18 17:23:20 +00:00
parent 2ea6174c83
commit 3aa64e2e8e
13 changed files with 1895 additions and 293 deletions
+2 -3
View File
@@ -115,9 +115,6 @@ bool CheckReplayContext()
// we require the below extensions on top of a 3.2 context. Some of these we could in theory
// do without, but support for them is so widespread it's not worthwhile
// for program introspection, needed for shader reflection.
// Possible to remove by compiling shaders to SPIR-V and reflecting ourselves.
REQUIRE_EXTENSION(ARB_program_interface_query);
// needed for program pipelines, glProgramUniform*, and reflecting shaders on their own
// Possible to remove this with self-compiled SPIR-V for reflection - see above. Likewise
// convenience for our own pipelines when replacing single shaders or such.
@@ -2594,6 +2591,8 @@ TEST_CASE("GL formats", "[format][gl]")
CHECK(size == GetByteSize(123, 456, 1, GetBaseFormat(f), GetDataType(f)));
}
};
GL = GLDispatchTable();
};
#endif // ENABLED(ENABLE_UNIT_TESTS)
+26
View File
@@ -293,6 +293,32 @@ struct GLPlatform
virtual void *GetReplayFunction(const char *funcname) = 0;
};
class GLDummyPlatform : public GLPlatform
{
virtual GLWindowingData CloneTemporaryContext(GLWindowingData share) { return GLWindowingData(); }
virtual void DeleteClonedContext(GLWindowingData context) {}
virtual void DeleteReplayContext(GLWindowingData context) {}
virtual bool MakeContextCurrent(GLWindowingData data) { return true; }
virtual void SwapBuffers(GLWindowingData context) {}
virtual void WindowResized(GLWindowingData context) {}
virtual void GetOutputWindowDimensions(GLWindowingData context, int32_t &w, int32_t &h) {}
virtual bool IsOutputWindowVisible(GLWindowingData context) { return false; }
virtual GLWindowingData MakeOutputWindow(WindowingData window, bool depth,
GLWindowingData share_context)
{
return GLWindowingData();
}
virtual void DrawQuads(float width, float height, const std::vector<Vec4f> &vertices) {}
virtual void *GetReplayFunction(const char *funcname) { return NULL; }
// for initialisation at replay time
virtual bool CanCreateGLESContext() { return true; }
virtual bool PopulateForReplay() { return true; }
virtual ReplayStatus InitialiseAPI(GLWindowingData &replayContext, RDCDriver api)
{
return ReplayStatus::Succeeded;
}
};
struct GLVersion
{
int major;
+5
View File
@@ -288,6 +288,9 @@ void GLReplay::InitDebugData()
if(m_pDriver == NULL)
return;
// don't reflect any shaders or programs we make
m_pDriver->PushInternalShader();
m_HighlightCache.driver = m_pDriver->GetReplay();
RenderDoc::Inst().SetProgress(LoadProgress::DebugManagerInit, 0.0f);
@@ -1048,6 +1051,8 @@ void GLReplay::InitDebugData()
"Don't have shader image load/store or compute shaders, functionality will be degraded.");
m_Degraded = true;
}
m_pDriver->PopInternalShader();
}
void GLReplay::DeleteDebugData()
+5
View File
@@ -140,6 +140,8 @@ private:
uintptr_t m_ShareGroupID;
uint32_t m_InternalShader = 0;
std::vector<GLWindowingData> m_LastContexts;
std::set<void *> m_AcceptedCtx;
@@ -497,6 +499,9 @@ public:
ContextPair &GetCtx();
GLResourceRecord *GetContextRecord();
void PushInternalShader() { m_InternalShader++; }
void PopInternalShader() { m_InternalShader--; }
bool IsInternalShader() { return m_InternalShader > 0; }
void *ShareCtx(void *ctx) { return ctx ? m_ContextData[ctx].shareGroup : NULL; }
void SetStructuredExport(uint64_t sectionVersion)
{
+26 -24
View File
@@ -1254,6 +1254,8 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r
}
else if(Type == eResProgram)
{
WrappedOpenGL &drv = *m_Driver;
GLuint bindingsProgram = 0, uniformsProgram = 0;
std::map<GLint, GLint> *translationTable = NULL;
@@ -1261,7 +1263,7 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r
{
WrappedOpenGL::ProgramData &details = m_Driver->m_Programs[GetLiveID(Id)];
GLuint initProg = GL.glCreateProgram();
GLuint initProg = drv.glCreateProgram();
uint32_t numShaders = 0;
@@ -1275,7 +1277,7 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r
const auto &shadDetails = m_Driver->m_Shaders[details.stageShaders[i]];
GLuint shad = GL.glCreateShader(shadDetails.type);
GLuint shad = drv.glCreateShader(shadDetails.type);
if(shadDetails.type == eGL_VERTEX_SHADER)
{
@@ -1301,24 +1303,24 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r
char **srcs = new char *[shadDetails.sources.size()];
for(size_t s = 0; s < shadDetails.sources.size(); s++)
srcs[s] = (char *)shadDetails.sources[s].c_str();
GL.glShaderSource(shad, (GLsizei)shadDetails.sources.size(), srcs, NULL);
drv.glShaderSource(shad, (GLsizei)shadDetails.sources.size(), srcs, NULL);
SAFE_DELETE_ARRAY(srcs);
GL.glCompileShader(shad);
GL.glAttachShader(initProg, shad);
GL.glDeleteShader(shad);
drv.glCompileShader(shad);
drv.glAttachShader(initProg, shad);
drv.glDeleteShader(shad);
}
else if(!shadDetails.spirvWords.empty())
{
GL.glShaderBinary(1, &shad, eGL_SHADER_BINARY_FORMAT_SPIR_V, shadDetails.spirvWords.data(),
(GLsizei)shadDetails.spirvWords.size() * sizeof(uint32_t));
drv.glShaderBinary(1, &shad, eGL_SHADER_BINARY_FORMAT_SPIR_V, shadDetails.spirvWords.data(),
(GLsizei)shadDetails.spirvWords.size() * sizeof(uint32_t));
GL.glSpecializeShader(shad, shadDetails.entryPoint.c_str(),
(GLuint)shadDetails.specIDs.size(), shadDetails.specIDs.data(),
shadDetails.specValues.data());
drv.glSpecializeShader(shad, shadDetails.entryPoint.c_str(),
(GLuint)shadDetails.specIDs.size(), shadDetails.specIDs.data(),
shadDetails.specValues.data());
GL.glAttachShader(initProg, shad);
GL.glDeleteShader(shad);
drv.glAttachShader(initProg, shad);
drv.glDeleteShader(shad);
}
else
{
@@ -1335,21 +1337,21 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r
vertexOutputsPtr.resize(vertexOutputs.size());
for(size_t i = 0; i < vertexOutputs.size(); i++)
vertexOutputsPtr[i] = vertexOutputs[i].c_str();
GL.glTransformFeedbackVaryings(initProg, (GLsizei)vertexOutputsPtr.size(),
&vertexOutputsPtr[0], eGL_INTERLEAVED_ATTRIBS);
GL.glLinkProgram(initProg);
drv.glTransformFeedbackVaryings(initProg, (GLsizei)vertexOutputsPtr.size(),
&vertexOutputsPtr[0], eGL_INTERLEAVED_ATTRIBS);
drv.glLinkProgram(initProg);
GLint status = 0;
GL.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
drv.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
// if it failed to link, first remove the varyings hack above as maybe the driver is barfing
// on trying to make some output a varying
if(status == 0)
{
GL.glTransformFeedbackVaryings(initProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS);
GL.glLinkProgram(initProg);
drv.glTransformFeedbackVaryings(initProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS);
drv.glLinkProgram(initProg);
GL.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
drv.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
}
// if it failed to link, try again as a separable program.
@@ -1357,10 +1359,10 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r
// shaders need fixup to be separable-compatible.
if(status == 0)
{
GL.glProgramParameteri(initProg, eGL_PROGRAM_SEPARABLE, 1);
GL.glLinkProgram(initProg);
drv.glProgramParameteri(initProg, eGL_PROGRAM_SEPARABLE, 1);
drv.glLinkProgram(initProg);
GL.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
drv.glGetProgramiv(initProg, eGL_LINK_STATUS, &status);
}
if(status == 0)
@@ -1372,7 +1374,7 @@ bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId r
else
{
char buffer[1025] = {0};
GL.glGetProgramInfoLog(initProg, 1024, NULL, buffer);
drv.glGetProgramInfoLog(initProg, 1024, NULL, buffer);
RDCERR("Link error: %s", buffer);
}
}
-26
View File
@@ -3491,32 +3491,6 @@ ReplayStatus CreateReplayDevice(RDCDriver rdcdriver, RDCFile *rdc, GLPlatform &p
return ReplayStatus::Succeeded;
}
class GLDummyPlatform : public GLPlatform
{
virtual GLWindowingData CloneTemporaryContext(GLWindowingData share) { return GLWindowingData(); }
virtual void DeleteClonedContext(GLWindowingData context) {}
virtual void DeleteReplayContext(GLWindowingData context) {}
virtual bool MakeContextCurrent(GLWindowingData data) { return true; }
virtual void SwapBuffers(GLWindowingData context) {}
virtual void WindowResized(GLWindowingData context) {}
virtual void GetOutputWindowDimensions(GLWindowingData context, int32_t &w, int32_t &h) {}
virtual bool IsOutputWindowVisible(GLWindowingData context) { return false; }
virtual GLWindowingData MakeOutputWindow(WindowingData window, bool depth,
GLWindowingData share_context)
{
return GLWindowingData();
}
virtual void DrawQuads(float width, float height, const std::vector<Vec4f> &vertices) {}
virtual void *GetReplayFunction(const char *funcname) { return NULL; }
// for initialisation at replay time
virtual bool CanCreateGLESContext() { return true; }
virtual bool PopulateForReplay() { return true; }
virtual ReplayStatus InitialiseAPI(GLWindowingData &replayContext, RDCDriver api)
{
return ReplayStatus::Succeeded;
}
};
void GL_ProcessStructured(RDCFile *rdc, SDFile &output)
{
GLDummyPlatform dummy;
+178 -87
View File
@@ -28,6 +28,41 @@
#include "3rdparty/glslang/glslang/Public/ShaderLang.h"
#include "gl_driver.h"
template <>
std::string DoStringise(const FFVertexOutput &el)
{
BEGIN_ENUM_STRINGISE(FFVertexOutput);
{
STRINGISE_ENUM_CLASS_NAMED(PointSize, "gl_PointSize");
STRINGISE_ENUM_CLASS_NAMED(ClipDistance, "gl_ClipDistance");
STRINGISE_ENUM_CLASS_NAMED(ClipVertex, "gl_ClipVertex");
STRINGISE_ENUM_CLASS_NAMED(FrontColor, "gl_FrontColor");
STRINGISE_ENUM_CLASS_NAMED(BackColor, "gl_BackColor");
STRINGISE_ENUM_CLASS_NAMED(FrontSecondaryColor, "gl_FrontSecondaryColor");
STRINGISE_ENUM_CLASS_NAMED(BackSecondaryColor, "gl_BackSecondaryColor");
STRINGISE_ENUM_CLASS_NAMED(TexCoord, "gl_TexCoord");
STRINGISE_ENUM_CLASS_NAMED(FogFragCoord, "gl_FogFragCoord");
STRINGISE_ENUM_CLASS_NAMED(Count, "gl_Count");
}
END_ENUM_STRINGISE();
}
void namesort(rdcarray<ShaderConstant> &vars)
{
if(vars.empty())
return;
struct name_sort
{
bool operator()(const ShaderConstant &a, const ShaderConstant &b) { return a.name < b.name; }
};
std::sort(vars.begin(), vars.end(), name_sort());
for(size_t i = 0; i < vars.size(); i++)
namesort(vars[i].type.members);
}
void sort(rdcarray<ShaderConstant> &vars)
{
if(vars.empty())
@@ -41,64 +76,42 @@ void sort(rdcarray<ShaderConstant> &vars)
sort(vars[i].type.members);
}
void CheckVertexOutputUses(const vector<string> &sources, bool &pointSizeUsed, bool &clipDistanceUsed)
void CheckVertexOutputUses(const std::vector<std::string> &sources,
FixedFunctionVertexOutputs &outputUsage)
{
pointSizeUsed = false;
clipDistanceUsed = false;
outputUsage = FixedFunctionVertexOutputs();
for(size_t i = 0; i < sources.size(); i++)
for(FFVertexOutput output : values<FFVertexOutput>())
{
const string &s = sources[i];
// we consider an output used if we encounter a '=' before either a ';' or the end of the string
std::string name = ToStr(output);
size_t offs = 0;
for(;;)
for(size_t i = 0; i < sources.size(); i++)
{
offs = s.find("gl_PointSize", offs);
const std::string &s = sources[i];
if(offs == string::npos)
break;
size_t offs = 0;
// consider gl_PointSize used if we encounter a '=' before a ';' or the end of the string
while(offs < s.length())
for(;;)
{
if(s[offs] == '=')
offs = s.find(name, offs);
if(offs == string::npos)
break;
while(offs < s.length())
{
pointSizeUsed = true;
break;
if(s[offs] == '=')
{
outputUsage.used[(int)output] = true;
break;
}
if(s[offs] == ';')
break;
offs++;
}
if(s[offs] == ';')
break;
offs++;
}
}
offs = 0;
for(;;)
{
offs = s.find("gl_ClipDistance", offs);
if(offs == string::npos)
break;
// consider gl_ClipDistance used if we encounter a '=' before a ';' or the end of the string
while(offs < s.length())
{
if(s[offs] == '=')
{
clipDistanceUsed = true;
break;
}
if(s[offs] == ';')
break;
offs++;
}
}
}
@@ -116,35 +129,35 @@ static GLuint CreateSepProgram(WrappedOpenGL &driver, GLenum type, GLsizei numSo
GLuint program = 0;
// definition of glCreateShaderProgramv from the spec
GLuint shader = GL.glCreateShader(type);
GLuint shader = driver.glCreateShader(type);
if(shader)
{
GL.glShaderSource(shader, numSources, sources, NULL);
driver.glShaderSource(shader, numSources, sources, NULL);
if(paths == NULL)
GL.glCompileShader(shader);
driver.glCompileShader(shader);
else
GL.glCompileShaderIncludeARB(shader, numPaths, paths, NULL);
driver.glCompileShaderIncludeARB(shader, numPaths, paths, NULL);
program = GL.glCreateProgram();
program = driver.glCreateProgram();
if(program)
{
GLint compiled = 0;
GL.glGetShaderiv(shader, eGL_COMPILE_STATUS, &compiled);
GL.glProgramParameteri(program, eGL_PROGRAM_SEPARABLE, GL_TRUE);
driver.glGetShaderiv(shader, eGL_COMPILE_STATUS, &compiled);
driver.glProgramParameteri(program, eGL_PROGRAM_SEPARABLE, GL_TRUE);
if(compiled)
{
GL.glAttachShader(program, shader);
GL.glLinkProgram(program);
driver.glAttachShader(program, shader);
driver.glLinkProgram(program);
// we deliberately leave the shaders attached so this program can be re-linked.
// they will be cleaned up when the program is deleted
// driver.glDetachShader(program, shader);
}
}
GL.glDeleteShader(shader);
driver.glDeleteShader(shader);
}
driver.SuppressDebugMessages(false);
@@ -1037,8 +1050,50 @@ int ParseVersionStatement(const char *version)
return ret;
}
static void AddSigParameter(vector<SigParameter> &sigs, uint32_t &regIndex, const SigParameter &sig,
const char *nm, int rows, int arrayIdx)
{
if(rows == 1)
{
SigParameter s = sig;
if(s.regIndex == ~0U)
s.regIndex = regIndex++;
if(arrayIdx >= 0)
{
s.arrayIndex = arrayIdx;
s.varName = StringFormat::Fmt("%s[%d]", nm, arrayIdx);
}
sigs.push_back(s);
}
else
{
for(int r = 0; r < rows; r++)
{
SigParameter s = sig;
if(s.regIndex == ~0U)
s.regIndex = regIndex++;
if(arrayIdx >= 0)
{
s.arrayIndex = arrayIdx;
s.varName = StringFormat::Fmt("%s[%d]:row%d", nm, arrayIdx, r);
}
else
{
s.varName = StringFormat::Fmt("%s:row%d", nm, r);
}
sigs.push_back(s);
}
}
}
void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &refl,
bool pointSizeUsed, bool clipDistanceUsed)
const FixedFunctionVertexOutputs &outputUsage)
{
if(shadType == eGL_COMPUTE_SHADER)
{
@@ -1616,7 +1671,7 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref
res.resType = TextureType::Buffer;
res.variableType.descriptor.rows = 0;
res.variableType.descriptor.columns = 0;
res.variableType.descriptor.elements = len;
res.variableType.descriptor.elements = 0;
res.variableType.descriptor.rowMajorStorage = false;
res.variableType.descriptor.arrayByteStride = 0;
res.variableType.descriptor.matrixByteStride = 0;
@@ -1625,13 +1680,15 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref
res.bindPoint = (int32_t)rwresources.size();
res.name = nm;
GLint numMembers = 0;
propName = eGL_NUM_ACTIVE_VARIABLES;
GL.glGetProgramResourceiv(sepProg, eGL_SHADER_STORAGE_BLOCK, u, 1, &propName, 1, NULL,
(GLint *)&res.variableType.descriptor.elements);
(GLint *)&numMembers);
rwresources.push_back(res);
ssbos.push_back(res.bindPoint);
ssboMembers += res.variableType.descriptor.elements;
ssboMembers += numMembers;
delete[] nm;
}
@@ -1739,7 +1796,9 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref
globals.bufferBacked = false;
globals.bindPoint = (int32_t)refl.constantBlocks.size();
sort(globalUniforms);
// global uniforms have no defined order, location will be per implementation, so sort instead
// alphabetically
namesort(globalUniforms);
std::swap(globals.variables, globalUniforms);
refl.constantBlocks.push_back(globals);
@@ -1759,16 +1818,20 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref
{
vector<SigParameter> sigs;
sigs.reserve(numInputs);
uint32_t regIndex = 0;
for(GLint i = 0; i < numInputs; i++)
{
GLenum props[] = {eGL_NAME_LENGTH, eGL_TYPE, eGL_LOCATION, eGL_LOCATION_COMPONENT};
GLint values[] = {0, 0, 0, 0};
GLenum props[] = {eGL_NAME_LENGTH, eGL_TYPE, eGL_LOCATION, eGL_ARRAY_SIZE,
eGL_LOCATION_COMPONENT};
GLint values[] = {0, 0, 0, 0, 0};
GLsizei numSigProps = (GLsizei)ARRAY_COUNT(props);
// GL_LOCATION_COMPONENT not supported on core <4.4 (or without GL_ARB_enhanced_layouts)
// and on GLES, either
if(!HasExt[ARB_enhanced_layouts])
// on GLES, or when we don't have native program interface query
if(!HasExt[ARB_enhanced_layouts] || !HasExt[ARB_program_interface_query])
numSigProps--;
GL.glGetProgramResourceiv(sepProg, sigEnum, i, numSigProps, props, numSigProps, NULL, values);
@@ -1924,19 +1987,35 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref
break;
}
sig.regChannelMask <<= values[3];
sig.channelUsedMask = sig.regChannelMask;
sig.systemValue = ShaderBuiltin::Undefined;
#define IS_BUILTIN(builtin) !strncmp(nm, builtin, sizeof(builtin) - 1)
const char *varname = nm;
// if these weren't used, they were probably added just to make a separable program
// (either by us or the program originally). Skip them from the output signature
if(IS_BUILTIN("gl_PointSize") && !pointSizeUsed)
continue;
if(IS_BUILTIN("gl_ClipDistance") && !clipDistanceUsed)
if(!strncmp(varname, "gl_PerVertex.", 13))
varname += 13;
#define IS_BUILTIN(builtin) !strncmp(varname, builtin, sizeof(builtin) - 1)
// some vertex outputs can be reflected (especially by glslang) if they're just declared and
// not used, which is quite common with redeclaring outputs for separable programs - either
// by the program or by us. So instead use our manual quick-and-dirty usage check to skip
// potential false-positives.
bool unused = false;
for(FFVertexOutput ffoutput : ::values<FFVertexOutput>())
{
// we consider an output used if we encounter a '=' before either a ';' or the end of the
// string
std::string outName = ToStr(ffoutput);
// we do a substring search so that gl_ClipDistance matches gl_ClipDistance[0]
if(strstr(varname, outName.c_str()))
{
unused = !outputUsage.used[(int)ffoutput];
break;
}
}
if(unused)
continue;
// VS built-in inputs
@@ -2026,27 +2105,39 @@ void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &ref
sig.systemValue = ShaderBuiltin::GroupFlatIndex;
#undef IS_BUILTIN
if(sig.systemValue == ShaderBuiltin::Undefined)
sig.regIndex = values[2] >= 0 ? values[2] : ~0U;
else
sig.regIndex = 0;
if(shadType == eGL_FRAGMENT_SHADER && sigEnum == eGL_PROGRAM_OUTPUT &&
sig.systemValue == ShaderBuiltin::Undefined)
sig.systemValue = ShaderBuiltin::ColorOutput;
// don't apply location component for built-ins
if(sig.systemValue == ShaderBuiltin::Undefined)
sig.regIndex = values[2] >= 0 ? values[2] : i;
else
sig.regIndex = values[2] >= 0 ? values[2] : 0;
sig.regChannelMask <<= values[4];
if(rows == 1)
sig.channelUsedMask = sig.regChannelMask;
if(values[3] <= 1)
{
sigs.push_back(sig);
AddSigParameter(sigs, regIndex, sig, nm, rows, -1);
}
else
{
for(int r = 0; r < rows; r++)
std::string basename = nm;
if(basename[basename.size() - 3] == '[' && basename[basename.size() - 2] == '0' &&
basename[basename.size() - 1] == ']')
{
SigParameter s = sig;
s.varName = StringFormat::Fmt("%s:row%d", nm, r);
s.regIndex += r;
sigs.push_back(s);
basename.erase(basename.size() - 3);
for(int a = 0; a < values[3]; a++)
AddSigParameter(sigs, regIndex, sig, basename.c_str(), rows, a);
}
else
{
RDCWARN("Got signature parameter %s with array size %d but no [0] suffix", nm, values[3]);
AddSigParameter(sigs, regIndex, sig, nm, rows, -1);
}
}
+29 -3
View File
@@ -27,10 +27,36 @@
class WrappedOpenGL;
enum class FFVertexOutput : uint32_t
{
// Core members of gl_PerVertex
PointSize,
First = PointSize,
ClipDistance,
// Compatibility implicit varyings, generally only comes back from glslang's reflection
ClipVertex,
FrontColor,
BackColor,
FrontSecondaryColor,
BackSecondaryColor,
TexCoord,
FogFragCoord,
Count,
};
DECLARE_REFLECTION_ENUM(FFVertexOutput);
ITERABLE_OPERATORS(FFVertexOutput);
struct FixedFunctionVertexOutputs
{
bool used[arraydim<FFVertexOutput>()] = {};
};
int ParseVersionStatement(const char *version);
void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &refl,
bool pointSizeUsed, bool clipDistanceUsed);
const FixedFunctionVertexOutputs &outputUsage);
GLuint MakeSeparableShaderProgram(WrappedOpenGL &drv, GLenum type, std::vector<std::string> sources,
vector<string> *includepaths);
void CheckVertexOutputUses(const std::vector<std::string> &sources, bool &pointSizeUsed,
bool &clipDistanceUsed);
void CheckVertexOutputUses(const std::vector<std::string> &sources,
FixedFunctionVertexOutputs &outputUsage);
File diff suppressed because it is too large Load Diff
@@ -120,9 +120,9 @@ void WrappedOpenGL::ShaderData::ProcessSPIRVCompilation(WrappedOpenGL &drv, Reso
void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceId id,
GLuint realShader)
{
bool pointSizeUsed = false, clipDistanceUsed = false;
FixedFunctionVertexOutputs outputUsage = {};
if(type == eGL_VERTEX_SHADER)
CheckVertexOutputUses(sources, pointSizeUsed, clipDistanceUsed);
CheckVertexOutputUses(sources, outputUsage);
entryPoint = "main";
@@ -224,9 +224,6 @@ void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceI
if(version == 0)
version = 100;
reflection.encoding = ShaderEncoding::GLSL;
reflection.rawBytes.assign((byte *)concatenated.c_str(), concatenated.size());
GLuint sepProg = prog;
GLint status = 0;
@@ -235,12 +232,17 @@ void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceI
else
drv.glGetShaderiv(realShader, eGL_COMPILE_STATUS, &status);
if(IsCaptureMode(drv.GetState()))
{
// if we don't have program_interface_query, need to compile the shader with glslang to be able
// to reflect with. This is needed on capture or replay
if(!HasExt[ARB_program_interface_query] && status == 1)
glslangShader = CompileShaderForReflection(SPIRVShaderStage(ShaderIdx(type)), sources);
}
else
if(IsReplayMode(drv.GetState()) && !drv.IsInternalShader())
{
// no shaders made under this point should be reflected themselves, they're only used for
// reflection
drv.PushInternalShader();
if(sepProg == 0 && status == 1)
sepProg = MakeSeparableShaderProgram(drv, type, sources, NULL);
@@ -257,7 +259,7 @@ void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceI
else
{
prog = sepProg;
MakeShaderReflection(type, sepProg, reflection, pointSizeUsed, clipDistanceUsed);
MakeShaderReflection(type, sepProg, reflection, outputUsage);
vector<uint32_t> spirvwords;
@@ -275,12 +277,17 @@ void WrappedOpenGL::ShaderData::ProcessCompilation(WrappedOpenGL &drv, ResourceI
reflection.stage = MakeShaderStage(type);
reflection.encoding = ShaderEncoding::GLSL;
reflection.rawBytes.assign((byte *)concatenated.c_str(), concatenated.size());
reflection.debugInfo.encoding = ShaderEncoding::GLSL;
reflection.debugInfo.files.resize(1);
reflection.debugInfo.files[0].filename = "main.glsl";
reflection.debugInfo.files[0].contents = concatenated;
}
drv.PopInternalShader();
}
}
@@ -395,7 +402,7 @@ bool WrappedOpenGL::Serialise_glShaderSource(SerialiserType &ser, GLuint shaderH
// so people who do that should be moderately ashamed.
if(m_Shaders[liveId].prog)
{
GL.glDeleteProgram(m_Shaders[liveId].prog);
glDeleteProgram(m_Shaders[liveId].prog);
m_Shaders[liveId].prog = 0;
m_Shaders[liveId].spirv = SPVModule();
m_Shaders[liveId].reflection = ShaderReflection();
@@ -855,6 +862,28 @@ bool WrappedOpenGL::Serialise_glLinkProgram(SerialiserType &ser, GLuint programH
}
}
if(!HasExt[ARB_program_interface_query])
{
std::vector<glslang::TShader *> glslangShaders;
for(ResourceId id : progDetails.stageShaders)
{
if(id == ResourceId())
continue;
glslang::TShader *s = m_Shaders[id].glslangShader;
if(s == NULL)
{
RDCERR("Shader attached with no compiled glslang reflection shader!");
continue;
}
glslangShaders.push_back(m_Shaders[id].glslangShader);
}
progDetails.glslangProgram = LinkProgramForReflection(glslangShaders);
}
GL.glLinkProgram(program.name);
AddResourceInitChunk(program);
@@ -898,12 +927,15 @@ void WrappedOpenGL::glLinkProgram(GLuint program)
}
}
if(IsCaptureMode(m_State) && !HasExt[ARB_program_interface_query])
if(!HasExt[ARB_program_interface_query])
{
std::vector<glslang::TShader *> glslangShaders;
for(ResourceId id : progDetails.shaders)
for(ResourceId id : progDetails.stageShaders)
{
if(id == ResourceId())
continue;
glslang::TShader *s = m_Shaders[id].glslangShader;
if(s == NULL)
{
@@ -1988,4 +2020,4 @@ INSTANTIATE_FUNCTION_SERIALISED(void, glShaderBinary, GLsizei count, const GLuin
GLenum binaryformat, const void *binary, GLsizei length);
INSTANTIATE_FUNCTION_SERIALISED(void, glSpecializeShader, GLuint shader, const GLchar *pEntryPoint,
GLuint numSpecializationConstants, const GLuint *pConstantIndex,
const GLuint *pConstantValue);
const GLuint *pConstantValue);
+299 -81
View File
@@ -30,6 +30,7 @@
#undef min
#undef max
#include "3rdparty/glslang/glslang/Include/Types.h"
#include "3rdparty/glslang/glslang/Public/ShaderLang.h"
static bool inited = false;
@@ -100,20 +101,14 @@ void glslangGetProgramInterfaceiv(glslang::TProgram *program, ReflectionInterfac
{
switch(programInterface)
{
case ReflectionInterface::Input: *params = program->getNumLiveAttributes(); break;
case ReflectionInterface::Output:
// unsupported
*params = 0;
break;
case ReflectionInterface::Uniform: *params = program->getNumLiveUniformVariables(); break;
case ReflectionInterface::UniformBlock: *params = program->getNumLiveUniformBlocks(); break;
case ReflectionInterface::ShaderStorageBlock:
// unsupported
*params = 0;
break;
case ReflectionInterface::Input: *params = program->getNumPipeInputs(); break;
case ReflectionInterface::Output: *params = program->getNumPipeOutputs(); break;
case ReflectionInterface::Uniform: *params = program->getNumUniformVariables(); break;
case ReflectionInterface::UniformBlock: *params = program->getNumUniformBlocks(); break;
case ReflectionInterface::BufferVariable: *params = program->getNumBufferVariables(); break;
case ReflectionInterface::ShaderStorageBlock: *params = program->getNumBufferBlocks(); break;
case ReflectionInterface::AtomicCounterBuffer:
// unsupported
*params = 0;
*params = program->getNumAtomicCounters();
break;
}
}
@@ -127,13 +122,6 @@ void glslangGetProgramResourceiv(glslang::TProgram *program, ReflectionInterface
uint32_t index, const std::vector<ReflectionProperty> &props,
int32_t bufSize, int32_t *length, int32_t *params)
{
if(programInterface == ReflectionInterface::Output ||
programInterface == ReflectionInterface::ShaderStorageBlock ||
programInterface == ReflectionInterface::AtomicCounterBuffer)
{
RDCWARN("unsupported program interface");
}
// all of our properties are single-element values, so we just loop up to buffer size or number of
// properties, whichever comes first.
for(size_t i = 0; i < RDCMIN((size_t)bufSize, props.size()); i++)
@@ -145,99 +133,324 @@ void glslangGetProgramResourceiv(glslang::TProgram *program, ReflectionInterface
params[i] = 0;
break;
case ReflectionProperty::BufferBinding:
RDCASSERT(programInterface == ReflectionInterface::UniformBlock);
params[i] = program->getUniformBlockBinding(index);
break;
case ReflectionProperty::TopLevelArrayStride:
// TODO glslang doesn't give us this
params[i] = 16;
break;
case ReflectionProperty::BlockIndex:
RDCASSERT(programInterface == ReflectionInterface::Uniform);
params[i] = program->getUniformBlockIndex(index);
break;
case ReflectionProperty::ArraySize:
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniformArraySize(index);
else if(programInterface == ReflectionInterface::Input)
// TODO assuming all inputs are non-arrayed
params[i] = 1;
{
if(programInterface == ReflectionInterface::UniformBlock)
params[i] = program->getUniformBlock(index).getBinding();
else if(programInterface == ReflectionInterface::ShaderStorageBlock)
params[i] = program->getBufferBlock(index).getBinding();
else
RDCERR("Unsupported interface for BufferBinding query");
break;
}
case ReflectionProperty::BlockIndex:
{
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniform(index).index;
else if(programInterface == ReflectionInterface::BufferVariable)
params[i] = program->getBufferVariable(index).index;
else
RDCERR("Unsupported interface for BlockIndex query");
break;
}
case ReflectionProperty::ArraySize:
{
if(programInterface == ReflectionInterface::Uniform)
{
params[i] = program->getUniform(index).size;
}
else if(programInterface == ReflectionInterface::BufferVariable)
{
params[i] = program->getBufferVariable(index).size;
}
else if(programInterface == ReflectionInterface::Input)
{
const glslang::TType *type = program->getPipeInput(index).getType();
if(type->isArray())
params[i] = type->getOuterArraySize();
else
params[i] = 1;
}
else if(programInterface == ReflectionInterface::Output)
{
const glslang::TType *type = program->getPipeOutput(index).getType();
if(type->isArray())
params[i] = type->getOuterArraySize();
else
params[i] = 1;
}
else
{
RDCERR("Unsupported interface for ArraySize query");
}
break;
}
case ReflectionProperty::IsRowMajor:
// TODO glslang doesn't expose this, assume column major.
params[i] = 0;
{
const glslang::TType *ttype = NULL;
if(programInterface == ReflectionInterface::Uniform)
ttype = program->getUniform(index).getType();
else if(programInterface == ReflectionInterface::BufferVariable)
ttype = program->getBufferVariable(index).getType();
else
RDCERR("Unsupported interface for RowMajor query");
if(ttype)
params[i] = (ttype->getQualifier().layoutMatrix == glslang::ElmRowMajor);
else
params[i] = 0;
break;
}
case ReflectionProperty::MatrixStride:
{
// From documentation of std140:
//
// 5. "If the member is a column-major matrix with C columns and R rows, the matrix is
// stored identically to an array of C column vectors with R components each, according to
// rule (4)."
// 7. "If the member is a row-major matrix with C columns and R rows, the matrix is stored
// identically to an array of R row vectors with C components each, according to rule (4)."
//
// So in std140 the matrix stride is always at least 16-bytes unless the matrix is doubles.
// In std430, because the rule (4) array alignment is relaxed, it can be less.
if(programInterface == ReflectionInterface::Uniform)
{
params[i] = 16;
}
else if(programInterface == ReflectionInterface::BufferVariable)
{
const glslang::TType *ttype = program->getBufferVariable(index).getType();
if(ttype->getQualifier().layoutMatrix == glslang::ElmRowMajor)
params[i] = ttype->getMatrixCols() * sizeof(float);
else
params[i] = ttype->getMatrixRows() * sizeof(float);
}
else
{
RDCERR("Unsupported interface for RowMajor query");
}
break;
}
case ReflectionProperty::NumActiveVariables:
// TODO glslang doesn't give us this
params[i] = 1;
{
if(programInterface == ReflectionInterface::UniformBlock)
params[i] = program->getUniformBlock(index).numMembers;
else if(programInterface == ReflectionInterface::ShaderStorageBlock)
params[i] = program->getBufferBlock(index).numMembers;
else
RDCERR("Unsupported interface for NumActiveVariables query");
break;
}
case ReflectionProperty::BufferDataSize:
RDCASSERT(programInterface == ReflectionInterface::UniformBlock);
params[i] = program->getUniformBlockSize(index);
params[i] = program->getUniformBlock(index).size;
break;
case ReflectionProperty::NameLength:
{
// The name length includes a terminating null character.
if(programInterface == ReflectionInterface::Uniform)
params[i] = (int32_t)strlen(program->getUniformName(index)) + 1;
params[i] = (int32_t)program->getUniform(index).name.size() + 1;
else if(programInterface == ReflectionInterface::BufferVariable)
params[i] = (int32_t)program->getBufferVariable(index).name.size() + 1;
else if(programInterface == ReflectionInterface::UniformBlock)
params[i] = (int32_t)strlen(program->getUniformBlockName(index)) + 1;
params[i] = (int32_t)program->getUniformBlock(index).name.size() + 1;
else if(programInterface == ReflectionInterface::Input)
params[i] = (int32_t)strlen(program->getAttributeName(index)) + 1;
params[i] = (int32_t)program->getPipeInput(index).name.size() + 1;
else if(programInterface == ReflectionInterface::Output)
params[i] = (int32_t)program->getPipeOutput(index).name.size() + 1;
else if(programInterface == ReflectionInterface::AtomicCounterBuffer)
params[i] = (int32_t)program->getAtomicCounter(index).name.size() + 1;
else if(programInterface == ReflectionInterface::ShaderStorageBlock)
params[i] = (int32_t)program->getBufferBlock(index).name.size() + 1;
else
RDCERR("Unsupported interface for NameLEngth query");
RDCERR("Unsupported interface for NameLength query");
break;
}
case ReflectionProperty::Type:
{
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniformType(index);
params[i] = program->getUniform(index).glDefineType;
else if(programInterface == ReflectionInterface::BufferVariable)
params[i] = program->getBufferVariable(index).glDefineType;
else if(programInterface == ReflectionInterface::Input)
params[i] = program->getAttributeType(index);
params[i] = program->getPipeInput(index).glDefineType;
else if(programInterface == ReflectionInterface::Output)
params[i] = program->getPipeOutput(index).glDefineType;
else
RDCERR("Unsupported interface for Type query");
if(params[i] == 0)
params[i] = 0x1406; // GL_FLOAT
break;
}
case ReflectionProperty::LocationComponent:
// TODO glslang doesn't give us this information
params[i] = 0;
{
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniform(index).getType()->getQualifier().layoutComponent;
else if(programInterface == ReflectionInterface::BufferVariable)
params[i] = program->getBufferVariable(index).getType()->getQualifier().layoutComponent;
else if(programInterface == ReflectionInterface::Input)
params[i] = program->getPipeInput(index).getType()->getQualifier().layoutComponent;
else if(programInterface == ReflectionInterface::Output)
params[i] = program->getPipeOutput(index).getType()->getQualifier().layoutComponent;
else
RDCERR("Unsupported interface for LocationComponent query");
if(params[i] == glslang::TQualifier::layoutComponentEnd)
params[i] = 0;
break;
}
case ReflectionProperty::ReferencedByVertexShader:
case ReflectionProperty::ReferencedByTessControlShader:
case ReflectionProperty::ReferencedByTessEvaluationShader:
case ReflectionProperty::ReferencedByGeometryShader:
case ReflectionProperty::ReferencedByFragmentShader:
case ReflectionProperty::ReferencedByComputeShader:
// TODO glslang doesn't give us this information
params[i] = 1;
break;
case ReflectionProperty::AtomicCounterBufferIndex:
RDCERR("Atomic counters not supported");
break;
case ReflectionProperty::Offset:
RDCASSERT(programInterface == ReflectionInterface::Uniform);
params[i] = program->getUniformBufferOffset(index);
break;
case ReflectionProperty::MatrixStride:
RDCASSERT(programInterface == ReflectionInterface::Uniform);
// TODO glslang doesn't give us this information
params[i] = 64;
break;
case ReflectionProperty::ArrayStride:
RDCASSERT(programInterface == ReflectionInterface::Uniform);
// TODO glslang doesn't give us this information
params[i] = 64;
break;
case ReflectionProperty::Location:
// have to query the actual implementation, which is handled elsewhere. We return either -1
// for uniforms that don't have a location (i.e. are in a block) or 0 for bare uniforms
{
EShLanguageMask mask = {};
switch(props[i])
{
case ReflectionProperty::ReferencedByVertexShader: mask = EShLangVertexMask; break;
case ReflectionProperty::ReferencedByTessControlShader:
mask = EShLangTessControlMask;
break;
case ReflectionProperty::ReferencedByTessEvaluationShader:
mask = EShLangTessEvaluationMask;
break;
case ReflectionProperty::ReferencedByGeometryShader: mask = EShLangGeometryMask; break;
case ReflectionProperty::ReferencedByFragmentShader: mask = EShLangFragmentMask; break;
case ReflectionProperty::ReferencedByComputeShader: mask = EShLangComputeMask; break;
default: break;
}
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniformBlockIndex(index) >= 0 ? -1 : 0;
params[i] = (program->getUniform(index).stages & mask) != 0;
else if(programInterface == ReflectionInterface::UniformBlock)
params[i] = (program->getUniformBlock(index).stages & mask) != 0;
else if(programInterface == ReflectionInterface::BufferVariable)
params[i] = (program->getBufferVariable(index).stages & mask) != 0;
else if(programInterface == ReflectionInterface::ShaderStorageBlock)
params[i] = (program->getBufferBlock(index).stages & mask) != 0;
else if(programInterface == ReflectionInterface::Input)
params[i] = index;
params[i] = (program->getPipeInput(index).stages & mask) != 0;
else if(programInterface == ReflectionInterface::Output)
params[i] = (program->getPipeOutput(index).stages & mask) != 0;
else if(programInterface == ReflectionInterface::AtomicCounterBuffer)
params[i] = (program->getAtomicCounter(index).stages & mask) != 0;
else
RDCERR("Unexpected interface being queried for referenced-by");
break;
}
case ReflectionProperty::Internal_Binding:
{
if(programInterface == ReflectionInterface::UniformBlock)
{
params[i] = program->getUniformBlock(index).getType()->getQualifier().layoutBinding;
break;
}
// deliberate fall-through
}
case ReflectionProperty::AtomicCounterBufferIndex:
{
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniform(index).getType()->getQualifier().layoutBinding;
else if(programInterface == ReflectionInterface::AtomicCounterBuffer)
params[i] = program->getAtomicCounter(index).getType()->getQualifier().layoutBinding;
else
RDCERR("Unexpected interface being queried for AtomicCounterBufferIndex");
break;
}
case ReflectionProperty::Offset:
{
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniform(index).offset;
else if(programInterface == ReflectionInterface::BufferVariable)
params[i] = program->getBufferVariable(index).offset;
else
RDCERR("Unsupported interface for Offset query");
break;
}
case ReflectionProperty::TopLevelArrayStride:
{
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniform(index).topLevelArrayStride;
else if(programInterface == ReflectionInterface::BufferVariable)
params[i] = program->getBufferVariable(index).topLevelArrayStride;
else
RDCERR("Unsupported interface for ArrayStride query");
break;
}
case ReflectionProperty::ArrayStride:
{
if(programInterface == ReflectionInterface::Uniform)
params[i] = program->getUniform(index).arrayStride;
else if(programInterface == ReflectionInterface::BufferVariable)
params[i] = program->getBufferVariable(index).arrayStride;
else
RDCERR("Unsupported interface for ArrayStride query");
break;
}
case ReflectionProperty::Location:
{
// want to query the actual implementation for bare uniform locations, which is handled
// elsewhere. So we always return either -1 for uniforms that don't have a location (i.e.
// are in a block) or 0 for bare uniforms
if(programInterface == ReflectionInterface::Uniform)
{
params[i] = program->getUniform(index).index >= 0 ? -1 : 0;
}
// for program inputs/outputs for a vertex/fragment shader respectively, we want to do the
// same as above and always query when possible, however for fragment inputs e.g. we want to
// keep the locations that might be present in the shader. So we do the reverse - return -1
// when it's a vertex input to force a query, and otherwise return the layout set.
else if(programInterface == ReflectionInterface::Input)
{
params[i] = program->getPipeInput(index).getType()->getQualifier().layoutLocation;
if(params[i] == glslang::TQualifier::layoutLocationEnd)
params[i] = -1;
if(program->getPipeInput(index).stages == EShLangVertexMask)
params[i] = -1;
}
else if(programInterface == ReflectionInterface::Output)
{
params[i] = program->getPipeOutput(index).getType()->getQualifier().layoutLocation;
if(params[i] == glslang::TQualifier::layoutLocationEnd)
params[i] = -1;
if(program->getPipeOutput(index).stages == EShLangFragmentMask)
params[i] = -1;
}
break;
}
}
}
}
uint32_t glslangGetProgramResourceIndex(glslang::TProgram *program, const char *name)
{
uint32_t idx = program->getReflectionIndex(name);
// Additionally, if <name> would exactly match the name string of an active
// resource if "[0]" were appended to <name>, the index of the matched
// resource is returned.
if(idx == ~0U)
{
std::string arraysuffixed = name;
arraysuffixed += "[0]";
idx = program->getReflectionIndex(arraysuffixed.c_str());
}
return idx;
}
const char *glslangGetProgramResourceName(glslang::TProgram *program,
ReflectionInterface programInterface, uint32_t index)
{
@@ -245,17 +458,22 @@ const char *glslangGetProgramResourceName(glslang::TProgram *program,
switch(programInterface)
{
case ReflectionInterface::Input: fetchedName = program->getAttributeName(index); break;
case ReflectionInterface::Output: RDCWARN("Output attributes unsupported"); break;
case ReflectionInterface::Uniform: fetchedName = program->getUniformName(index); break;
case ReflectionInterface::Input: fetchedName = program->getPipeInput(index).name.c_str(); break;
case ReflectionInterface::Output:
fetchedName = program->getPipeOutput(index).name.c_str();
break;
case ReflectionInterface::Uniform: fetchedName = program->getUniform(index).name.c_str(); break;
case ReflectionInterface::UniformBlock:
fetchedName = program->getUniformBlockName(index);
fetchedName = program->getUniformBlock(index).name.c_str();
break;
case ReflectionInterface::BufferVariable:
fetchedName = program->getBufferVariable(index).name.c_str();
break;
case ReflectionInterface::ShaderStorageBlock:
RDCWARN("shader storage blocks unsupported");
fetchedName = program->getBufferBlock(index).name.c_str();
break;
case ReflectionInterface::AtomicCounterBuffer:
RDCWARN("atomic counter buffers unsupported");
fetchedName = program->getAtomicCounter(index).name.c_str();
break;
}
@@ -187,6 +187,7 @@ enum class ReflectionInterface
UniformBlock,
ShaderStorageBlock,
AtomicCounterBuffer,
BufferVariable,
};
enum class ReflectionProperty
@@ -208,10 +209,11 @@ enum class ReflectionProperty
ReferencedByGeometryShader,
ReferencedByFragmentShader,
ReferencedByComputeShader,
Internal_Binding,
AtomicCounterBufferIndex,
Offset,
MatrixStride,
ArrayStride,
MatrixStride,
Location,
};
@@ -221,6 +223,7 @@ void glslangGetProgramInterfaceiv(glslang::TProgram *program, ReflectionInterfac
void glslangGetProgramResourceiv(glslang::TProgram *program, ReflectionInterface programInterface,
uint32_t index, const std::vector<ReflectionProperty> &props,
int32_t bufSize, int32_t *length, int32_t *params);
uint32_t glslangGetProgramResourceIndex(glslang::TProgram *program, const char *name);
const char *glslangGetProgramResourceName(glslang::TProgram *program,
ReflectionInterface programInterface, uint32_t index);
@@ -264,7 +264,9 @@ glslang::TProgram *LinkProgramForReflection(const std::vector<glslang::TShader *
if(program->link(EShMsgDefault))
{
program->buildReflection();
program->buildReflection(EShReflectionStrictArraySuffix | EShReflectionBasicArraySuffix |
EShReflectionIntermediateIO | EShReflectionSeparateBuffers |
EShReflectionAllBlockVariables | EShReflectionUnwrapIOBlocks);
allocatedPrograms.push_back(program);
return program;
}