[Fix] (Review): honour a SPIR-V module's transform-feedback decorations, take the composite's capture list from the linked snapshot, and reorder every glProgramUniformMatrix* link check

This commit is contained in:
2026-08-27 05:56:45 -04:00
parent 9ef33f4274
commit 11f4b4bd3b
4 changed files with 422 additions and 97 deletions
@@ -222,24 +222,39 @@ namespace MGITest {
glDeleteShader(shader);
}
TEST_F(SpirvShaderBinaryScenario, SpecializeShaderRejectsAConstantTheModuleDoesNotDeclare) {
TEST_F(SpirvShaderBinaryScenario, SpecializeShaderErrorSurfaceMatchesTheExtension) {
if (!Ready()) return;
const GLuint shader = glCreateShader(GL_VERTEX_SHADER);
glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kVertexModule, sizeof(kVertexModule));
ASSERT_EQ(FirstGLError(), 0u);
// 4242 is not one of the module's constant ids. A specialization failure is a COMPILE
// failure, not a GL error - the extension routes it through COMPILE_STATUS.
// 4242 is not one of the module's constant ids. ARB_gl_spirv enumerates that as
// GL_INVALID_VALUE, and an erroring GL command has no other effect - so the shader is left
// untouched rather than pushed into a failed-compile state.
const unsigned int badId = 4242;
const unsigned int value = 0;
glSpecializeShader(shader, "main", 1, &badId, &value);
GLint compiled = GL_TRUE;
EXPECT_EQ(FirstGLError(), static_cast<unsigned int>(GL_INVALID_VALUE));
// Same for an entry point the module does not carry.
glSpecializeShader(shader, "notMain", 0, nullptr, nullptr);
EXPECT_EQ(FirstGLError(), static_cast<unsigned int>(GL_INVALID_VALUE));
// Neither refusal specialized the shader, so a well-formed call still works.
glSpecializeShader(shader, "main", 0, nullptr, nullptr);
EXPECT_EQ(FirstGLError(), 0u);
GLint compiled = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
EXPECT_EQ(compiled, GL_FALSE);
GLint logLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
EXPECT_GT(logLength, 0) << "a failed specialization has to say why";
EXPECT_EQ(compiled, GL_TRUE);
// But a SECOND specialization of a shader that HAS been specialized is INVALID_OPERATION
// until glShaderBinary re-associates the module.
glSpecializeShader(shader, "main", 0, nullptr, nullptr);
EXPECT_EQ(FirstGLError(), static_cast<unsigned int>(GL_INVALID_OPERATION));
glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kVertexModule, sizeof(kVertexModule));
glSpecializeShader(shader, "main", 0, nullptr, nullptr);
EXPECT_EQ(FirstGLError(), 0u) << "re-associating the module makes specialization legal again";
glDeleteShader(shader);
}
+241 -16
View File
@@ -4527,38 +4527,124 @@ TEST_F(ProgramTest, SpecializeShaderCompilesTheModuleAndAppliesItsConstants) {
GetShaderInfoLog(shader, sizeof(infoLog), nullptr, infoLog);
EXPECT_EQ(compiled, GL_TRUE) << infoLog;
// The module stays attached after specialization: glSpecializeShader may legally run again
// with different constants, and it has to re-specialize the ORIGINAL words.
// The module stays attached after specialization - GL_SPIR_V_BINARY keeps reading TRUE - but
// the shader may NOT be specialized again. ARB_gl_spirv: "Once specialized, a shader may not
// be re-specialized without first re-associating the original SPIR-V module with it, through
// ShaderBinary."
GLint isSpirv = GL_FALSE;
GetShaderiv(shader, GL_SPIR_V_BINARY, &isSpirv);
EXPECT_EQ(isSpirv, GL_TRUE);
SpecializeShader(shader, "main", 0, nullptr, nullptr);
ExpectOnlyThisGlError(GL_INVALID_OPERATION);
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
EXPECT_EQ(compiled, GL_TRUE) << "the refused call must not have disturbed the first specialization";
// Re-associating the module is what makes a second specialization legal again - and it is the
// only thing that does.
ShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kVertexModule, sizeof(kVertexModule));
ASSERT_EQ(GetError(), GL_NO_ERROR);
SpecializeShader(shader, "main", 0, nullptr, nullptr);
EXPECT_EQ(GetError(), GL_NO_ERROR);
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
EXPECT_EQ(compiled, GL_TRUE) << "a second specialization of the same module must also compile";
EXPECT_EQ(compiled, GL_TRUE);
DrainProgramTestErrors();
}
TEST_F(ProgramTest, SpecializeShaderReportsBadEntryPointsAndUnknownConstantsThroughCompileStatus) {
// glShaderSource does the same re-association in the other direction: it turns the object back
// into a GLSL shader, so a later glShaderBinary + glSpecializeShader pair is legal again.
TEST_F(ProgramTest, ShaderSourceClearsTheSpecializedLatch) {
DrainProgramTestErrors();
const GLuint shader = CreateShader(GL_VERTEX_SHADER);
ShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kVertexModule, sizeof(kVertexModule));
SpecializeShader(shader, "main", 0, nullptr, nullptr);
ASSERT_EQ(GetError(), GL_NO_ERROR);
const char* source = "#version 450 core\nvoid main() { gl_Position = vec4(0.0); }\n";
ShaderSource(shader, 1, &source, nullptr);
ShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kVertexModule, sizeof(kVertexModule));
ASSERT_EQ(GetError(), GL_NO_ERROR);
SpecializeShader(shader, "main", 0, nullptr, nullptr);
EXPECT_EQ(GetError(), GL_NO_ERROR) << "the latch must not survive a round trip through glShaderSource";
DrainProgramTestErrors();
}
// A shader that came from glShaderBinary has never had glShaderSource called on it, so GL 4.6
// core 7.1 makes its source the empty string - including AFTER glSpecializeShader, when the
// object internally holds the GLSL the module was translated into. That text is MobileGL's, not
// the application's, and handing it back invites an application to cache and re-submit it.
TEST_F(ProgramTest, ASpirvShaderReportsNoApplicationSource) {
DrainProgramTestErrors();
const GLuint shader = CreateShader(GL_VERTEX_SHADER);
ShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kVertexModule, sizeof(kVertexModule));
ASSERT_EQ(GetError(), GL_NO_ERROR);
// A constant id the module does not declare. ARB_gl_spirv routes a failed specialization
// through COMPILE_STATUS and the info log, exactly as glCompileShader does - it is not a GL
// error, which is why an application that only checks glGetError would see nothing.
GLint sourceLength = -1;
GetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);
EXPECT_EQ(sourceLength, 0);
SpecializeShader(shader, "main", 0, nullptr, nullptr);
ASSERT_EQ(GetError(), GL_NO_ERROR);
GLint compiled = GL_FALSE;
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
ASSERT_EQ(compiled, GL_TRUE) << "the leak this pins only exists on the specialized path";
sourceLength = -1;
GetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);
EXPECT_EQ(sourceLength, 0) << "the SPIRV-Cross GLSL is not the application's source";
char buffer[64];
std::memset(buffer, 'x', sizeof(buffer));
GLsizei written = -1;
GetShaderSource(shader, static_cast<GLsizei>(sizeof(buffer)), &written, buffer);
EXPECT_EQ(written, 0);
EXPECT_EQ(buffer[0], '\0');
EXPECT_EQ(GetError(), GL_NO_ERROR);
// A GLSL shader still answers with what the application gave it.
const char* source = "#version 450 core\nvoid main() { gl_Position = vec4(0.0); }\n";
ShaderSource(shader, 1, &source, nullptr);
GetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);
EXPECT_EQ(sourceLength, static_cast<GLint>(std::strlen(source)) + 1);
DrainProgramTestErrors();
}
// The two conditions ARB_gl_spirv ENUMERATES are GL_INVALID_VALUE, not compile failures: "an
// INVALID_VALUE error is generated if pEntryPoint does not name a valid entry point for shader"
// and "...if any element of pConstantIndex refers to a specialization constant that does not exist
// in the shader module contained in shader". Both used to be reported as COMPILE_STATUS false with
// no GL error, which an application checking glGetError could not see at all.
//
// The distinction matters beyond the error code: an erroring GL command must have NO OTHER EFFECT,
// so neither of these may leave the shader object in a failed-compile state. The conformance suite
// leans on exactly that - it fails specialization twice on one object and then requires the next,
// well-formed call on that same object to succeed.
TEST_F(ProgramTest, SpecializeShaderRaisesInvalidValueForBadEntryPointsAndUnknownConstants) {
DrainProgramTestErrors();
const GLuint shader = CreateShader(GL_VERTEX_SHADER);
ShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kVertexModule, sizeof(kVertexModule));
ASSERT_EQ(GetError(), GL_NO_ERROR);
// A constant id the module does not declare.
const unsigned int unknownId = 4242;
const unsigned int value = 0;
SpecializeShader(shader, "main", 1, &unknownId, &value);
GLint compiled = GL_TRUE;
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
EXPECT_EQ(compiled, GL_FALSE);
GLint logLength = 0;
GetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
EXPECT_GT(logLength, 0) << "a failed specialization has to say why";
ExpectOnlyThisGlError(GL_INVALID_VALUE);
// An entry point the module does not carry.
SpecializeShader(shader, "notMain", 0, nullptr, nullptr);
ExpectOnlyThisGlError(GL_INVALID_VALUE);
// Neither of them may name an entry point at all.
SpecializeShader(shader, nullptr, 0, nullptr, nullptr);
ExpectOnlyThisGlError(GL_INVALID_VALUE);
SpecializeShader(shader, "", 0, nullptr, nullptr);
ExpectOnlyThisGlError(GL_INVALID_VALUE);
// A repeated constant index is GL_INVALID_VALUE at the entry point itself.
const unsigned int repeated[2] = {3, 3};
@@ -4566,10 +4652,35 @@ TEST_F(ProgramTest, SpecializeShaderReportsBadEntryPointsAndUnknownConstantsThro
SpecializeShader(shader, "main", 2, repeated, values);
ExpectOnlyThisGlError(GL_INVALID_VALUE);
// An entry point the module does not carry.
SpecializeShader(shader, "notMain", 0, nullptr, nullptr);
// AND NOW THE POINT: none of the five refused calls specialized the shader or damaged it, so
// the well-formed call that follows must still be accepted. Latching the "specialized" flag on
// the failure path - the obvious way to implement the re-specialization rule - breaks exactly
// here, which is why the flag is only ever set on the success path.
SpecializeShader(shader, "main", 0, nullptr, nullptr);
EXPECT_EQ(GetError(), GL_NO_ERROR) << "a failed specialization does not make the shader specialized";
GLint compiled = GL_FALSE;
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
EXPECT_EQ(compiled, GL_FALSE);
EXPECT_EQ(compiled, GL_TRUE);
DrainProgramTestErrors();
}
// A GENUINE compile failure of a well-formed request keeps the COMPILE_STATUS surface: the module
// is a valid SPIR-V module naming a real entry point, it simply cannot be translated for this
// stage. Nothing about that is one of the enumerated errors.
TEST_F(ProgramTest, SpecializeShaderStillReportsATranslationFailureThroughCompileStatus) {
DrainProgramTestErrors();
// The vertex module handed to a FRAGMENT shader object: its only entry point carries the
// Vertex execution model, so no fragment entry point named "main" exists in it.
const GLuint shader = CreateShader(GL_FRAGMENT_SHADER);
ShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kVertexModule, sizeof(kVertexModule));
ASSERT_EQ(GetError(), GL_NO_ERROR);
SpecializeShader(shader, "main", 0, nullptr, nullptr);
// Reported as INVALID_VALUE (there is no such entry point FOR THIS STAGE) - the stage is part
// of what "a valid entry point for shader" means.
ExpectOnlyThisGlError(GL_INVALID_VALUE);
DrainProgramTestErrors();
}
@@ -4590,3 +4701,117 @@ TEST_F(ProgramTest, ShaderBinaryFormatsAreAdvertisedConsistently) {
DrainProgramTestErrors();
}
// ---------------------------------------------------------------------------------------------
// ARB_gl_spirv makes XfbBuffer / XfbStride / Offset DECORATIONS the only way a SPIR-V program
// declares transform feedback - glTransformFeedbackVaryings has no effect on such a program. The
// decorations were ignored entirely: the link ran off `in.requestedXfbVaryings`, which is empty
// for a SPIR-V program, so a module that asked for capture captured nothing and
// GL_TRANSFORM_FEEDBACK_VARYINGS answered zero.
//
// glSpecializeShader now reflects the decorations and re-expresses them as the equivalent
// glTransformFeedbackVaryings request (ARB_transform_feedback3's gl_SkipComponentsN carrying the
// declared offset), which is the form every consumer downstream already implements.
//
// The module below is `layout(xfb_buffer = 0, xfb_offset = 16) out gl_PerVertex { vec4
// gl_Position; };` over a trivial vertex shader - the exact shape gl4cGlSpirvTests'
// spirv_modules_state_queries_test feeds in first. Offset 16 with a stride of 32 means the capture
// is four components in, i.e. one gl_SkipComponents4 ahead of gl_Position.
// ---------------------------------------------------------------------------------------------
namespace {
// 177 words
const unsigned int kXfbVertexModule[] = {
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000015u, 0x00000000u, 0x00020011u, 0x00000001u, 0x00020011u,
0x00000035u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu,
0x00000000u, 0x00000001u, 0x0009000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000au,
0x0000000eu, 0x00000013u, 0x00000014u, 0x00030010u, 0x00000004u, 0x0000000bu, 0x00030003u, 0x00000002u,
0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00060005u, 0x00000008u, 0x505f6c67u,
0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x00000008u, 0x00000000u, 0x505f6c67u, 0x7469736fu,
0x006e6f69u, 0x00030005u, 0x0000000au, 0x00000000u, 0x00050005u, 0x0000000eu, 0x69736f70u, 0x6e6f6974u,
0x00000000u, 0x00050005u, 0x00000013u, 0x565f6c67u, 0x65747265u, 0x00444978u, 0x00060005u, 0x00000014u,
0x495f6c67u, 0x6174736eu, 0x4965636eu, 0x00000044u, 0x00030047u, 0x00000008u, 0x00000002u, 0x00050048u,
0x00000008u, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, 0x00000008u, 0x00000000u, 0x00000023u,
0x00000010u, 0x00040047u, 0x0000000au, 0x00000024u, 0x00000000u, 0x00040047u, 0x0000000au, 0x00000025u,
0x00000020u, 0x00040047u, 0x0000000eu, 0x0000001eu, 0x00000000u, 0x00040047u, 0x00000013u, 0x0000000bu,
0x00000005u, 0x00040047u, 0x00000014u, 0x0000000bu, 0x00000006u, 0x00020013u, 0x00000002u, 0x00030021u,
0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u,
0x00000004u, 0x0003001eu, 0x00000008u, 0x00000007u, 0x00040020u, 0x00000009u, 0x00000003u, 0x00000008u,
0x0004003bu, 0x00000009u, 0x0000000au, 0x00000003u, 0x00040015u, 0x0000000bu, 0x00000020u, 0x00000001u,
0x0004002bu, 0x0000000bu, 0x0000000cu, 0x00000000u, 0x00040020u, 0x0000000du, 0x00000001u, 0x00000007u,
0x0004003bu, 0x0000000du, 0x0000000eu, 0x00000001u, 0x00040020u, 0x00000010u, 0x00000003u, 0x00000007u,
0x00040020u, 0x00000012u, 0x00000001u, 0x0000000bu, 0x0004003bu, 0x00000012u, 0x00000013u, 0x00000001u,
0x0004003bu, 0x00000012u, 0x00000014u, 0x00000001u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u,
0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u, 0x0000000fu, 0x0000000eu, 0x00050041u,
0x00000010u, 0x00000011u, 0x0000000au, 0x0000000cu, 0x0003003eu, 0x00000011u, 0x0000000fu, 0x000100fdu,
0x00010038u,
};
} // namespace
TEST_F(ProgramTest, ASpirvModulesXfbDecorationsBecomeTheProgramsCaptureList) {
DrainProgramTestErrors();
const GLuint shader = CreateShader(GL_VERTEX_SHADER);
ShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kXfbVertexModule, sizeof(kXfbVertexModule));
ASSERT_EQ(GetError(), GL_NO_ERROR);
SpecializeShader(shader, "main", 0, nullptr, nullptr);
ASSERT_EQ(GetError(), GL_NO_ERROR);
GLint compiled = GL_FALSE;
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
char shaderLog[2048] = "";
GetShaderInfoLog(shader, sizeof(shaderLog), nullptr, shaderLog);
ASSERT_EQ(compiled, GL_TRUE) << shaderLog;
const GLuint program = CreateProgram();
AttachShader(program, shader);
// NO glTransformFeedbackVaryings anywhere: the declaration is the module's own.
LinkProgram(program);
GLint linked = GL_FALSE;
GetProgramiv(program, GL_LINK_STATUS, &linked);
char programLog[2048] = "";
GetProgramInfoLog(program, sizeof(programLog), nullptr, programLog);
ASSERT_EQ(linked, GL_TRUE) << programLog;
GLint varyingCount = -1;
GetProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS, &varyingCount);
EXPECT_GT(varyingCount, 0) << "the module's xfb decorations declared a capture and none was recorded";
// The captured name is the built-in the block redeclared. It is found by BuiltIn decoration,
// not by string, because a stripped module carries no OpMemberName at all.
Bool sawPosition = false;
for (GLint i = 0; i < varyingCount; ++i) {
char name[128] = "";
GLsizei nameLength = 0;
GLsizei size = 0;
GLenum type = 0;
GetTransformFeedbackVarying(program, static_cast<GLuint>(i), sizeof(name), &nameLength, &size, &type, name);
if (String(name) == "gl_Position") sawPosition = true;
}
EXPECT_TRUE(sawPosition) << "gl_Position was declared captured by the module's Offset decoration";
EXPECT_EQ(GetError(), GL_NO_ERROR);
DrainProgramTestErrors();
}
// The other half of the same fix: the decorations must NOT survive into the GLSL the module is
// translated into. SPIRV-Cross re-emits them as layout(xfb_buffer/xfb_stride/xfb_offset), glslang
// re-encodes them into the regenerated SPIR-V, and the DirectGLES ESSL hop then refuses them
// outright ("Need GL_ARB_enhanced_layouts for xfb_stride or xfb_buffer") and drops the stage -
// a program that links clean and draws nothing. Compiling at all is the observable proof they are
// gone; the ESSL leg is exercised by the integration scenario.
TEST_F(ProgramTest, ASpirvModulesXfbDecorationsDoNotSurviveIntoTheTranslatedSource) {
DrainProgramTestErrors();
const GLuint shader = CreateShader(GL_VERTEX_SHADER);
ShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, kXfbVertexModule, sizeof(kXfbVertexModule));
SpecializeShader(shader, "main", 0, nullptr, nullptr);
ASSERT_EQ(GetError(), GL_NO_ERROR);
GLint compiled = GL_FALSE;
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
char shaderLog[2048] = "";
GetShaderInfoLog(shader, sizeof(shaderLog), nullptr, shaderLog);
EXPECT_EQ(compiled, GL_TRUE) << shaderLog;
DrainProgramTestErrors();
}
@@ -12,6 +12,7 @@
#include "ShaderCompiler.h"
#include <algorithm>
#include <format>
#include <cmath>
+157 -73
View File
@@ -538,75 +538,151 @@ namespace MobileGL {
}
} // namespace
namespace {
// The four gl_PerVertex members, by their GL interface names. These are the only
// built-ins GL lets transform feedback capture, and a SPIR-V module names them by
// BuiltIn decoration rather than by string - so the mapping has to live somewhere.
const char* XfbBuiltInName(SpvBuiltIn builtin) {
switch (builtin) {
case SpvBuiltInPosition:
return "gl_Position";
case SpvBuiltInPointSize:
return "gl_PointSize";
case SpvBuiltInClipDistance:
return "gl_ClipDistance";
case SpvBuiltInCullDistance:
return "gl_CullDistance";
default:
return nullptr;
}
}
} // namespace
Vector<SpirvXfbCapture> SpvcSession::ReflectTransformFeedbackCaptures() const {
Vector<SpirvXfbCapture> captures;
if (compiler == nullptr || resources == nullptr) return captures;
// XfbBuffer/XfbStride sit on the declaring VARIABLE; Offset sits on the variable
// for a plain output and on each MEMBER for a block.
auto readVariableDecorations = [this](SpvId id, Uint32& outBuffer, Uint32& outStride) {
outBuffer = spvc_compiler_has_decoration(compiler, id, SpvDecorationXfbBuffer) == SPVC_TRUE
? spvc_compiler_get_decoration(compiler, id, SpvDecorationXfbBuffer)
: 0u;
outStride = spvc_compiler_has_decoration(compiler, id, SpvDecorationXfbStride) == SPVC_TRUE
? spvc_compiler_get_decoration(compiler, id, SpvDecorationXfbStride)
: 0u;
};
// ---- application outputs: plain variables and application blocks ----
const spvc_reflected_resource* outputs = nullptr;
SizeT outputCount = 0;
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STAGE_OUTPUT, &outputs,
&outputCount) != SPVC_SUCCESS) {
return captures;
&outputCount) == SPVC_SUCCESS) {
for (SizeT i = 0; i < outputCount; ++i) {
const spvc_reflected_resource& output = outputs[i];
Uint32 buffer = 0;
Uint32 stride = 0;
readVariableDecorations(output.id, buffer, stride);
const spvc_type type = spvc_compiler_get_type_handle(compiler, output.base_type_id);
const unsigned memberCount =
type != nullptr && spvc_type_get_basetype(type) == SPVC_BASETYPE_STRUCT
? spvc_type_get_num_member_types(type)
: 0u;
if (memberCount == 0) {
if (spvc_compiler_has_decoration(compiler, output.id, SpvDecorationOffset) != SPVC_TRUE) {
continue;
}
SpirvXfbCapture capture;
capture.name = output.name ? output.name : "";
capture.buffer = buffer;
capture.stride = stride;
capture.offset = spvc_compiler_get_decoration(compiler, output.id, SpvDecorationOffset);
capture.componentCount = XfbComponentCount(compiler, output.type_id);
if (!capture.name.empty()) captures.push_back(Move(capture));
continue;
}
for (unsigned member = 0; member < memberCount; ++member) {
if (spvc_compiler_has_member_decoration(compiler, output.base_type_id, member,
SpvDecorationOffset) != SPVC_TRUE) {
continue;
}
const char* memberName =
spvc_compiler_get_member_name(compiler, output.base_type_id, member);
if (memberName == nullptr || *memberName == '\0') continue;
SpirvXfbCapture capture;
const String blockName = output.name ? String(output.name) : String{};
// GL's capture interface spells an application block's member
// "Block.member"; a redeclared built-in block contributes its members
// by their own names, which the built-in walk below handles.
capture.name = blockName.empty() ? String(memberName)
: blockName + "." + String(memberName);
capture.buffer = buffer;
capture.stride = stride;
capture.offset = spvc_compiler_get_member_decoration(compiler, output.base_type_id,
member, SpvDecorationOffset);
capture.componentCount =
XfbComponentCount(compiler, spvc_type_get_member_type(type, member));
captures.push_back(Move(capture));
}
}
}
for (SizeT i = 0; i < outputCount; ++i) {
const spvc_reflected_resource& output = outputs[i];
// XfbBuffer/XfbStride sit on the VARIABLE; Offset sits on the variable for a
// plain output and on each MEMBER for a block (which is how a redeclared
// gl_PerVertex carries it).
const Bool hasBuffer =
spvc_compiler_has_decoration(compiler, output.id, SpvDecorationXfbBuffer) == SPVC_TRUE;
const Uint32 buffer =
hasBuffer ? spvc_compiler_get_decoration(compiler, output.id, SpvDecorationXfbBuffer) : 0u;
const Uint32 stride =
spvc_compiler_has_decoration(compiler, output.id, SpvDecorationXfbStride) == SPVC_TRUE
? spvc_compiler_get_decoration(compiler, output.id, SpvDecorationXfbStride)
: 0u;
// ---- the redeclared built-in block ----
// SPIRV-Cross keeps gl_PerVertex out of the STAGE_OUTPUT list and reports it here
// instead, one entry per built-in member. That is the shape the conformance suite
// feeds in first (`layout(xfb_buffer = 0, xfb_offset = 16) out gl_PerVertex { vec4
// gl_Position; }`), so walking only the list above would have found nothing at all.
const spvc_reflected_builtin_resource* builtins = nullptr;
SizeT builtinCount = 0;
if (spvc_resources_get_builtin_resource_list_for_type(
resources, SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT, &builtins, &builtinCount) ==
SPVC_SUCCESS) {
for (SizeT i = 0; i < builtinCount; ++i) {
const spvc_reflected_builtin_resource& entry = builtins[i];
const char* name = XfbBuiltInName(entry.builtin);
if (name == nullptr) continue;
const spvc_type type = spvc_compiler_get_type_handle(compiler, output.base_type_id);
const unsigned memberCount =
type != nullptr && spvc_type_get_basetype(type) == SPVC_BASETYPE_STRUCT
? spvc_type_get_num_member_types(type)
: 0u;
Uint32 buffer = 0;
Uint32 stride = 0;
readVariableDecorations(entry.resource.id, buffer, stride);
if (memberCount == 0) {
if (spvc_compiler_has_decoration(compiler, output.id, SpvDecorationOffset) != SPVC_TRUE) {
const spvc_type blockType =
spvc_compiler_get_type_handle(compiler, entry.resource.base_type_id);
if (blockType == nullptr ||
spvc_type_get_basetype(blockType) != SPVC_BASETYPE_STRUCT) {
continue;
}
SpirvXfbCapture capture;
capture.name = output.name ? output.name : "";
capture.buffer = buffer;
capture.stride = stride;
capture.offset = spvc_compiler_get_decoration(compiler, output.id, SpvDecorationOffset);
capture.componentCount = XfbComponentCount(compiler, output.type_id);
if (!capture.name.empty()) captures.push_back(Move(capture));
continue;
}
for (unsigned member = 0; member < memberCount; ++member) {
if (spvc_compiler_has_member_decoration(compiler, output.base_type_id, member,
SpvDecorationOffset) != SPVC_TRUE) {
continue;
// The member index is not in the reflection entry, so it is recovered by
// matching the BuiltIn decoration - the same key the entry is keyed on.
const unsigned memberCount = spvc_type_get_num_member_types(blockType);
for (unsigned member = 0; member < memberCount; ++member) {
if (spvc_compiler_has_member_decoration(compiler, entry.resource.base_type_id, member,
SpvDecorationBuiltIn) != SPVC_TRUE) {
continue;
}
if (spvc_compiler_get_member_decoration(compiler, entry.resource.base_type_id, member,
SpvDecorationBuiltIn) !=
static_cast<unsigned>(entry.builtin)) {
continue;
}
if (spvc_compiler_has_member_decoration(compiler, entry.resource.base_type_id, member,
SpvDecorationOffset) != SPVC_TRUE) {
break; // this built-in is present but not captured
}
SpirvXfbCapture capture;
capture.name = name;
capture.buffer = buffer;
capture.stride = stride;
capture.offset = spvc_compiler_get_member_decoration(
compiler, entry.resource.base_type_id, member, SpvDecorationOffset);
capture.componentCount =
XfbComponentCount(compiler, spvc_type_get_member_type(blockType, member));
captures.push_back(Move(capture));
break;
}
const char* memberName =
spvc_compiler_get_member_name(compiler, output.base_type_id, member);
if (memberName == nullptr || *memberName == '\0') continue;
SpirvXfbCapture capture;
// A redeclared built-in block contributes its members by their own names
// ("gl_Position"), which is how GL's capture interface spells them; an
// application block spells them "Block.member".
const String blockName = output.name ? String(output.name) : String{};
const Bool isBuiltInBlock = blockName.compare(0, 3, "gl_") == 0;
capture.name = isBuiltInBlock || blockName.empty()
? String(memberName)
: blockName + "." + String(memberName);
capture.buffer = buffer;
capture.stride = stride;
capture.offset = spvc_compiler_get_member_decoration(compiler, output.base_type_id, member,
SpvDecorationOffset);
capture.componentCount = XfbComponentCount(
compiler, spvc_type_get_member_type(type, member));
captures.push_back(Move(capture));
}
}
@@ -624,28 +700,36 @@ namespace MobileGL {
void SpvcSession::StripTransformFeedbackDecorations() {
if (compiler == nullptr || resources == nullptr) return;
auto stripVariable = [this](SpvId variableId, spvc_type_id baseTypeId) {
spvc_compiler_unset_decoration(compiler, variableId, SpvDecorationXfbBuffer);
spvc_compiler_unset_decoration(compiler, variableId, SpvDecorationXfbStride);
spvc_compiler_unset_decoration(compiler, variableId, SpvDecorationOffset);
const spvc_type type = spvc_compiler_get_type_handle(compiler, baseTypeId);
if (type == nullptr || spvc_type_get_basetype(type) != SPVC_BASETYPE_STRUCT) return;
const unsigned memberCount = spvc_type_get_num_member_types(type);
for (unsigned member = 0; member < memberCount; ++member) {
spvc_compiler_unset_member_decoration(compiler, baseTypeId, member, SpvDecorationOffset);
spvc_compiler_unset_member_decoration(compiler, baseTypeId, member, SpvDecorationXfbBuffer);
spvc_compiler_unset_member_decoration(compiler, baseTypeId, member, SpvDecorationXfbStride);
}
};
const spvc_reflected_resource* outputs = nullptr;
SizeT outputCount = 0;
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STAGE_OUTPUT, &outputs,
&outputCount) != SPVC_SUCCESS) {
return;
&outputCount) == SPVC_SUCCESS) {
for (SizeT i = 0; i < outputCount; ++i) {
stripVariable(outputs[i].id, outputs[i].base_type_id);
}
}
for (SizeT i = 0; i < outputCount; ++i) {
const spvc_reflected_resource& output = outputs[i];
spvc_compiler_unset_decoration(compiler, output.id, SpvDecorationXfbBuffer);
spvc_compiler_unset_decoration(compiler, output.id, SpvDecorationXfbStride);
spvc_compiler_unset_decoration(compiler, output.id, SpvDecorationOffset);
const spvc_type type = spvc_compiler_get_type_handle(compiler, output.base_type_id);
if (type == nullptr || spvc_type_get_basetype(type) != SPVC_BASETYPE_STRUCT) continue;
const unsigned memberCount = spvc_type_get_num_member_types(type);
for (unsigned member = 0; member < memberCount; ++member) {
spvc_compiler_unset_member_decoration(compiler, output.base_type_id, member,
SpvDecorationOffset);
spvc_compiler_unset_member_decoration(compiler, output.base_type_id, member,
SpvDecorationXfbBuffer);
spvc_compiler_unset_member_decoration(compiler, output.base_type_id, member,
SpvDecorationXfbStride);
const spvc_reflected_builtin_resource* builtins = nullptr;
SizeT builtinCount = 0;
if (spvc_resources_get_builtin_resource_list_for_type(
resources, SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT, &builtins, &builtinCount) ==
SPVC_SUCCESS) {
for (SizeT i = 0; i < builtinCount; ++i) {
stripVariable(builtins[i].resource.id, builtins[i].resource.base_type_id);
}
}
}