// // Copyright 2017 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // GeometryShader_test.cpp: // tests for compiling a Geometry Shader // #include "GLSLANG/ShaderLang.h" #include "angle_gl.h" #include "compiler/translator/BaseTypes.h" #include "gtest/gtest.h" #include "tests/test_utils/ShaderCompileTreeTest.h" #include "tests/test_utils/compiler_test.h" using namespace sh; class GeometryShaderTest : public ShaderCompileTreeTest { public: GeometryShaderTest() {} protected: void initResources(ShBuiltInResources *resources) override { resources->EXT_geometry_shader = 1; } ::GLenum getShaderType() const override { return GL_GEOMETRY_SHADER_EXT; } ShShaderSpec getShaderSpec() const override { return SH_GLES3_1_SPEC; } bool compileGeometryShader(const std::string &statement1, const std::string &statement2) { std::ostringstream sstream; sstream << kHeader << statement1 << statement2 << kEmptyBody; return compile(sstream.str()); } bool compileGeometryShader(const std::string &statement1, const std::string &statement2, const std::string &statement3, const std::string &statement4) { std::ostringstream sstream; sstream << kHeader << statement1 << statement2 << statement3 << statement4 << kEmptyBody; return compile(sstream.str()); } static std::string GetGeometryShaderLayout(const std::string &layoutType, const std::string &primitive, int invocations, int maxVertices) { std::ostringstream sstream; sstream << "layout ("; if (!primitive.empty()) { sstream << primitive; } if (invocations > 0) { sstream << ", invocations = " << invocations; } if (maxVertices >= 0) { sstream << ", max_vertices = " << maxVertices; } sstream << ") " << layoutType << ";" << std::endl; return sstream.str(); } static std::string GetInputDeclaration(const std::string &var, int size) { std::ostringstream sstream; sstream << "in "; if (size < 0) { sstream << var << "[];\n"; } else { sstream << var << "[" << size << "];\n"; } return sstream.str(); } const std::string kVersion = "#version 310 es\n"; const std::string kHeader = "#version 310 es\n" "#extension GL_EXT_geometry_shader : require\n"; const std::string kInputLayout = "layout (points) in;\n"; const std::string kOutputLayout = "layout (points, max_vertices = 1) out;\n"; const std::array kInterpolationQualifiers = {{"flat", "smooth", "centroid"}}; const std::map kInputPrimitivesAndInputArraySizeMap = { {"points", 1}, {"lines", 2}, {"lines_adjacency", 4}, {"triangles", 3}, {"triangles_adjacency", 6}}; const std::string kEmptyBody = "void main()\n" "{\n" "}\n"; }; class GeometryShaderOutputCodeTest : public MatchOutputCodeTest { public: GeometryShaderOutputCodeTest() : MatchOutputCodeTest(GL_GEOMETRY_SHADER_EXT, SH_OBJECT_CODE, SH_ESSL_OUTPUT) { getResources()->EXT_geometry_shader = 1; } }; // Geometry Shaders are not supported in GLSL ES shaders version lower than 310. TEST_F(GeometryShaderTest, Version300) { const std::string &shaderString = R"(#version 300 es void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders are not supported in GLSL ES shaders version 310 without extension // EXT_geometry_shader enabled. TEST_F(GeometryShaderTest, Version310WithoutExtension) { const std::string &shaderString = R"(#version 310 es void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders are not supported in GLSL ES shaders version 310 with extension // EXT_geometry_shader disabled. TEST_F(GeometryShaderTest, Version310ExtensionDisabled) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : disable void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders are supported in GLSL ES shaders version 310 with EXT_geometry_shader enabled. TEST_F(GeometryShaderTest, Version310WithEXTExtension) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout(points) in; layout(points, max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Missing the declaration of input primitive in a geometry shader should be a link error instead of // a compile error. TEST_F(GeometryShaderTest, NoInputPrimitives) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout(points, max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders can only support 5 kinds of input primitives, which cannot be used as output // primitives except 'points'. // Skip testing "points" as it can be used as both input and output primitives. TEST_F(GeometryShaderTest, ValidInputPrimitives) { const std::array kInputPrimitives = { {"lines", "lines_adjacency", "triangles", "triangles_adjacency"}}; for (const std::string &inputPrimitive : kInputPrimitives) { if (!compileGeometryShader(GetGeometryShaderLayout("in", inputPrimitive, -1, -1), kOutputLayout)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } if (compileGeometryShader(kInputLayout, GetGeometryShaderLayout("out", inputPrimitive, -1, 6))) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } } // Geometry Shaders allow duplicated declaration of input primitive, but all of them must be same. TEST_F(GeometryShaderTest, RedeclareInputPrimitives) { const std::array kInputPrimitives = { {"points", "lines", "lines_adjacency", "triangles", "triangles_adjacency"}}; for (GLuint i = 0; i < kInputPrimitives.size(); ++i) { const std::string &inputLayoutStr1 = GetGeometryShaderLayout("in", kInputPrimitives[i], -1, -1); if (!compileGeometryShader(inputLayoutStr1, inputLayoutStr1, kOutputLayout, "")) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } for (GLuint j = i + 1; j < kInputPrimitives.size(); ++j) { const std::string &inputLayoutStr2 = GetGeometryShaderLayout("in", kInputPrimitives[j], -1, -1); if (compileGeometryShader(inputLayoutStr1, inputLayoutStr2, kOutputLayout, "")) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } } } // Geometry Shaders don't allow declaring different input primitives in one layout. TEST_F(GeometryShaderTest, DeclareDifferentInputPrimitivesInOneLayout) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, triangles) in; layout (points, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders don't allow 'invocations' < 1. TEST_F(GeometryShaderTest, InvocationsLessThanOne) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 0) in; layout (points, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders allow declaring 'invocations' == 1 together with input primitive declaration in // one layout. TEST_F(GeometryShaderTest, InvocationsEqualsOne) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 1) in; layout (points, max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders allow declaring 'invocations' == 1 in an individual layout. TEST_F(GeometryShaderTest, InvocationsEqualsOneInSeparatedLayout) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (invocations = 1) in; layout (points, max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders don't allow 'invocations' larger than the implementation-dependent maximum value // (32 in this test). TEST_F(GeometryShaderTest, TooLargeInvocations) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 9989899) in; layout (points, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders allow 'invocations' declared together with input primitives in one layout. TEST_F(GeometryShaderTest, InvocationsDeclaredWithInputPrimitives) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 3) in; layout (points, max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders allow 'invocations' declared before input primitives in one input layout. TEST_F(GeometryShaderTest, InvocationsBeforeInputPrimitives) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (invocations = 3, points) in; layout (points, max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders allow 'invocations' declared in an individual input layout. TEST_F(GeometryShaderTest, InvocationsInIndividualLayout) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (invocations = 3) in; layout (points, max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders allow duplicated 'invocations' declarations. TEST_F(GeometryShaderTest, DuplicatedInvocations) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 3) in; layout (invocations = 3) in; layout (points, max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders don't allow multiple different 'invocations' declarations in different // layouts. TEST_F(GeometryShaderTest, RedeclareDifferentInvocations) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 3) in; layout (invocations = 5) in; layout (points, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders don't allow multiple different 'invocations' declarations in different // layouts. TEST_F(GeometryShaderTest, RedeclareDifferentInvocationsAfterInvocationEqualsOne) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 1) in; layout (invocations = 5) in; layout (points, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders don't allow multiple different 'invocations' declarations in one layout. TEST_F(GeometryShaderTest, RedeclareDifferentInvocationsInOneLayout) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 3, invocations = 5) in; layout (points, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders don't allow 'invocations' in out layouts. TEST_F(GeometryShaderTest, DeclareInvocationsInOutLayout) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, invocations = 3, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders don't allow 'invocations' in layouts without 'in' qualifier. TEST_F(GeometryShaderTest, DeclareInvocationsInLayoutNoQualifier) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (invocations = 3); layout (points, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders allow declaring output primitive before input primitive declaration. TEST_F(GeometryShaderTest, DeclareOutputPrimitiveBeforeInputPrimitiveDeclare) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, max_vertices = 1) out; layout (points) in; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders allow declaring 'max_vertices' before output primitive in one output layout. TEST_F(GeometryShaderTest, DeclareMaxVerticesBeforeOutputPrimitive) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (max_vertices = 1, points) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Missing the declaration of output primitive should be a link error instead of a compile error in // a geometry shader. TEST_F(GeometryShaderTest, NoOutputPrimitives) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders can only support 3 kinds of output primitives, which cannot be used as input // primitives except 'points'. // Skip testing "points" as it can be used as both input and output primitives. TEST_F(GeometryShaderTest, ValidateOutputPrimitives) { const std::string outputPrimitives[] = {"line_strip", "triangle_strip"}; for (const std::string &outPrimitive : outputPrimitives) { if (!compileGeometryShader(kInputLayout, GetGeometryShaderLayout("out", outPrimitive, -1, 6))) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } if (compileGeometryShader(GetGeometryShaderLayout("in", outPrimitive, -1, -1), kOutputLayout)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } } // Geometry Shaders allow duplicated output primitive declarations, but all of them must be same. TEST_F(GeometryShaderTest, RedeclareOutputPrimitives) { const std::array outPrimitives = {{"points", "line_strip", "triangle_strip"}}; for (GLuint i = 0; i < outPrimitives.size(); i++) { constexpr int maxVertices = 1; const std::string &outputLayoutStr1 = GetGeometryShaderLayout("out", outPrimitives[i], -1, maxVertices); const std::string &outputLayoutStr2 = GetGeometryShaderLayout("out", outPrimitives[i], -1, -1); if (!compileGeometryShader(kInputLayout, outputLayoutStr1, outputLayoutStr2, "")) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } for (GLuint j = i + 1; j < outPrimitives.size(); j++) { const std::string &outputLayoutStr3 = GetGeometryShaderLayout("out", outPrimitives[j], -1, -1); if (compileGeometryShader(kInputLayout, outputLayoutStr1, outputLayoutStr3, "")) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } } } // Geometry Shaders don't allow declaring different output primitives in one layout. TEST_F(GeometryShaderTest, RedeclareDifferentOutputPrimitivesInOneLayout) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 3, line_strip) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Missing the declarations of output primitives and 'max_vertices' in a geometry shader should // be a link error instead of a compile error. TEST_F(GeometryShaderTest, NoOutLayouts) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Missing the declarations of 'max_vertices' in a geometry shader should be a link error // instead of a compile error. TEST_F(GeometryShaderTest, NoMaxVertices) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders cannot declare a negative 'max_vertices'. TEST_F(GeometryShaderTest, NegativeMaxVertices) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = -1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders allow max_vertices == 0. TEST_F(GeometryShaderTest, ZeroMaxVertices) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 0) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders cannot declare a 'max_vertices' that is greater than // MAX_GEOMETRY_OUTPUT_VERTICES_EXT (256 in this test). TEST_F(GeometryShaderTest, TooLargeMaxVertices) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 257) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders can declare 'max_vertices' in an individual out layout. TEST_F(GeometryShaderTest, MaxVerticesInIndividualLayout) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points) out; layout (max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders allow duplicated 'max_vertices' declarations. TEST_F(GeometryShaderTest, DuplicatedMaxVertices) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 1) out; layout (max_vertices = 1) out; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders don't allow declaring different 'max_vertices'. TEST_F(GeometryShaderTest, RedeclareDifferentMaxVertices) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 1) out; layout (max_vertices = 2) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders don't allow declaring different 'max_vertices'. TEST_F(GeometryShaderTest, RedeclareDifferentMaxVerticesInOneLayout) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2, max_vertices = 1) out; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders don't allow 'location' declared with input/output primitives in one layout. TEST_F(GeometryShaderTest, InvalidLocation) { const std::string &shaderString1 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, location = 1) in; layout (points, max_vertices = 2) out; void main() { })"; const std::string &shaderString2 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (invocations = 2, location = 1) in; layout (points, max_vertices = 2) out; void main() { })"; const std::string &shaderString3 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, location = 3, max_vertices = 2) out; void main() { })"; const std::string &shaderString4 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points) out; layout (max_vertices = 2, location = 3) out; void main() { })"; if (compile(shaderString1) || compile(shaderString2) || compile(shaderString3) || compile(shaderString4)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders don't allow invalid layout qualifier declarations. TEST_F(GeometryShaderTest, InvalidLayoutQualifiers) { const std::string &shaderString1 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, abc) in; layout (points, max_vertices = 2) out; void main() { })"; const std::string &shaderString2 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, abc, max_vertices = 2) out; void main() { })"; const std::string &shaderString3 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, xyz = 2) in; layout (points, max_vertices = 2) out; void main() { })"; const std::string &shaderString4 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points) out; layout (max_vertices = 2, xyz = 3) out; void main() { })"; if (compile(shaderString1) || compile(shaderString2) || compile(shaderString3) || compile(shaderString4)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that indexing an array with a constant integer on gl_in is legal. TEST_F(GeometryShaderTest, IndexGLInByConstantInteger) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; void main() { vec4 position; position = gl_in[0].gl_Position; })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that indexing an array with an integer variable on gl_in is legal. TEST_F(GeometryShaderTest, IndexGLInByVariable) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (lines) in; layout (points, max_vertices = 2) out; void main() { vec4 position; for (int i = 0; i < 2; i++) { position = gl_in[i].gl_Position; } })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that indexing an array on gl_in without input primitive declaration causes a compile // error. TEST_F(GeometryShaderTest, IndexGLInWithoutInputPrimitive) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, max_vertices = 2) out; void main() { vec4 position = gl_in[0].gl_Position; })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that using gl_in.length() without input primitive declaration causes a compile error. TEST_F(GeometryShaderTest, UseGLInLengthWithoutInputPrimitive) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, max_vertices = 2) out; void main() { int length = gl_in.length(); })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that using gl_in.length() with input primitive declaration can compile. TEST_F(GeometryShaderTest, UseGLInLengthWithInputPrimitive) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; void main() { gl_Position = vec4(gl_in.length()); EmitVertex(); })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that gl_in[].gl_Position cannot be l-value. TEST_F(GeometryShaderTest, AssignValueToGLIn) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; void main() { gl_in[0].gl_Position = vec4(0, 0, 0, 1); })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify Geometry Shader supports all required built-in variables. TEST_F(GeometryShaderTest, BuiltInVariables) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 2) in; layout (points, max_vertices = 2) out; void main() { gl_Position = gl_in[gl_InvocationID].gl_Position; int invocation = gl_InvocationID; gl_Layer = invocation; int primitiveIn = gl_PrimitiveIDIn; gl_PrimitiveID = primitiveIn; })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that gl_PrimitiveIDIn cannot be l-value. TEST_F(GeometryShaderTest, AssignValueToGLPrimitiveIn) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 2) in; layout (points, max_vertices = 2) out; void main() { gl_PrimitiveIDIn = 1; })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that gl_InvocationID cannot be l-value. TEST_F(GeometryShaderTest, AssignValueToGLInvocations) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, invocations = 2) in; layout (points, max_vertices = 2) out; void main() { gl_InvocationID = 1; })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that both EmitVertex() and EndPrimitive() are supported in Geometry Shader. TEST_F(GeometryShaderTest, GeometryShaderBuiltInFunctions) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; void main() { gl_Position = gl_in[0].gl_Position; EmitVertex(); EndPrimitive(); })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that using EmitVertex() or EndPrimitive() without GL_EXT_geometry_shader declared causes a // compile error. TEST_F(GeometryShaderTest, GeometryShaderBuiltInFunctionsWithoutExtension) { const std::string &shaderString1 = R"(#version 310 es void main() { EmitVertex(); })"; const std::string &shaderString2 = R"(#version 310 es void main() { EndPrimitive(); })"; if (compile(shaderString1) || compile(shaderString2)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that all required built-in constant values are supported in Geometry Shaders TEST_F(GeometryShaderTest, GeometryShaderBuiltInConstants) { const std::string &kShaderHeader = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; void main() { gl_Position.x = float()"; const std::array kGeometryShaderBuiltinConstants = {{ "gl_MaxGeometryInputComponents", "gl_MaxGeometryOutputComponents", "gl_MaxGeometryImageUniforms", "gl_MaxGeometryTextureImageUnits", "gl_MaxGeometryOutputVertices", "gl_MaxGeometryTotalOutputComponents", "gl_MaxGeometryUniformComponents", "gl_MaxGeometryAtomicCounters", "gl_MaxGeometryAtomicCounterBuffers", }}; const std::string &kShaderTail = R"(); EmitVertex(); })"; for (const std::string &kGSBuiltinConstant : kGeometryShaderBuiltinConstants) { std::ostringstream ostream; ostream << kShaderHeader << kGSBuiltinConstant << kShaderTail; if (!compile(ostream.str())) { FAIL() << "Shader compilation failed, expecting success: \n" << mInfoLog; } } } // Verify that using any Geometry Shader built-in constant values without GL_EXT_geometry_shader // declared causes a compile error. TEST_F(GeometryShaderTest, GeometryShaderBuiltInConstantsWithoutExtension) { const std::string &kShaderHeader = "#version 310 es\n" "void main()\n" "{\n" " int val = "; const std::array kGeometryShaderBuiltinConstants = {{ "gl_MaxGeometryInputComponents", "gl_MaxGeometryOutputComponents", "gl_MaxGeometryImageUniforms", "gl_MaxGeometryTextureImageUnits", "gl_MaxGeometryOutputVertices", "gl_MaxGeometryTotalOutputComponents", "gl_MaxGeometryUniformComponents", "gl_MaxGeometryAtomicCounters", "gl_MaxGeometryAtomicCounterBuffers", }}; const std::string &kShaderTail = ";\n" "}\n"; for (const std::string &kGSBuiltinConstant : kGeometryShaderBuiltinConstants) { std::ostringstream ostream; ostream << kShaderHeader << kGSBuiltinConstant << kShaderTail; if (compile(ostream.str())) { FAIL() << "Shader compilation succeeded, expecting failure: \n" << mInfoLog; } } } // Verify that Geometry Shaders cannot accept non-array inputs. TEST_F(GeometryShaderTest, NonArrayInput) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 1) out; in vec4 texcoord; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that it is a compile error to declare an unsized Geometry Shader input before a valid // input primitive declaration. TEST_F(GeometryShaderTest, DeclareUnsizedInputBeforeInputPrimitive) { const std::string &shaderString1 = R"(#version 310 es #extension GL_EXT_geometry_shader : require in vec4 texcoord[]; layout (points) in; layout (points, max_vertices = 1) out; void main() { vec4 coord = texcoord[0]; int length = texcoord.length(); })"; const std::string &shaderString2 = R"(#version 310 es #extension GL_EXT_geometry_shader : require in vec4 texcoord1[1]; in vec4 texcoord2[]; layout (points) in; layout (points, max_vertices = 1) out; void main() { vec4 coord = texcoord2[0]; int length = texcoord2.length(); })"; if (compile(shaderString1) || compile(shaderString2)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that it is a compile error to declare an unsized Geometry Shader input without a valid // input primitive declaration. TEST_F(GeometryShaderTest, DeclareUnsizedInputWithoutInputPrimitive) { const std::string &shaderString1 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, max_vertices = 1) out; in vec4 texcoord[]; void main() { })"; const std::string &shaderString2 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points, max_vertices = 1) out; in vec4 texcoord1[1]; in vec4 texcoord2[]; void main() { })"; if (compile(shaderString1) || compile(shaderString2)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that indexing an unsized Geometry Shader input which is declared after a // valid input primitive declaration can compile. TEST_F(GeometryShaderTest, IndexingUnsizedInputDeclaredAfterInputPrimitive) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 1) out; in vec4 texcoord[], texcoord2[]; in vec4[] texcoord3, texcoord4; void main() { gl_Position = texcoord[0] + texcoord2[0] + texcoord3[0] + texcoord4[0]; EmitVertex(); })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that calling length() function on an unsized Geometry Shader input which // is declared before a valid input primitive declaration can compile. TEST_F(GeometryShaderTest, CallingLengthOnUnsizedInputDeclaredAfterInputPrimitive) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 1) out; in vec4 texcoord[]; void main() { gl_Position = vec4(texcoord.length()); EmitVertex(); })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that assigning a value to the input of a geometry shader causes a compile error. TEST_F(GeometryShaderTest, AssignValueToInput) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 1) out; in vec4 texcoord[]; void main() { texcoord[0] = vec4(1.0, 0.0, 0.0, 1.0); })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Geometry Shaders allow inputs with location qualifier. TEST_F(GeometryShaderTest, InputWithLocations) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (triangles) in; layout (points, max_vertices = 1) out; layout (location = 0) in vec4 texcoord1[]; layout (location = 1) in vec4 texcoord2[]; void main() { int index = 0; vec4 coord1 = texcoord1[0]; vec4 coord2 = texcoord2[index]; gl_Position = coord1 + coord2; })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders allow inputs with explicit size declared before the declaration of the // input primitive, but they should have same size and match the declaration of the // following input primitive declarations. TEST_F(GeometryShaderTest, InputWithSizeBeforeInputPrimitive) { for (auto &primitiveAndArraySize : kInputPrimitivesAndInputArraySizeMap) { const std::string &inputLayoutStr = GetGeometryShaderLayout("in", primitiveAndArraySize.first, -1, -1); const int inputSize = primitiveAndArraySize.second; const std::string &inputDeclaration1 = GetInputDeclaration("vec4 input1", inputSize); if (!compileGeometryShader(inputDeclaration1, "", inputLayoutStr, kOutputLayout)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } const std::string &inputDeclaration2 = GetInputDeclaration("vec4 input2", inputSize + 1); if (compileGeometryShader(inputDeclaration2, "", inputLayoutStr, kOutputLayout) || compileGeometryShader(inputDeclaration1, inputDeclaration2, inputLayoutStr, kOutputLayout)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } } // Geometry shaders allow inputs with explicit size declared after the declaration of the // input primitive, but their sizes should match the previous input primitive declaration. TEST_F(GeometryShaderTest, InputWithSizeAfterInputPrimitive) { for (auto &primitiveAndArraySize : kInputPrimitivesAndInputArraySizeMap) { const std::string &inputLayoutStr = GetGeometryShaderLayout("in", primitiveAndArraySize.first, -1, -1); const int inputSize = primitiveAndArraySize.second; const std::string &inputDeclaration1 = GetInputDeclaration("vec4 input1", inputSize); if (!compileGeometryShader(inputLayoutStr, kOutputLayout, inputDeclaration1, "")) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } const std::string &inputDeclaration2 = GetInputDeclaration("vec4 input2", inputSize + 1); if (compileGeometryShader(inputLayoutStr, kOutputLayout, inputDeclaration2, "")) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } } // Verify that Geometry Shaders accept non-array outputs. TEST_F(GeometryShaderTest, NonArrayOutputs) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 1) out; out vec4 color; void main() { })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that Geometry Shaders allow declaring outputs with 'location' layout qualifier. TEST_F(GeometryShaderTest, OutputsWithLocation) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (triangles) in; layout (points, max_vertices = 1) out; layout (location = 0) out vec4 color1; layout (location = 1) out vec4 color2; void main() { color1 = vec4(0.0, 1.0, 0.0, 1.0); color2 = vec4(1.0, 0.0, 0.0, 1.0); })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Geometry Shaders allow declaring sized array outputs. TEST_F(GeometryShaderTest, SizedArrayOutputs) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (triangles) in; layout (points, max_vertices = 1) out; out vec4 color[2]; void main() { color[0] = vec4(0.0, 1.0, 0.0, 1.0); color[1] = vec4(1.0, 0.0, 0.0, 1.0); })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that Geometry Shader outputs cannot be declared as an unsized array. TEST_F(GeometryShaderTest, UnsizedArrayOutputs) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (triangles) in; layout (points, max_vertices = 1) out; out vec4 color[]; void main() { })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } } // Verify that Geometry Shader inputs can use interpolation qualifiers. TEST_F(GeometryShaderTest, InputWithInterpolationQualifiers) { for (const std::string &qualifier : kInterpolationQualifiers) { std::ostringstream stream; stream << kHeader << kInputLayout << kOutputLayout << qualifier << " in vec4 texcoord[];\n" << kEmptyBody; if (!compile(stream.str())) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } } // Verify that Geometry Shader outputs can use interpolation qualifiers. TEST_F(GeometryShaderTest, OutputWithInterpolationQualifiers) { for (const std::string &qualifier : kInterpolationQualifiers) { std::ostringstream stream; stream << kHeader << kInputLayout << kOutputLayout << qualifier << " out vec4 color;\n" << kEmptyBody; if (!compile(stream.str())) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } } // Verify that Geometry Shader outputs can use 'invariant' qualifier. TEST_F(GeometryShaderTest, InvariantOutput) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; invariant out vec4 gs_output; void main() { gl_Position = gl_in[0].gl_Position; })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that the member of gl_in won't be incorrectly changed in the output shader string. TEST_F(GeometryShaderOutputCodeTest, ValidateGLInMembersInOutputShaderString) { const std::string &shaderString1 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (lines) in; layout (points, max_vertices = 2) out; void main() { vec4 position; for (int i = 0; i < 2; i++) { position = gl_in[i].gl_Position; } })"; compile(shaderString1); EXPECT_TRUE(foundInESSLCode("].gl_Position")); const std::string &shaderString2 = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; void main() { vec4 position; position = gl_in[0].gl_Position; })"; compile(shaderString2); EXPECT_TRUE(foundInESSLCode("].gl_Position")); } // Verify that geometry shader inputs can be declared as struct arrays. TEST_F(GeometryShaderTest, StructArrayInput) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; struct S { float value1; vec4 value2; }; in S gs_input[]; out S gs_output; void main() { gs_output = gs_input[0]; })"; if (!compile(shaderString)) { FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog; } } // Verify that geometry shader outputs cannot be declared as struct arrays. TEST_F(GeometryShaderTest, StructArrayOutput) { const std::string &shaderString = R"(#version 310 es #extension GL_EXT_geometry_shader : require layout (points) in; layout (points, max_vertices = 2) out; struct S { float value1; vec4 value2; }; out S gs_output[1]; void main() { gs_output[0].value1 = 1.0; gs_output[0].value2 = vec4(1.0, 0.0, 0.0, 1.0); })"; if (compile(shaderString)) { FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; } }