mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix] (Program): count the tessellation control stage as a transform-feedback capture stage
This commit is contained in:
@@ -710,8 +710,15 @@ namespace MobileGL::MG_State {
|
|||||||
// must not, or failing the link and killing every draw. A capture stage with an
|
// must not, or failing the link and killing every draw. A capture stage with an
|
||||||
// empty list is not a reason to look further down: it is the answer, and
|
// empty list is not a reason to look further down: it is the answer, and
|
||||||
// glBeginTransformFeedback's INVALID_OPERATION is the correct consequence.
|
// glBeginTransformFeedback's INVALID_OPERATION is the correct consequence.
|
||||||
|
//
|
||||||
|
// The order is the pipeline read backwards and includes the tessellation CONTROL
|
||||||
|
// stage, which is a vertex-processing stage too (GL 4.6 core 11): it can only be
|
||||||
|
// the last one in a pipeline that has a TCS but no evaluation or geometry stage,
|
||||||
|
// which is why it sits after TessEval. Same four stages, same order, as
|
||||||
|
// ProgramLinkTask::ResolveTransformFeedbackVaryings - see rule (2).
|
||||||
for (const ShaderStage captureStage:
|
for (const ShaderStage captureStage:
|
||||||
{ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::Vertex}) {
|
{ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::TessControl,
|
||||||
|
ShaderStage::Vertex}) {
|
||||||
if (!compositeHasStage[static_cast<SizeT>(captureStage)]) continue;
|
if (!compositeHasStage[static_cast<SizeT>(captureStage)]) continue;
|
||||||
const auto& captureProgram = pipeline->GetStageProgram(captureStage);
|
const auto& captureProgram = pipeline->GetStageProgram(captureStage);
|
||||||
if (!captureProgram) continue;
|
if (!captureProgram) continue;
|
||||||
|
|||||||
@@ -1910,10 +1910,18 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture happens at the last vertex-processing stage (geometry, then
|
// Capture happens at the last vertex-processing stage (geometry, then tessellation
|
||||||
// tessellation evaluation, then vertex).
|
// evaluation, then tessellation CONTROL, then vertex). All four are vertex-processing
|
||||||
|
// stages in GL 4.6 core 11 - the control shader included - and in a separable program
|
||||||
|
// whose only stage is a TCS it is the last one that exists, so it is the capture stage
|
||||||
|
// and such a program MUST link (GL 4.6 core 7.3/11.1.2.1; the conformance suite spells
|
||||||
|
// the API split out at esextcTessellationShaderXFB.cpp:390-416, where a non-ES context
|
||||||
|
// takes should_succeed=true). TessControl sits AFTER TessEvaluation so a complete
|
||||||
|
// pipeline still captures at the evaluation stage and only a TCS-only program falls
|
||||||
|
// through to it. If MobileGL ever serves an ES context this arm has to be gated on the
|
||||||
|
// advertised API: ES requires the very same link to FAIL.
|
||||||
const glslang::TIntermediate* captureIntermediate = nullptr;
|
const glslang::TIntermediate* captureIntermediate = nullptr;
|
||||||
for (EShLanguage stage : {EShLangGeometry, EShLangTessEvaluation, EShLangVertex}) {
|
for (EShLanguage stage : {EShLangGeometry, EShLangTessEvaluation, EShLangTessControl, EShLangVertex}) {
|
||||||
captureIntermediate = artifacts.program->getIntermediate(stage);
|
captureIntermediate = artifacts.program->getIntermediate(stage);
|
||||||
if (captureIntermediate != nullptr) {
|
if (captureIntermediate != nullptr) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -554,7 +554,8 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// asked for is the safer of the two readings.
|
// asked for is the safer of the two readings.
|
||||||
if (task->in.requestedXfbVaryings.empty()) {
|
if (task->in.requestedXfbVaryings.empty()) {
|
||||||
for (const ShaderStage captureStage:
|
for (const ShaderStage captureStage:
|
||||||
{ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::Vertex}) {
|
{ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::TessControl,
|
||||||
|
ShaderStage::Vertex}) {
|
||||||
Bool stagePresent = false;
|
Bool stagePresent = false;
|
||||||
for (const auto& shader : m_shaders) {
|
for (const auto& shader : m_shaders) {
|
||||||
if (!shader || shader->GetShaderStage() != captureStage) continue;
|
if (!shader || shader->GetShaderStage() != captureStage) continue;
|
||||||
|
|||||||
@@ -180,6 +180,22 @@ target_link_libraries(
|
|||||||
${LINK_LIBRARIES}
|
${LINK_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
TessellationLinkTest
|
||||||
|
TessellationLinkTest.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(TessellationLinkTest PRIVATE
|
||||||
|
${MGL_ROOT}/include
|
||||||
|
${MGL_ROOT}/MobileGL
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
TessellationLinkTest PRIVATE
|
||||||
|
GTest::gtest_main
|
||||||
|
${LINK_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(
|
add_executable(
|
||||||
ProgramPipelineCompositeTest
|
ProgramPipelineCompositeTest
|
||||||
ProgramPipelineCompositeTest.cpp
|
ProgramPipelineCompositeTest.cpp
|
||||||
@@ -229,6 +245,7 @@ gtest_discover_tests(ProgramTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
|||||||
gtest_discover_tests(ProgramInterfaceTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
gtest_discover_tests(ProgramInterfaceTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||||
gtest_discover_tests(ProgramPipelineCompositeTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
gtest_discover_tests(ProgramPipelineCompositeTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||||
gtest_discover_tests(XfbBlockVaryingTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
gtest_discover_tests(XfbBlockVaryingTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||||
|
gtest_discover_tests(TessellationLinkTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||||
# Heavier than the rest of the unit suite by design: several cases deliberately saturate the
|
# Heavier than the rest of the unit suite by design: several cases deliberately saturate the
|
||||||
# compile pool so there is something in flight to race against.
|
# compile pool so there is something in flight to race against.
|
||||||
gtest_discover_tests(AsyncCompileTest DISCOVERY_TIMEOUT 60 PROPERTIES LABELS unit TIMEOUT 300)
|
gtest_discover_tests(AsyncCompileTest DISCOVERY_TIMEOUT 60 PROPERTIES LABELS unit TIMEOUT 300)
|
||||||
|
|||||||
@@ -0,0 +1,288 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Test/Program/TessellationLinkTest.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
|
||||||
|
|
||||||
|
// Link-time properties of programs that carry a tessellation control stage. GPU-free:
|
||||||
|
// everything asserted here is a property of the link, not of any driver.
|
||||||
|
//
|
||||||
|
// Two independent defects live here, both found by KHR-GL4x.tessellation_shader:
|
||||||
|
//
|
||||||
|
// (1) The transform-feedback capture stage. GL 4.6 core 11 makes the tessellation CONTROL
|
||||||
|
// shader a vertex-processing stage like the other three, so in a separable program whose
|
||||||
|
// only stage is a TCS it is the LAST vertex-processing stage and therefore the capture
|
||||||
|
// stage - such a program must link with transform-feedback varyings requested. MobileGL
|
||||||
|
// searched {geometry, tessellation evaluation, vertex} only and refused the link with
|
||||||
|
// "Transform feedback varyings requested but the program has no vertex-processing stage",
|
||||||
|
// failing KHR-GL4x.tessellation_shader.single.xfb_captures_data_from_correct_stage on all
|
||||||
|
// three API versions (esextcTessellationShaderXFB.cpp:390-416 passes should_succeed=true
|
||||||
|
// for a non-ES context; ES demands the opposite, which is why the new arm is documented as
|
||||||
|
// desktop-GL-only at the search site).
|
||||||
|
//
|
||||||
|
// (2) `patch out T name[N]` against `patch in T name[N]`. Legal, identically spelled on both
|
||||||
|
// sides, and rejected - see the last case, which carries the diagnosis.
|
||||||
|
|
||||||
|
#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 TessellationLinkTest: public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
MobileGL::Initialize();
|
||||||
|
for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string ShaderLog(GLuint shader) {
|
||||||
|
char log[4096] = "";
|
||||||
|
GetShaderInfoLog(shader, sizeof(log), nullptr, log);
|
||||||
|
return std::string(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attaches one compiled shader of each requested stage. Compilation is asserted, so a
|
||||||
|
// failure here is a shader bug in the test rather than a link result.
|
||||||
|
GLuint MakeProgram(const std::vector<std::pair<GLenum, const char*>>& stages, Bool separable) {
|
||||||
|
const GLuint program = CreateProgram();
|
||||||
|
if (separable) {
|
||||||
|
ProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
|
||||||
|
}
|
||||||
|
for (const auto& [type, source]: stages) {
|
||||||
|
const GLuint shader = CreateShader(type);
|
||||||
|
ShaderSource(shader, 1, &source, nullptr);
|
||||||
|
CompileShader(shader);
|
||||||
|
GLint compiled = GL_FALSE;
|
||||||
|
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
|
||||||
|
EXPECT_EQ(compiled, GL_TRUE) << "stage 0x" << std::hex << type << "\n" << ShaderLog(shader);
|
||||||
|
AttachShader(program, shader);
|
||||||
|
}
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The conformance suite's own tessellation control shader
|
||||||
|
// (esextcTessellationShaderXFB.cpp:360-381), with the ES-only ${...} expansions dropped -
|
||||||
|
// on a desktop context they expand to nothing.
|
||||||
|
constexpr const char* kCtsTessControl = R"(#version 460 core
|
||||||
|
layout (vertices=4) out;
|
||||||
|
|
||||||
|
in BLOCK_INOUT { vec4 value; } user_in[];
|
||||||
|
out BLOCK_INOUT { vec4 value; } user_out[];
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_out [gl_InvocationID].gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
user_out [gl_InvocationID].value = vec4(2.0, 3.0, 4.0, 5.0);
|
||||||
|
|
||||||
|
gl_TessLevelOuter[0] = 1.0;
|
||||||
|
gl_TessLevelOuter[1] = 1.0;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
// A tessellation control shader IS a vertex-processing stage (GL 4.6 core 11), and in a
|
||||||
|
// TCS-only separable program it is the last one - so it is the capture stage and the link
|
||||||
|
// must succeed with the block member resolved against ITS outputs.
|
||||||
|
TEST_F(TessellationLinkTest, TcsOnlySeparableProgramWithXfbVaryingsLinks) {
|
||||||
|
const GLuint program = MakeProgram({{GL_TESS_CONTROL_SHADER, kCtsTessControl}}, /*separable=*/true);
|
||||||
|
const GLchar* const varyings[1] = {"BLOCK_INOUT.value"};
|
||||||
|
TransformFeedbackVaryings(program, 1, varyings, GL_SEPARATE_ATTRIBS);
|
||||||
|
LinkProgram(program);
|
||||||
|
|
||||||
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
||||||
|
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_NO_ERROR));
|
||||||
|
|
||||||
|
// The request resolved rather than being quietly dropped: the interface reports it back.
|
||||||
|
EXPECT_EQ(Programiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS), 1);
|
||||||
|
EXPECT_EQ(Programiv(program, GL_TRANSFORM_FEEDBACK_BUFFER_MODE), GL_SEPARATE_ATTRIBS);
|
||||||
|
|
||||||
|
GLchar name[128] = {'\0'};
|
||||||
|
GLsizei length = 0;
|
||||||
|
GLsizei size = 0;
|
||||||
|
GLenum type = 0;
|
||||||
|
GetTransformFeedbackVarying(program, 0, sizeof(name), &length, &size, &type, name);
|
||||||
|
EXPECT_EQ(std::string(name, name + (length < 0 ? 0 : length)), "BLOCK_INOUT.value");
|
||||||
|
EXPECT_EQ(type, static_cast<GLenum>(GL_FLOAT_VEC4));
|
||||||
|
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_NO_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control: the same program with no capture request. It linked before the fix too, which is
|
||||||
|
// what keeps this case honest about WHICH half of the link moved.
|
||||||
|
TEST_F(TessellationLinkTest, TcsOnlySeparableProgramWithoutXfbVaryingsLinks) {
|
||||||
|
const GLuint program = MakeProgram({{GL_TESS_CONTROL_SHADER, kCtsTessControl}}, /*separable=*/true);
|
||||||
|
LinkProgram(program);
|
||||||
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
||||||
|
EXPECT_EQ(Programiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS), 0);
|
||||||
|
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_NO_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A program with no vertex-processing stage at all still has to be refused - the fix widened
|
||||||
|
// the search, it did not remove the check.
|
||||||
|
TEST_F(TessellationLinkTest, FragmentOnlySeparableProgramWithXfbVaryingsStillFailsToLink) {
|
||||||
|
constexpr const char* fs = R"(#version 460 core
|
||||||
|
out vec4 color;
|
||||||
|
void main() { color = vec4(1.0); }
|
||||||
|
)";
|
||||||
|
const GLuint program = MakeProgram({{GL_FRAGMENT_SHADER, fs}}, /*separable=*/true);
|
||||||
|
const GLchar* const varyings[1] = {"color"};
|
||||||
|
TransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
|
||||||
|
LinkProgram(program);
|
||||||
|
EXPECT_EQ(Programiv(program, GL_LINK_STATUS), GL_FALSE);
|
||||||
|
EXPECT_NE(LinkLog(program).find("no vertex-processing stage"), std::string::npos) << LinkLog(program);
|
||||||
|
for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const char* kPassthroughVs = R"(#version 460 core
|
||||||
|
void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }
|
||||||
|
)";
|
||||||
|
|
||||||
|
constexpr const char* kTcsWithPatchScalar = R"(#version 460 core
|
||||||
|
layout (vertices = 3) out;
|
||||||
|
patch out vec4 tcs_patch;
|
||||||
|
out vec4 tcs_per_vertex[];
|
||||||
|
void main() {
|
||||||
|
tcs_patch = vec4(1.0);
|
||||||
|
tcs_per_vertex[gl_InvocationID] = vec4(2.0);
|
||||||
|
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
|
||||||
|
gl_TessLevelOuter[0] = 1.0; gl_TessLevelOuter[1] = 1.0; gl_TessLevelOuter[2] = 1.0;
|
||||||
|
gl_TessLevelInner[0] = 1.0;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
constexpr const char* kTesWithPatchScalar = R"(#version 460 core
|
||||||
|
layout (triangles) in;
|
||||||
|
patch in vec4 tcs_patch;
|
||||||
|
in vec4 tcs_per_vertex[];
|
||||||
|
out vec4 tes_out;
|
||||||
|
void main() {
|
||||||
|
tes_out = tcs_patch + tcs_per_vertex[0];
|
||||||
|
gl_Position = gl_in[0].gl_Position;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
// The capture stage of a COMPLETE pipeline is unchanged by the widened search: tessellation
|
||||||
|
// control sits AFTER tessellation evaluation in the order, so a program that has both still
|
||||||
|
// resolves its capture names against the EVALUATION stage's outputs. Both halves are pinned -
|
||||||
|
// an evaluation output resolves, a control output does not.
|
||||||
|
TEST_F(TessellationLinkTest, CompletePipelineStillCapturesAtTheEvaluationStage) {
|
||||||
|
{
|
||||||
|
const GLuint program = MakeProgram({{GL_VERTEX_SHADER, kPassthroughVs},
|
||||||
|
{GL_TESS_CONTROL_SHADER, kTcsWithPatchScalar},
|
||||||
|
{GL_TESS_EVALUATION_SHADER, kTesWithPatchScalar}},
|
||||||
|
/*separable=*/true);
|
||||||
|
const GLchar* const varyings[1] = {"tes_out"};
|
||||||
|
TransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
|
||||||
|
LinkProgram(program);
|
||||||
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
||||||
|
EXPECT_EQ(Programiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS), 1);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// tcs_per_vertex is an output of the CONTROL stage, which is not the capture stage
|
||||||
|
// here. Resolving it would mean capturing at the wrong stage, so the link must fail.
|
||||||
|
const GLuint program = MakeProgram({{GL_VERTEX_SHADER, kPassthroughVs},
|
||||||
|
{GL_TESS_CONTROL_SHADER, kTcsWithPatchScalar},
|
||||||
|
{GL_TESS_EVALUATION_SHADER, kTesWithPatchScalar}},
|
||||||
|
/*separable=*/true);
|
||||||
|
const GLchar* const varyings[1] = {"tcs_per_vertex"};
|
||||||
|
TransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
|
||||||
|
LinkProgram(program);
|
||||||
|
EXPECT_EQ(Programiv(program, GL_LINK_STATUS), GL_FALSE) << LinkLog(program);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A patch-qualified SCALAR crosses the TCS/TES boundary today. It is the control for the
|
||||||
|
// array case below: same qualifier, same stages, only the arrayness differs.
|
||||||
|
TEST_F(TessellationLinkTest, PatchQualifiedScalarLinksAcrossTheTessellationStages) {
|
||||||
|
const GLuint program = MakeProgram({{GL_VERTEX_SHADER, kPassthroughVs},
|
||||||
|
{GL_TESS_CONTROL_SHADER, kTcsWithPatchScalar},
|
||||||
|
{GL_TESS_EVALUATION_SHADER, kTesWithPatchScalar}},
|
||||||
|
/*separable=*/true);
|
||||||
|
LinkProgram(program);
|
||||||
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << LinkLog(program);
|
||||||
|
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_NO_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
// `patch out int a[N]` against `patch in int a[N]`: legal GLSL, identical spellings, and
|
||||||
|
// currently refused by the VENDORED glslang with "Array sizes must be compatible" while
|
||||||
|
// printing the two sides as the same type. This is what fails
|
||||||
|
// KHR-GL4x.tessellation_shader.tessellation_shader_tc_barriers.* on all three API versions.
|
||||||
|
//
|
||||||
|
// The defect is one asymmetric clause in glslang, not in MobileGL:
|
||||||
|
// 3rdparty/glslang/glslang/MachineIndependent/linkValidate.cpp, TIntermediate::isIoResizeArray.
|
||||||
|
// The TessControl arm is guarded with `&& ! type.getQualifier().patch`; the TessEvaluation arm
|
||||||
|
// is not. A patch-qualified array therefore answers false on the control side and true on the
|
||||||
|
// evaluation side, and the caller's dimension arithmetic (linkValidate.cpp:1200-1218) computes
|
||||||
|
// (numDim - firstDim) == (unitNumDim - unitFirstDim) as (1 - 0) == (1 - 1), i.e. false. The fix
|
||||||
|
// is to mirror the control arm - add `&& ! type.getQualifier().patch` to the TessEvaluation
|
||||||
|
// clause - which belongs in the fork (and upstream), so it is deliberately not made here.
|
||||||
|
//
|
||||||
|
// Written to flip from skip to assertion the moment that lands: the skip is allowed ONLY for
|
||||||
|
// the exact known message, and any other failure is a real failure.
|
||||||
|
TEST_F(TessellationLinkTest, PatchQualifiedArrayLinksAcrossTheTessellationStages) {
|
||||||
|
constexpr const char* tcs = R"(#version 460 core
|
||||||
|
layout (vertices = 3) out;
|
||||||
|
patch out int tcs_patch_result[16];
|
||||||
|
void main() {
|
||||||
|
for (int i = 0; i < 16; ++i) { tcs_patch_result[i] = i; }
|
||||||
|
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
|
||||||
|
gl_TessLevelOuter[0] = 1.0; gl_TessLevelOuter[1] = 1.0; gl_TessLevelOuter[2] = 1.0;
|
||||||
|
gl_TessLevelInner[0] = 1.0;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
constexpr const char* tes = R"(#version 460 core
|
||||||
|
layout (triangles) in;
|
||||||
|
patch in int tcs_patch_result[16];
|
||||||
|
out vec4 tes_out;
|
||||||
|
void main() {
|
||||||
|
tes_out = vec4(float(tcs_patch_result[0] + tcs_patch_result[15]));
|
||||||
|
gl_Position = gl_in[0].gl_Position;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
const GLuint program = MakeProgram({{GL_VERTEX_SHADER, kPassthroughVs},
|
||||||
|
{GL_TESS_CONTROL_SHADER, tcs},
|
||||||
|
{GL_TESS_EVALUATION_SHADER, tes}},
|
||||||
|
/*separable=*/true);
|
||||||
|
LinkProgram(program);
|
||||||
|
const std::string log = LinkLog(program);
|
||||||
|
if (Programiv(program, GL_LINK_STATUS) != GL_TRUE &&
|
||||||
|
log.find("Array sizes must be compatible") != std::string::npos) {
|
||||||
|
for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {
|
||||||
|
}
|
||||||
|
GTEST_SKIP() << "vendored glslang still credits the TessEvaluation side of a "
|
||||||
|
"patch-qualified array with an implicit outer dimension "
|
||||||
|
"(linkValidate.cpp isIoResizeArray, TessEvaluation arm missing "
|
||||||
|
"`&& ! type.getQualifier().patch`); link log:\n"
|
||||||
|
<< log;
|
||||||
|
}
|
||||||
|
ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) << log;
|
||||||
|
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_NO_ERROR));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user