blob: d042728eb0527a49d2388ee2a0440d7e40734bc8 [file] [log] [blame] [edit]
//
// Copyright 2025 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.
//
#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#include "test_utils/CompilerTest.h"
#include "test_utils/angle_test_configs.h"
#include "test_utils/gl_raii.h"
using namespace angle;
namespace
{
class GLSLOutputTest : public CompilerTest
{
protected:
GLSLOutputTest() {}
// Helper to create a shader
void compileShader(GLenum shaderType, const char *shaderSource)
{
const CompiledShader &shader = compile(shaderType, shaderSource);
EXPECT_TRUE(shader.success());
}
// Check that the output contains (or not) the expected substring.
void verifyIsInTranslation(GLenum shaderType, const char *expect)
{
EXPECT_TRUE(getCompiledShader(shaderType).verifyInTranslatedSource(expect)) << expect;
}
void verifyIsNotInTranslation(GLenum shaderType, const char *expect)
{
EXPECT_TRUE(getCompiledShader(shaderType).verifyNotInTranslatedSource(expect)) << expect;
}
void verifyCountInTranslation(GLenum shaderType, const char *expect, size_t expectCount)
{
EXPECT_TRUE(
getCompiledShader(shaderType).verifyCountInTranslatedSource(expect, expectCount))
<< expectCount << "x " << expect;
}
};
class GLSLOutputGLSLTest : public GLSLOutputTest
{};
class GLSLOutputGLSLTest_ES3 : public GLSLOutputTest
{};
class WebGLGLSLOutputGLSLTest : public GLSLOutputGLSLTest
{
protected:
WebGLGLSLOutputGLSLTest() { setWebGLCompatibilityEnabled(true); }
};
class WebGL2GLSLOutputGLSLTest : public GLSLOutputGLSLTest_ES3
{
protected:
WebGL2GLSLOutputGLSLTest() { setWebGLCompatibilityEnabled(true); }
};
// Verifies that without explicitly enabling GL_EXT_draw_buffers extension in the shader, no
// broadcast emulation.
TEST_P(GLSLOutputGLSLTest, FragColorNoBroadcast)
{
constexpr char kFS[] = R"(void main()
{
gl_FragColor = vec4(1, 0, 0, 0);
})";
compileShader(GL_FRAGMENT_SHADER, kFS);
verifyIsInTranslation(GL_FRAGMENT_SHADER, "gl_FragColor");
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[0]");
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[1]");
}
// Verifies that with explicitly enabling GL_EXT_draw_buffers extension
// in the shader, broadcast is emualted by replacing gl_FragColor with gl_FragData.
TEST_P(GLSLOutputGLSLTest, FragColorBroadcast)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
void main()
{
gl_FragColor = vec4(1, 0, 0, 0);
})";
compileShader(GL_FRAGMENT_SHADER, kFS);
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "gl_FragColor");
verifyIsInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[0]");
verifyIsInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[1]");
}
// Verifies that with explicitly enabling GL_EXT_draw_buffers extension
// in the shader with an empty main(), nothing happens.
TEST_P(GLSLOutputGLSLTest, EmptyMain)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
void main()
{
})";
compileShader(GL_FRAGMENT_SHADER, kFS);
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "gl_FragColor");
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[0]");
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[1]");
}
// Test the initialization of output variables with various qualifiers in a vertex shader.
TEST_P(WebGL2GLSLOutputGLSLTest, OutputAllQualifiers)
{
constexpr char kVS[] = R"(#version 300 es
precision mediump float;
precision lowp int;
out vec4 out1;
flat out int out2;
centroid out float out3;
smooth out float out4;
void main() {
out1.x += 0.0001;
out2 += 1;
out3 += 0.0001;
out4 += 0.0001;
})";
compileShader(GL_VERTEX_SHADER, kVS);
verifyIsInTranslation(GL_VERTEX_SHADER, "gl_Position = vec4(0.0, 0.0, 0.0, 0.0)");
verifyIsInTranslation(GL_VERTEX_SHADER, "_uout1 = vec4(0.0, 0.0, 0.0, 0.0)");
verifyIsInTranslation(GL_VERTEX_SHADER, "_uout2 = 0");
verifyIsInTranslation(GL_VERTEX_SHADER, "_uout3 = 0.0");
verifyIsInTranslation(GL_VERTEX_SHADER, "_uout4 = 0.0");
}
// Test the initialization of an output array in a vertex shader.
TEST_P(WebGL2GLSLOutputGLSLTest, OutputArray)
{
constexpr char kVS[] = R"(#version 300 es
precision mediump float;
out float out1[2];
void main() {
out1[0] += 0.0001;
})";
compileShader(GL_VERTEX_SHADER, kVS);
verifyIsInTranslation(GL_VERTEX_SHADER, "_uout1[0] = 0.0");
verifyIsInTranslation(GL_VERTEX_SHADER, "_uout1[1] = 0.0");
}
// Test the initialization of a struct output variable in a vertex shader.
TEST_P(WebGL2GLSLOutputGLSLTest, OutputStruct)
{
constexpr char kVS[] = R"(#version 300 es
precision mediump float;
struct MyS{
float a;
float b;
};
out MyS out1;
void main() {
out1.a += 0.0001;
})";
compileShader(GL_VERTEX_SHADER, kVS);
verifyIsInTranslation(GL_VERTEX_SHADER, "_uout1 = _uMyS(");
}
// Test the initialization of a varying variable in an ESSL1 vertex shader.
TEST_P(WebGL2GLSLOutputGLSLTest, OutputFromESSL1Shader)
{
constexpr char kVS[] = R"(precision mediump float;
varying vec4 out1;
void main() {
out1.x += 0.0001;
})";
compileShader(GL_VERTEX_SHADER, kVS);
verifyIsInTranslation(GL_VERTEX_SHADER, "gl_Position = vec4(0.0, 0.0, 0.0, 0.0)");
verifyIsInTranslation(GL_VERTEX_SHADER, "_uout1 = vec4(0.0, 0.0, 0.0, 0.0)");
}
// Test the initialization of output variables in a fragment shader.
TEST_P(WebGL2GLSLOutputGLSLTest, FragmentOutput)
{
constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 out1;
void main() {
out1.x += 0.0001;
})";
compileShader(GL_FRAGMENT_SHADER, kFS);
verifyIsInTranslation(GL_FRAGMENT_SHADER, "_uout1 = vec4(0.0, 0.0, 0.0, 0.0)");
}
// Test the initialization of gl_FragData in a WebGL2 ESSL1 fragment shader. Only writes to
// gl_FragData[0] should be found.
TEST_P(WebGL2GLSLOutputGLSLTest, FragData)
{
constexpr char kFS[] = R"(precision mediump float;
void main() {
gl_FragData[0] = vec4(1.);
})";
compileShader(GL_FRAGMENT_SHADER, kFS);
verifyIsInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[0] = vec4(0.0, 0.0, 0.0, 0.0)");
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[1]");
}
// Test the initialization of gl_FragData in a WebGL1 ESSL1 fragment shader. Only writes to
// gl_FragData[0] should be found.
TEST_P(WebGLGLSLOutputGLSLTest, FragData)
{
constexpr char kFS[] = R"(precision mediump float;
void main() {
gl_FragData[0] = vec4(1.);
})";
compileShader(GL_FRAGMENT_SHADER, kFS);
verifyIsInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[0] = vec4(0.0, 0.0, 0.0, 0.0)");
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[1]");
}
// Test the initialization of gl_FragData in a WebGL1 ESSL1 fragment shader with GL_EXT_draw_buffers
// enabled. All attachment slots should be initialized.
TEST_P(WebGLGLSLOutputGLSLTest, FragDataWithDrawBuffersExtEnabled)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : enable
precision mediump float;
void main() {
gl_FragData[0] = vec4(1.);
})";
compileShader(GL_FRAGMENT_SHADER, kFS);
verifyIsInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[0] = vec4(0.0, 0.0, 0.0, 0.0)");
verifyIsInTranslation(GL_FRAGMENT_SHADER, "gl_FragData[1] = vec4(0.0, 0.0, 0.0, 0.0)");
}
// Test that gl_Position is initialized once in case it is not statically used and both
// initOutputVariables (by webgl) and initGLPosition (by webgl, but also the GL backend) flags are
// set.
TEST_P(WebGL2GLSLOutputGLSLTest, InitGLPositionWhenNotStaticallyUsed)
{
constexpr char kVS[] = R"(#version 300 es
precision highp float;
void main() {
})";
compileShader(GL_VERTEX_SHADER, kVS);
verifyCountInTranslation(GL_VERTEX_SHADER, "gl_Position = vec4(0.0, 0.0, 0.0, 0.0)", 1);
}
// Test that gl_Position is initialized once in case it is statically used and both
// initOutputVariables (by webgl) and initGLPosition (by webgl, but also the GL backend) flags are
// set.
TEST_P(WebGL2GLSLOutputGLSLTest, InitGLPositionOnceWhenStaticallyUsed)
{
constexpr char kVS[] = R"(#version 300 es
precision highp float;
void main() {
gl_Position = vec4(1.0);
})";
compileShader(GL_VERTEX_SHADER, kVS);
verifyCountInTranslation(GL_VERTEX_SHADER, "gl_Position = vec4(0.0, 0.0, 0.0, 0.0)", 1);
}
class GLSLOutputGLSLTest_InitShaderVariables : public GLSLOutputGLSLTest
{};
// Mirrors ClipDistanceTest.ThreeClipDistancesRedeclared
TEST_P(GLSLOutputGLSLTest_InitShaderVariables, RedeclareClipDistance)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
constexpr char kVS[] = R"(#extension GL_APPLE_clip_distance : require
varying highp float gl_ClipDistance[3];
void computeClipDistances(in vec4 position, in vec4 plane[3])
{
gl_ClipDistance[0] = dot(position, plane[0]);
gl_ClipDistance[1] = dot(position, plane[1]);
gl_ClipDistance[2] = dot(position, plane[2]);
}
uniform vec4 u_plane[3];
attribute vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
computeClipDistances(gl_Position, u_plane);
})";
compileShader(GL_VERTEX_SHADER, kVS);
verifyIsInTranslation(GL_VERTEX_SHADER, "gl_Position = vec4(0.0, 0.0, 0.0, 0.0)");
verifyIsInTranslation(GL_VERTEX_SHADER, "gl_ClipDistance[0] = 0.0");
verifyIsInTranslation(GL_VERTEX_SHADER, "gl_ClipDistance[1] = 0.0");
verifyIsInTranslation(GL_VERTEX_SHADER, "gl_ClipDistance[2] = 0.0");
}
class GLSLOutputGLSLVerifyIRUseTest : public GLSLOutputGLSLTest
{};
// A basic test that makes sure the `useIr` feature is actually effective.
TEST_P(GLSLOutputGLSLVerifyIRUseTest, Basic)
{
constexpr char kFS[] = R"(void main()
{
})";
compileShader(GL_FRAGMENT_SHADER, kFS);
// With AST, implicit `return` remains implicit. With IR, every block ends in a branch, so the
// `return` is explicit.
if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
{
verifyIsInTranslation(GL_FRAGMENT_SHADER, "return");
}
else
{
verifyIsNotInTranslation(GL_FRAGMENT_SHADER, "return");
}
}
} // namespace
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLOutputGLSLTest);
ANGLE_INSTANTIATE_TEST(GLSLOutputGLSLTest, ES2_OPENGL(), ES2_OPENGLES());
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLOutputGLSLTest_ES3);
ANGLE_INSTANTIATE_TEST(GLSLOutputGLSLTest_ES3, ES3_OPENGL(), ES3_OPENGLES());
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WebGLGLSLOutputGLSLTest);
ANGLE_INSTANTIATE_TEST(WebGLGLSLOutputGLSLTest, ES2_OPENGL(), ES2_OPENGLES());
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WebGL2GLSLOutputGLSLTest);
ANGLE_INSTANTIATE_TEST(WebGL2GLSLOutputGLSLTest, ES3_OPENGL(), ES3_OPENGLES());
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLOutputGLSLTest_InitShaderVariables);
ANGLE_INSTANTIATE_TEST(GLSLOutputGLSLTest_InitShaderVariables,
ES2_OPENGL().enable(Feature::ForceInitShaderVariables),
ES2_OPENGLES().enable(Feature::ForceInitShaderVariables));
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLOutputGLSLVerifyIRUseTest);
ANGLE_INSTANTIATE_TEST(GLSLOutputGLSLVerifyIRUseTest,
ES2_OPENGL(),
ES2_OPENGLES(),
ES2_OPENGL().disable(Feature::UseIr),
ES2_OPENGLES().disable(Feature::UseIr));