mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix, Test] (MG_State, MG_Impl): the program interface of a separable program is its own first and last stage, not vertex and fragment
This commit is contained in:
@@ -210,11 +210,66 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface {
|
|||||||
|
|
||||||
// ---- model construction --------------------------------------------------------
|
// ---- model construction --------------------------------------------------------
|
||||||
|
|
||||||
|
// GL_REFERENCED_BY_*_SHADER for an ARRAYED block instance, refined per element.
|
||||||
|
//
|
||||||
|
// glslang records a block reference by walking up to the base symbol and calling
|
||||||
|
// addBlockName with the whole ARRAY type, which ORs the referencing stage into every
|
||||||
|
// element at once - it has not resolved the subscript yet at that point. So reading
|
||||||
|
// "e[0].b" marks both TrickyBlock[0] and TrickyBlock[1] as referenced by the fragment
|
||||||
|
// stage (KHR-GL43.program_interface_query.uniform-block-types).
|
||||||
|
//
|
||||||
|
// The MEMBER masks are exact: EShReflectionAllBlockVariables enumerates every member of
|
||||||
|
// every element with the stage mask suppressed, and only the dereference chain actually
|
||||||
|
// walked turns a bit on - and that chain carries the subscript. So the union of a block
|
||||||
|
// instance's members is the reference set of that instance.
|
||||||
|
//
|
||||||
|
// Applied ONLY to arrayed instances, because for a scalar block glslang is already exact.
|
||||||
|
// Note the union is used even when it is empty: an array element nobody dereferenced has
|
||||||
|
// no member bits and is genuinely referenced by nobody, which is the whole point - falling
|
||||||
|
// back to the block's own mask there would restore the over-approximation.
|
||||||
|
Vector<Uint32> BuildBlockStagesFromMembers(const glslang::TProgram& reflection, Int blockCount) {
|
||||||
|
auto& mutableReflection = const_cast<glslang::TProgram&>(reflection);
|
||||||
|
Vector<Uint32> stagesByBlock(static_cast<SizeT>(blockCount < 0 ? 0 : blockCount), 0u);
|
||||||
|
const Int uniformCount = mutableReflection.getNumUniformVariables();
|
||||||
|
for (Int index = 0; index < uniformCount; ++index) {
|
||||||
|
const auto& uniform = mutableReflection.getUniform(index);
|
||||||
|
const Int owner = uniform.index;
|
||||||
|
if (owner < 0 || owner >= blockCount) continue;
|
||||||
|
stagesByBlock[static_cast<SizeT>(owner)] |= static_cast<Uint32>(uniform.stages);
|
||||||
|
}
|
||||||
|
return stagesByBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UNIFORM blocks only, and that scope is load-bearing rather than cautious. The member
|
||||||
|
// names glslang produces for a uniform block array carry the subscript
|
||||||
|
// ("TrickyBlock[0].b", via EShReflectionStrictArraySuffix), so each element's members are
|
||||||
|
// distinct entries and the bits land on the right one. A SHADER STORAGE block array does
|
||||||
|
// NOT get that treatment - its buffer variables reflect under one subscript-free spelling
|
||||||
|
// shared by every element - so a union over them credits element 0 and starves the rest.
|
||||||
|
// KHR-GL43.program_interface_query.ssb-types is the case that says so: it reads ss[0] and
|
||||||
|
// ss[1] and requires both to report the fragment stage, which only glslang's own
|
||||||
|
// (deliberately over-approximating) block mask gets right. Storage and atomic-counter
|
||||||
|
// blocks therefore keep that mask untouched.
|
||||||
|
Uint32 UniformBlockStages(const glslang::TObjectReflection& block, const Vector<Uint32>& stagesFromMembers,
|
||||||
|
Int tIndex) {
|
||||||
|
String arrayBase;
|
||||||
|
Uint element = 0;
|
||||||
|
Bool malformed = false;
|
||||||
|
if (!SplitTrailingSubscript(block.name, arrayBase, element, malformed) || malformed) {
|
||||||
|
return static_cast<Uint32>(block.stages);
|
||||||
|
}
|
||||||
|
if (tIndex < 0 || tIndex >= static_cast<Int>(stagesFromMembers.size())) {
|
||||||
|
return static_cast<Uint32>(block.stages);
|
||||||
|
}
|
||||||
|
return stagesFromMembers[static_cast<SizeT>(tIndex)];
|
||||||
|
}
|
||||||
|
|
||||||
void BuildBlocks(ProgramObject& program, const glslang::TProgram& reflection, Model& model,
|
void BuildBlocks(ProgramObject& program, const glslang::TProgram& reflection, Model& model,
|
||||||
Vector<BlockKind>& blockKind, Vector<Int>& blockInterfaceIndex) {
|
Vector<BlockKind>& blockKind, Vector<Int>& blockInterfaceIndex) {
|
||||||
const Int blockCount = const_cast<glslang::TProgram&>(reflection).getNumUniformBlocks();
|
const Int blockCount = const_cast<glslang::TProgram&>(reflection).getNumUniformBlocks();
|
||||||
blockKind.assign(blockCount, BlockKind::Uniform);
|
blockKind.assign(blockCount, BlockKind::Uniform);
|
||||||
blockInterfaceIndex.assign(blockCount, -1);
|
blockInterfaceIndex.assign(blockCount, -1);
|
||||||
|
const Vector<Uint32> stagesFromMembers = BuildBlockStagesFromMembers(reflection, blockCount);
|
||||||
|
|
||||||
for (Int tIndex = 0; tIndex < blockCount; ++tIndex) {
|
for (Int tIndex = 0; tIndex < blockCount; ++tIndex) {
|
||||||
const auto& block = const_cast<glslang::TProgram&>(reflection).getUniformBlock(tIndex);
|
const auto& block = const_cast<glslang::TProgram&>(reflection).getUniformBlock(tIndex);
|
||||||
@@ -260,8 +315,8 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface {
|
|||||||
resource.bufferDataSize = static_cast<GLint>(program.GetUBOSizeAt(glIndex));
|
resource.bufferDataSize = static_cast<GLint>(program.GetUBOSizeAt(glIndex));
|
||||||
const Int tIndex = program.TProgramBlockIndex(static_cast<Uint>(glIndex));
|
const Int tIndex = program.TProgramBlockIndex(static_cast<Uint>(glIndex));
|
||||||
if (tIndex >= 0 && tIndex < blockCount) {
|
if (tIndex >= 0 && tIndex < blockCount) {
|
||||||
resource.stages =
|
resource.stages = UniformBlockStages(const_cast<glslang::TProgram&>(reflection).getUniformBlock(tIndex),
|
||||||
static_cast<Uint32>(const_cast<glslang::TProgram&>(reflection).getUniformBlock(tIndex).stages);
|
stagesFromMembers, tIndex);
|
||||||
}
|
}
|
||||||
model.uniformBlocks.push_back(Move(resource));
|
model.uniformBlocks.push_back(Move(resource));
|
||||||
}
|
}
|
||||||
@@ -352,6 +407,17 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A built-in interface block that a shader redeclares with fewer members keeps the
|
||||||
|
// omitted ones in its type when the redeclaration is ANONYMOUS - glslang hides them
|
||||||
|
// (basic type void) instead of erasing them, because the original shared declaration
|
||||||
|
// has to stay usable. Only the instance-named form erases. So a separable vertex
|
||||||
|
// program that redeclares `out gl_PerVertex { vec4 gl_Position; }` still carries
|
||||||
|
// gl_PointSize and gl_ClipDistance through the block-unwrapping reflection, and they
|
||||||
|
// are not part of its output interface.
|
||||||
|
Bool IsHiddenBlockMember(const glslang::TType* type) {
|
||||||
|
return type != nullptr && type->getBasicType() == glslang::EbtVoid;
|
||||||
|
}
|
||||||
|
|
||||||
void BuildStageIO(ProgramObject& program, const glslang::TProgram& reflection, Model& model) {
|
void BuildStageIO(ProgramObject& program, const glslang::TProgram& reflection, Model& model) {
|
||||||
auto& mutableReflection = const_cast<glslang::TProgram&>(reflection);
|
auto& mutableReflection = const_cast<glslang::TProgram&>(reflection);
|
||||||
|
|
||||||
@@ -359,6 +425,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface {
|
|||||||
for (Int index = 0; index < inputCount; ++index) {
|
for (Int index = 0; index < inputCount; ++index) {
|
||||||
const auto& refl = mutableReflection.getPipeInput(index);
|
const auto& refl = mutableReflection.getPipeInput(index);
|
||||||
const glslang::TType* type = refl.getType();
|
const glslang::TType* type = refl.getType();
|
||||||
|
if (IsHiddenBlockMember(type)) continue;
|
||||||
Resource resource;
|
Resource resource;
|
||||||
// The Vulkan-semantics parse reflects the vertex builtins under their SPIR-V
|
// The Vulkan-semantics parse reflects the vertex builtins under their SPIR-V
|
||||||
// names; GL enumerates the GL spellings.
|
// names; GL enumerates the GL spellings.
|
||||||
@@ -373,18 +440,28 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface {
|
|||||||
model.programInputs.push_back(Move(resource));
|
model.programInputs.push_back(Move(resource));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A color number, and therefore a color INDEX, exists only for a fragment stage's
|
||||||
|
// outputs. The output interface belongs to the program's last stage, so for a
|
||||||
|
// separable tessellation/geometry/vertex program these are varyings: asking the
|
||||||
|
// frag-data maps about them can still answer a location (a tess-control output
|
||||||
|
// carries its own layout(location=N)), and a location then manufactures a color
|
||||||
|
// index of 0 where GL requires -1
|
||||||
|
// (KHR-GL43.program_interface_query.separate-programs-tess-control).
|
||||||
|
const Bool lastStageIsFragment = mutableReflection.getIntermediate(EShLangFragment) != nullptr;
|
||||||
const Int outputCount = mutableReflection.getNumPipeOutputs();
|
const Int outputCount = mutableReflection.getNumPipeOutputs();
|
||||||
for (Int index = 0; index < outputCount; ++index) {
|
for (Int index = 0; index < outputCount; ++index) {
|
||||||
const auto& refl = mutableReflection.getPipeOutput(index);
|
const auto& refl = mutableReflection.getPipeOutput(index);
|
||||||
const glslang::TType* type = refl.getType();
|
const glslang::TType* type = refl.getType();
|
||||||
|
if (IsHiddenBlockMember(type)) continue;
|
||||||
Resource resource;
|
Resource resource;
|
||||||
resource.name = WithArraySuffix(refl.name, type);
|
resource.name = WithArraySuffix(refl.name, type);
|
||||||
resource.type = static_cast<GLenum>(refl.glDefineType);
|
resource.type = static_cast<GLenum>(refl.glDefineType);
|
||||||
resource.arraySize = ArraySizeOf(type, refl.size);
|
resource.arraySize = ArraySizeOf(type, refl.size);
|
||||||
resource.location = MappedLocation(program.GetFragmentDataLocation(refl.name.c_str()));
|
resource.location = MappedLocation(program.GetFragmentDataLocation(refl.name.c_str()));
|
||||||
if (resource.location < 0) {
|
if (resource.location < 0 || !lastStageIsFragment) {
|
||||||
// A built-in output (gl_FragDepth, gl_SampleMask) and a non-fragment stage
|
// A built-in output (gl_FragDepth, gl_SampleMask) has no location, and a
|
||||||
// output both have no location, and therefore no color index either.
|
// non-fragment stage's outputs have no color number at all - either way there
|
||||||
|
// is no color index.
|
||||||
resource.locationIndex = -1;
|
resource.locationIndex = -1;
|
||||||
} else {
|
} else {
|
||||||
resource.locationIndex = program.GetFragmentDataIndex(refl.name.c_str());
|
resource.locationIndex = program.GetFragmentDataIndex(refl.name.c_str());
|
||||||
|
|||||||
@@ -563,8 +563,20 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// - SharedStd140UBO: a DECLARED uniform block is active even when no member is
|
// - SharedStd140UBO: a DECLARED uniform block is active even when no member is
|
||||||
// ever read (reflected from the linker objects). PreprocessShaderSource coerces
|
// ever read (reflected from the linker objects). PreprocessShaderSource coerces
|
||||||
// every block to std140, so this covers all of them.
|
// every block to std140, so this covers all of them.
|
||||||
|
// - IntermediateIO: GL_PROGRAM_INPUT is the input interface of the program's FIRST
|
||||||
|
// stage and GL_PROGRAM_OUTPUT the output interface of its LAST one. Without this
|
||||||
|
// glslang hardcodes those boundaries to vertex/fragment, so a separable program
|
||||||
|
// made of one non-vertex stage has an empty input interface and one made of a
|
||||||
|
// non-fragment stage an empty output interface
|
||||||
|
// (KHR-GL43.program_interface_query.separate-programs-*).
|
||||||
|
// - UnwrapIOBlocks: an inter-stage interface block enumerates as its MEMBERS -
|
||||||
|
// "Color.r", and "gl_Position" for an anonymous gl_PerVertex - not as the block
|
||||||
|
// instance. Only reachable through IntermediateIO: a vertex stage's inputs and a
|
||||||
|
// fragment stage's outputs can never be blocks, so this is inert for a program
|
||||||
|
// whose boundary stages are the hardcoded ones.
|
||||||
if (!artifacts.program->buildReflection(EShReflectionStrictArraySuffix | EShReflectionBasicArraySuffix |
|
if (!artifacts.program->buildReflection(EShReflectionStrictArraySuffix | EShReflectionBasicArraySuffix |
|
||||||
EShReflectionAllBlockVariables | EShReflectionSharedStd140UBO)) {
|
EShReflectionAllBlockVariables | EShReflectionSharedStd140UBO |
|
||||||
|
EShReflectionIntermediateIO | EShReflectionUnwrapIOBlocks)) {
|
||||||
artifacts.linkStatus = false;
|
artifacts.linkStatus = false;
|
||||||
artifacts.infoLog = "Build reflection failed.";
|
artifacts.infoLog = "Build reflection failed.";
|
||||||
DeferLog(std::format("ProgramObject {}: DoReflection - buildReflection() returned false",
|
DeferLog(std::format("ProgramObject {}: DoReflection - buildReflection() returned false",
|
||||||
@@ -852,7 +864,14 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ------------ attributes (vertex in) ---------------
|
// ------------ attributes (vertex in) ---------------
|
||||||
Int inCount = artifacts.program->getNumPipeInputs();
|
// The pipe-input list is the input interface of the program's FIRST stage, which is only
|
||||||
|
// the vertex attribute set when the program actually HAS a vertex stage. A separable
|
||||||
|
// fragment/geometry/tessellation program reflects its own stage inputs here, and those are
|
||||||
|
// varyings - registering them as vertex attributes would hand glGetActiveAttrib and the
|
||||||
|
// attribute location table interstage varyings.
|
||||||
|
Int inCount = artifacts.program->getIntermediate(EShLangVertex) != nullptr
|
||||||
|
? artifacts.program->getNumPipeInputs()
|
||||||
|
: 0;
|
||||||
MGLOG_D("ProgramObject %u: Reflection - pipe input count (attributes) = %d", in.externalIndex, inCount);
|
MGLOG_D("ProgramObject %u: Reflection - pipe input count (attributes) = %d", in.externalIndex, inCount);
|
||||||
|
|
||||||
Int maxLoc = -1;
|
Int maxLoc = -1;
|
||||||
@@ -951,6 +970,11 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
|
|
||||||
Bool ProgramLinkTask::ValidateFragmentOutputLocations() {
|
Bool ProgramLinkTask::ValidateFragmentOutputLocations() {
|
||||||
if (!artifacts.program) return false;
|
if (!artifacts.program) return false;
|
||||||
|
// The pipe-output list is the output interface of the program's LAST stage. Only a
|
||||||
|
// fragment stage's outputs are color numbers indexed against GL_MAX_DRAW_BUFFERS; a
|
||||||
|
// separable vertex/geometry/tessellation program's outputs are varyings, and holding
|
||||||
|
// them to the draw-buffer range fails the link of every such program.
|
||||||
|
if (artifacts.program->getIntermediate(EShLangFragment) == nullptr) return true;
|
||||||
|
|
||||||
UnorderedMap<Int, String> colorNumberOwners;
|
UnorderedMap<Int, String> colorNumberOwners;
|
||||||
const Int outputCount = artifacts.program->getNumPipeOutputs();
|
const Int outputCount = artifacts.program->getNumPipeOutputs();
|
||||||
|
|||||||
@@ -121,12 +121,6 @@ void main() { color = vec4(0, 1, 0, 1); }
|
|||||||
ExpectLinked(p);
|
ExpectLinked(p);
|
||||||
ClearErrors();
|
ClearErrors();
|
||||||
|
|
||||||
// KNOWN GAP, not an expectation: a separable FRAGMENT program's own inputs are not
|
|
||||||
// in the reflection at all - glslang builds the "pipe input" list from the vertex
|
|
||||||
// stage unless EShReflectionIntermediateIO is set, and setting that makes a
|
|
||||||
// vertex-only separable program report its VS outputs as fragment outputs, which
|
|
||||||
// fails ValidateFragmentOutputLocations and breaks glCreateShaderProgramv. So
|
|
||||||
// GL_PROGRAM_INPUT is empty here until that validation is stage-aware.
|
|
||||||
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES), 1);
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES), 1);
|
||||||
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_MAX_NAME_LENGTH), 6);
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_MAX_NAME_LENGTH), 6);
|
||||||
|
|
||||||
@@ -804,6 +798,163 @@ void main(void) {
|
|||||||
EXPECT_EQ(TakeError(), GL_NO_ERROR);
|
EXPECT_EQ(TakeError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------- uniform-block-types --
|
||||||
|
// GL_REFERENCED_BY_*_SHADER is per block INSTANCE. An array of uniform blocks enumerates
|
||||||
|
// one resource per element, and only the elements a stage actually dereferences are
|
||||||
|
// referenced by it - declaring the array is not referencing every element.
|
||||||
|
TEST_F(ProgramInterfaceTest, ArrayedUniformBlockReferencedByIsPerElement) {
|
||||||
|
const char* vs = R"(#version 430
|
||||||
|
in vec4 position;
|
||||||
|
uniform SimpleBlock { mat3x2 a; mat4 b; vec4 c; };
|
||||||
|
void main(void) {
|
||||||
|
float tmp = a[0][1] * b[1][2] * c.x;
|
||||||
|
gl_Position = position * tmp;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
const char* fs = R"(#version 430
|
||||||
|
uniform TrickyBlock { mat4 b; uint c; } e[2];
|
||||||
|
out vec4 color;
|
||||||
|
void main() { color = vec4(0, 1, 0, 1) * e[0].b[0][0]; }
|
||||||
|
)";
|
||||||
|
const GLuint p = MakeProgram(vs, fs);
|
||||||
|
BindAttribLocation(p, 0, "position");
|
||||||
|
BindFragDataLocation(p, 0, "color");
|
||||||
|
LinkProgram(p);
|
||||||
|
ExpectLinked(p);
|
||||||
|
ClearErrors();
|
||||||
|
|
||||||
|
EXPECT_EQ(Interfaceiv(p, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES), 3);
|
||||||
|
ExpectResource(p, GL_UNIFORM_BLOCK, "TrickyBlock[0]", "TrickyBlock[0]");
|
||||||
|
ExpectResource(p, GL_UNIFORM_BLOCK, "TrickyBlock[1]", "TrickyBlock[1]");
|
||||||
|
|
||||||
|
const std::vector<GLenum> refProps = {GL_REFERENCED_BY_VERTEX_SHADER, GL_REFERENCED_BY_FRAGMENT_SHADER};
|
||||||
|
EXPECT_EQ(PropsOf(p, GL_UNIFORM_BLOCK, "SimpleBlock", refProps), (std::vector<GLint>{1, 0}));
|
||||||
|
EXPECT_EQ(PropsOf(p, GL_UNIFORM_BLOCK, "TrickyBlock[0]", refProps), (std::vector<GLint>{0, 1}));
|
||||||
|
EXPECT_EQ(PropsOf(p, GL_UNIFORM_BLOCK, "TrickyBlock[1]", refProps), (std::vector<GLint>{0, 0}))
|
||||||
|
<< "the unreferenced element of a block array must not inherit its sibling's stages";
|
||||||
|
EXPECT_EQ(TakeError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The boundary of the rule above, and the case that caught it on device
|
||||||
|
// (KHR-GL43.program_interface_query.ssb-types): a SHADER STORAGE block array's buffer
|
||||||
|
// variables reflect under ONE subscript-free spelling shared by every element, so a union
|
||||||
|
// over them credits element 0 and starves every other element - even the ones the shader
|
||||||
|
// plainly reads. Storage blocks keep glslang's own mask, and both elements here must report
|
||||||
|
// the fragment stage.
|
||||||
|
TEST_F(ProgramInterfaceTest, ArrayedStorageBlockKeepsGlslangStagesForEveryElement) {
|
||||||
|
const char* vs = R"(#version 430
|
||||||
|
in vec4 position;
|
||||||
|
void main(void) { gl_Position = position; }
|
||||||
|
)";
|
||||||
|
const char* fs = R"(#version 430
|
||||||
|
layout(binding = 4) buffer SimpleStorage { vec4 a; } ss[2];
|
||||||
|
out vec4 color;
|
||||||
|
void main() { color = ss[0].a + ss[1].a; }
|
||||||
|
)";
|
||||||
|
const GLuint p = MakeProgram(vs, fs);
|
||||||
|
BindAttribLocation(p, 0, "position");
|
||||||
|
BindFragDataLocation(p, 0, "color");
|
||||||
|
LinkProgram(p);
|
||||||
|
ExpectLinked(p);
|
||||||
|
ClearErrors();
|
||||||
|
|
||||||
|
const std::vector<GLenum> refProps = {GL_REFERENCED_BY_VERTEX_SHADER, GL_REFERENCED_BY_FRAGMENT_SHADER};
|
||||||
|
for (const char* name : {"SimpleStorage[0]", "SimpleStorage[1]"}) {
|
||||||
|
EXPECT_EQ(PropsOf(p, GL_SHADER_STORAGE_BLOCK, name, refProps), (std::vector<GLint>{0, 1}))
|
||||||
|
<< "for " << name << ": a storage block the fragment stage reads must say so";
|
||||||
|
}
|
||||||
|
EXPECT_EQ(TakeError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------- separate-programs-vertex --
|
||||||
|
// A vertex-only separable program's OUTPUT interface is its own stage outputs, and an
|
||||||
|
// inter-stage block enumerates as its members: "Color.r", and "gl_Position" for the
|
||||||
|
// anonymous gl_PerVertex redeclaration - never the block instance name "vs_color".
|
||||||
|
TEST_F(ProgramInterfaceTest, SeparableVertexProgramEnumeratesItsOwnOutputBlockMembers) {
|
||||||
|
const char* vs = R"(#version 430 core
|
||||||
|
layout(location = 0) in vec4 in_vertex;
|
||||||
|
out Color { float r, g, b; vec4 iLikePie; } vs_color;
|
||||||
|
out gl_PerVertex { vec4 gl_Position; };
|
||||||
|
uniform float u;
|
||||||
|
uniform vec4 v;
|
||||||
|
void main() {
|
||||||
|
gl_Position = in_vertex;
|
||||||
|
vs_color.r = u; vs_color.g = 0.0; vs_color.b = 0.0; vs_color.iLikePie = v;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
const GLuint p = CreateShaderProgramv(GL_VERTEX_SHADER, 1, &vs);
|
||||||
|
ExpectLinked(p);
|
||||||
|
ClearErrors();
|
||||||
|
|
||||||
|
// Exactly 5: the four Color members plus gl_Position. The anonymous gl_PerVertex
|
||||||
|
// redeclaration drops gl_PointSize and gl_ClipDistance, which glslang keeps in the
|
||||||
|
// block type as hidden members rather than erasing.
|
||||||
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES), 1);
|
||||||
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_INPUT, GL_MAX_NAME_LENGTH), 10);
|
||||||
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES), 5);
|
||||||
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_MAX_NAME_LENGTH), 15);
|
||||||
|
|
||||||
|
ExpectResource(p, GL_PROGRAM_INPUT, "in_vertex", "in_vertex");
|
||||||
|
for (const char* name : {"Color.r", "Color.g", "Color.b", "Color.iLikePie", "gl_Position"}) {
|
||||||
|
ExpectResource(p, GL_PROGRAM_OUTPUT, name, name);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(GetProgramResourceIndex(p, GL_PROGRAM_OUTPUT, "vs_color"), GL_INVALID_INDEX)
|
||||||
|
<< "the block instance is not the resource; its members are";
|
||||||
|
|
||||||
|
// A vertex-stage output has no color number, hence no index either, and it is not
|
||||||
|
// per-patch. NAME_LENGTH/TYPE/ARRAY_SIZE come from the member, not the block.
|
||||||
|
EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "Color.iLikePie",
|
||||||
|
{GL_NAME_LENGTH, GL_TYPE, GL_ARRAY_SIZE, GL_REFERENCED_BY_COMPUTE_SHADER,
|
||||||
|
GL_REFERENCED_BY_FRAGMENT_SHADER, GL_REFERENCED_BY_GEOMETRY_SHADER,
|
||||||
|
GL_REFERENCED_BY_TESS_CONTROL_SHADER, GL_REFERENCED_BY_TESS_EVALUATION_SHADER,
|
||||||
|
GL_REFERENCED_BY_VERTEX_SHADER, GL_IS_PER_PATCH, GL_LOCATION_INDEX}),
|
||||||
|
(std::vector<GLint>{15, GL_FLOAT_VEC4, 1, 0, 0, 0, 0, 0, 1, 0, -1}));
|
||||||
|
EXPECT_EQ(TakeError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------- separate-programs-geometry --
|
||||||
|
// Both boundaries are a middle stage here. The input block carries an instance name
|
||||||
|
// (gl_in[]) so its members are prefixed with the BLOCK name, and the arrayed-ness of the
|
||||||
|
// block itself is dropped: "gl_PerVertex.gl_Position", array size 1.
|
||||||
|
TEST_F(ProgramInterfaceTest, SeparableGeometryProgramEnumeratesBothBlockBoundaries) {
|
||||||
|
const char* gs = R"(#version 430
|
||||||
|
layout(triangles) in;
|
||||||
|
layout(triangle_strip, max_vertices = 4) out;
|
||||||
|
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };
|
||||||
|
in gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_in[];
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(-1, 1, 0, 1); EmitVertex();
|
||||||
|
gl_Position = gl_in[0].gl_Position; EmitVertex();
|
||||||
|
EndPrimitive();
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
const GLuint p = CreateShaderProgramv(GL_GEOMETRY_SHADER, 1, &gs);
|
||||||
|
ExpectLinked(p);
|
||||||
|
ClearErrors();
|
||||||
|
|
||||||
|
ExpectResource(p, GL_PROGRAM_INPUT, "gl_PerVertex.gl_Position", "gl_PerVertex.gl_Position");
|
||||||
|
ExpectResource(p, GL_PROGRAM_OUTPUT, "gl_Position", "gl_Position");
|
||||||
|
// A non-fragment stage's outputs are varyings: no color number, so no color index.
|
||||||
|
EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "gl_Position", {GL_LOCATION_INDEX}), (std::vector<GLint>{-1}));
|
||||||
|
|
||||||
|
const std::vector<GLenum> stageProps = {
|
||||||
|
GL_NAME_LENGTH,
|
||||||
|
GL_TYPE,
|
||||||
|
GL_ARRAY_SIZE,
|
||||||
|
GL_REFERENCED_BY_COMPUTE_SHADER,
|
||||||
|
GL_REFERENCED_BY_FRAGMENT_SHADER,
|
||||||
|
GL_REFERENCED_BY_GEOMETRY_SHADER,
|
||||||
|
GL_REFERENCED_BY_TESS_CONTROL_SHADER,
|
||||||
|
GL_REFERENCED_BY_TESS_EVALUATION_SHADER,
|
||||||
|
GL_REFERENCED_BY_VERTEX_SHADER,
|
||||||
|
GL_IS_PER_PATCH};
|
||||||
|
EXPECT_EQ(PropsOf(p, GL_PROGRAM_INPUT, "gl_PerVertex.gl_Position", stageProps),
|
||||||
|
(std::vector<GLint>{25, GL_FLOAT_VEC4, 1, 0, 0, 1, 0, 0, 0, 0}));
|
||||||
|
EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "gl_Position", stageProps),
|
||||||
|
(std::vector<GLint>{12, GL_FLOAT_VEC4, 1, 0, 0, 1, 0, 0, 0, 0}));
|
||||||
|
EXPECT_EQ(TakeError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------- separate-programs-fragment --
|
// ------------------------------------------------------- separate-programs-fragment --
|
||||||
TEST_F(ProgramInterfaceTest, SeparableFragmentProgramSeparatesUniformsFromBufferVariables) {
|
TEST_F(ProgramInterfaceTest, SeparableFragmentProgramSeparatesUniformsFromBufferVariables) {
|
||||||
const char* fs = R"(#version 430
|
const char* fs = R"(#version 430
|
||||||
@@ -817,11 +968,12 @@ void main() { fs_color = vs_color + x + a; }
|
|||||||
ExpectLinked(p);
|
ExpectLinked(p);
|
||||||
ClearErrors();
|
ClearErrors();
|
||||||
|
|
||||||
// KNOWN GAP, not an expectation - the same one SimpleShaders documents: a separable
|
// GL_PROGRAM_INPUT is the input interface of the program's FIRST stage, which for a
|
||||||
// FRAGMENT program's own inputs are absent from the glslang reflection, so
|
// separable fragment program is the fragment stage: "vs_color", name length 9.
|
||||||
// GL_PROGRAM_INPUT is empty. Spec-correct values here would be 1 and 9 ("vs_color").
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES), 1);
|
||||||
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES), 0);
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_INPUT, GL_MAX_NAME_LENGTH), 9);
|
||||||
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_INPUT, GL_MAX_NAME_LENGTH), 0);
|
EXPECT_EQ(GetProgramResourceIndex(p, GL_PROGRAM_INPUT, "vs_color"), 0u);
|
||||||
|
EXPECT_EQ(ResourceName(p, GL_PROGRAM_INPUT, 0), "vs_color");
|
||||||
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES), 1);
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES), 1);
|
||||||
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_MAX_NAME_LENGTH), 9);
|
EXPECT_EQ(Interfaceiv(p, GL_PROGRAM_OUTPUT, GL_MAX_NAME_LENGTH), 9);
|
||||||
// The buffer variable is NOT a uniform, even though the frontend reflection keeps
|
// The buffer variable is NOT a uniform, even though the frontend reflection keeps
|
||||||
|
|||||||
Reference in New Issue
Block a user