mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
223 lines
8.7 KiB
C++
223 lines
8.7 KiB
C++
// MobileGL - MobileGL/MG_Test/Program/XfbBlockVaryingTest.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
// Transform-feedback capture of a member of an output interface block.
|
|
//
|
|
// GL 4.6 core 11.1.2.1 names such a varying "<BLOCK name>.<member>" - the block's TYPE
|
|
// name, never the instance name - which is exactly what KHR-GL4x.vertex_attrib_binding
|
|
// (gl4cVertexAttribBindingTests.cpp:419-437, `out StageData { vec4 attrib[16]; } vs_out;`
|
|
// captured as "StageData.attrib[0]".."[15]") relies on. The resolver used to match the
|
|
// requested name against glslang's linker-object symbol name, which for a block is the
|
|
// INSTANCE ("vs_out"), so every one of those captures came back unresolved and the link
|
|
// failed with "is not an output of the vertex stage" + GL_INVALID_VALUE.
|
|
//
|
|
// GPU-free: everything asserted here is a property of the link, not of any driver.
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "Includes.h"
|
|
#include "Init.h"
|
|
#include "MG_Impl/GLImpl/Getter/GL_Getter.h"
|
|
#include "MG_Impl/GLImpl/Program/GL_Program.h"
|
|
#include "MG_State/GLState/Core.h"
|
|
|
|
using namespace MobileGL;
|
|
using namespace MobileGL::MG_Impl::GLImpl;
|
|
|
|
namespace {
|
|
class XfbBlockVaryingTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override { MobileGL::Initialize(); }
|
|
};
|
|
|
|
GLuint MakeVsOnlyProgram(const char* vs) {
|
|
const GLuint program = CreateProgram();
|
|
const GLuint shader = CreateShader(GL_VERTEX_SHADER);
|
|
ShaderSource(shader, 1, &vs, nullptr);
|
|
CompileShader(shader);
|
|
GLint compiled = GL_FALSE;
|
|
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
|
|
EXPECT_EQ(compiled, GL_TRUE) << [&] {
|
|
char log[4096] = "";
|
|
GetShaderInfoLog(shader, sizeof(log), nullptr, log);
|
|
return std::string(log);
|
|
}();
|
|
AttachShader(program, shader);
|
|
return program;
|
|
}
|
|
|
|
std::string LinkLog(GLuint program) {
|
|
char log[4096] = "";
|
|
GetProgramInfoLog(program, sizeof(log), nullptr, log);
|
|
return std::string(log);
|
|
}
|
|
|
|
GLint Programiv(GLuint program, GLenum pname) {
|
|
GLint value = -1;
|
|
GetProgramiv(program, pname, &value);
|
|
return value;
|
|
}
|
|
|
|
struct VaryingRecord {
|
|
std::string name;
|
|
GLsizei size = 0;
|
|
GLenum type = 0;
|
|
};
|
|
|
|
VaryingRecord Varying(GLuint program, GLuint index) {
|
|
VaryingRecord record;
|
|
GLchar buffer[256] = {'\0'};
|
|
GLsizei length = 0;
|
|
GetTransformFeedbackVarying(program, index, sizeof(buffer), &length, &record.size, &record.type, buffer);
|
|
record.name.assign(buffer, buffer + (length < 0 ? 0 : length));
|
|
return record;
|
|
}
|
|
|
|
void ClearErrors() {
|
|
for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {
|
|
}
|
|
}
|
|
|
|
// The CTS shader, narrowed to two elements so the expectations stay readable.
|
|
const char* kNamedBlockVs = R"(#version 430 core
|
|
layout(location = 0) in vec4 vs_in_attrib[2];
|
|
out StageData {
|
|
vec4 attrib[2];
|
|
} vs_out;
|
|
void main() {
|
|
for (int i = 0; i < vs_in_attrib.length(); ++i) {
|
|
vs_out.attrib[i] = vs_in_attrib[i];
|
|
}
|
|
}
|
|
)";
|
|
|
|
TEST_F(XfbBlockVaryingTest, CapturesBlockMemberElementsByBlockTypeName) {
|
|
ClearErrors();
|
|
const GLuint program = MakeVsOnlyProgram(kNamedBlockVs);
|
|
const GLchar* const varyings[2] = {"StageData.attrib[0]", "StageData.attrib[1]"};
|
|
TransformFeedbackVaryings(program, 2, varyings, GL_INTERLEAVED_ATTRIBS);
|
|
LinkProgram(program);
|
|
|
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
|
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
|
EXPECT_EQ(Programiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS), 2);
|
|
EXPECT_EQ(Programiv(program, GL_TRANSFORM_FEEDBACK_BUFFER_MODE), GL_INTERLEAVED_ATTRIBS);
|
|
|
|
for (GLuint i = 0; i < 2; ++i) {
|
|
const VaryingRecord record = Varying(program, i);
|
|
EXPECT_EQ(record.name, std::string("StageData.attrib[") + std::to_string(i) + "]");
|
|
// One element of the member array, not the whole array.
|
|
EXPECT_EQ(record.size, 1) << "index " << i;
|
|
EXPECT_EQ(record.type, static_cast<GLenum>(GL_FLOAT_VEC4)) << "index " << i;
|
|
}
|
|
}
|
|
|
|
// The whole member, no subscript: the array size has to survive.
|
|
TEST_F(XfbBlockVaryingTest, CapturesAWholeBlockMemberArray) {
|
|
ClearErrors();
|
|
const GLuint program = MakeVsOnlyProgram(kNamedBlockVs);
|
|
const GLchar* const varyings[1] = {"StageData.attrib"};
|
|
TransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
|
|
LinkProgram(program);
|
|
|
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
|
const VaryingRecord record = Varying(program, 0);
|
|
EXPECT_EQ(record.name, "StageData.attrib");
|
|
EXPECT_EQ(record.size, 2);
|
|
EXPECT_EQ(record.type, static_cast<GLenum>(GL_FLOAT_VEC4));
|
|
}
|
|
|
|
// Members of an anonymous instance are named the same way - the block name is still
|
|
// what identifies them, and there is no instance name to fall back on.
|
|
TEST_F(XfbBlockVaryingTest, CapturesAnonymousInstanceBlockMember) {
|
|
ClearErrors();
|
|
const GLuint program = MakeVsOnlyProgram(R"(#version 430 core
|
|
layout(location = 0) in vec4 vs_in_attrib;
|
|
out StageData {
|
|
vec4 color;
|
|
vec2 uv;
|
|
};
|
|
void main() {
|
|
color = vs_in_attrib;
|
|
uv = vs_in_attrib.xy;
|
|
}
|
|
)");
|
|
const GLchar* const varyings[2] = {"StageData.color", "StageData.uv"};
|
|
TransformFeedbackVaryings(program, 2, varyings, GL_INTERLEAVED_ATTRIBS);
|
|
LinkProgram(program);
|
|
|
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
|
EXPECT_EQ(Varying(program, 0).type, static_cast<GLenum>(GL_FLOAT_VEC4));
|
|
EXPECT_EQ(Varying(program, 1).type, static_cast<GLenum>(GL_FLOAT_VEC2));
|
|
}
|
|
|
|
// The instance-qualified spelling is not what the spec asks for, but it is what a lot of
|
|
// application code writes; resolving it too costs nothing and keeps those links alive.
|
|
TEST_F(XfbBlockVaryingTest, AlsoAcceptsTheInstanceQualifiedSpelling) {
|
|
ClearErrors();
|
|
const GLuint program = MakeVsOnlyProgram(kNamedBlockVs);
|
|
const GLchar* const varyings[1] = {"vs_out.attrib[1]"};
|
|
TransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
|
|
LinkProgram(program);
|
|
|
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
|
EXPECT_EQ(Varying(program, 0).size, 1);
|
|
EXPECT_EQ(Varying(program, 0).type, static_cast<GLenum>(GL_FLOAT_VEC4));
|
|
}
|
|
|
|
// A dotted path that resolves to nothing must still fail the link, and say so - the
|
|
// fix must not turn "unknown member" into a silently dropped capture.
|
|
TEST_F(XfbBlockVaryingTest, RejectsAnUnknownBlockMember) {
|
|
ClearErrors();
|
|
const GLuint program = MakeVsOnlyProgram(kNamedBlockVs);
|
|
const GLchar* const varyings[1] = {"StageData.missing"};
|
|
TransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
|
|
LinkProgram(program);
|
|
|
|
EXPECT_EQ(Programiv(program, GL_LINK_STATUS), GL_FALSE);
|
|
EXPECT_NE(LinkLog(program).find("StageData.missing"), std::string::npos) << LinkLog(program);
|
|
}
|
|
|
|
TEST_F(XfbBlockVaryingTest, RejectsAnUnknownBlock) {
|
|
ClearErrors();
|
|
const GLuint program = MakeVsOnlyProgram(kNamedBlockVs);
|
|
const GLchar* const varyings[1] = {"NoSuchBlock.attrib[0]"};
|
|
TransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
|
|
LinkProgram(program);
|
|
|
|
EXPECT_EQ(Programiv(program, GL_LINK_STATUS), GL_FALSE);
|
|
}
|
|
|
|
// Plain (non-block) outputs must keep resolving exactly as before.
|
|
TEST_F(XfbBlockVaryingTest, StillResolvesPlainOutputs) {
|
|
ClearErrors();
|
|
const GLuint program = MakeVsOnlyProgram(R"(#version 430 core
|
|
layout(location = 0) in vec4 vs_in_attrib;
|
|
out vec4 plain[2];
|
|
out vec3 single;
|
|
void main() {
|
|
plain[0] = vs_in_attrib;
|
|
plain[1] = vs_in_attrib;
|
|
single = vs_in_attrib.xyz;
|
|
}
|
|
)");
|
|
const GLchar* const varyings[3] = {"plain[1]", "single", "gl_Position"};
|
|
TransformFeedbackVaryings(program, 3, varyings, GL_INTERLEAVED_ATTRIBS);
|
|
LinkProgram(program);
|
|
|
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
|
EXPECT_EQ(Varying(program, 0).size, 1);
|
|
EXPECT_EQ(Varying(program, 0).type, static_cast<GLenum>(GL_FLOAT_VEC4));
|
|
EXPECT_EQ(Varying(program, 1).type, static_cast<GLenum>(GL_FLOAT_VEC3));
|
|
EXPECT_EQ(Varying(program, 2).type, static_cast<GLenum>(GL_FLOAT_VEC4));
|
|
}
|
|
} // namespace
|