| // | |
| // 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. | |
| // | |
| // EGLContextPassthroughShadersTest.cpp: | |
| // Tests of the EGL_ANGLE_create_context_passthrough_shaders extension. | |
| // | |
| #include <gtest/gtest.h> | |
| #include "GLES2/gl2.h" | |
| #include "test_utils/ANGLETest.h" | |
| #include "test_utils/angle_test_instantiate.h" | |
| #include "test_utils/angle_test_platform.h" | |
| #include "test_utils/gl_raii.h" | |
| #include "util/gles_loader_autogen.h" | |
| using namespace angle; | |
| class EGLContextPassthroughShadersTest : public ANGLETest<> | |
| { | |
| public: | |
| EGLContextPassthroughShadersTest() : mDisplay(EGL_NO_DISPLAY) {} | |
| void testSetUp() override | |
| { | |
| EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), | |
| EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | |
| static_cast<EGLAttrib>(GetPbufferOnlyDefaultPlatformType()), | |
| EGL_NONE}; | |
| mDisplay = eglGetPlatformDisplay(GetEglPlatform(), | |
| reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs); | |
| EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY); | |
| EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr) != EGL_FALSE); | |
| EGLint attribs[] = {EGL_RED_SIZE, | |
| 8, | |
| EGL_GREEN_SIZE, | |
| 8, | |
| EGL_BLUE_SIZE, | |
| 8, | |
| EGL_ALPHA_SIZE, | |
| 8, | |
| EGL_RENDERABLE_TYPE, | |
| EGL_OPENGL_ES2_BIT, | |
| EGL_SURFACE_TYPE, | |
| EGL_PBUFFER_BIT, | |
| EGL_NONE}; | |
| EGLint count = 0; | |
| EXPECT_EGL_TRUE(eglChooseConfig(mDisplay, attribs, &mConfig, 1, &count)); | |
| ANGLE_SKIP_TEST_IF(mConfig == EGL_NO_CONFIG_KHR); | |
| EXPECT_GT(count, 0); | |
| EGLint pBufferAttribs[] = {EGL_WIDTH, 32, EGL_HEIGHT, 32, EGL_NONE}; | |
| mSurface = eglCreatePbufferSurface(mDisplay, mConfig, pBufferAttribs); | |
| EXPECT_NE(mSurface, EGL_NO_SURFACE); | |
| } | |
| void testTearDown() override | |
| { | |
| if (mDisplay != EGL_NO_DISPLAY) | |
| { | |
| eglTerminate(mDisplay); | |
| eglReleaseThread(); | |
| mDisplay = EGL_NO_DISPLAY; | |
| } | |
| ASSERT_EGL_SUCCESS() << "Error during test TearDown"; | |
| } | |
| bool supportsPassthroughShadersExtension() | |
| { | |
| return IsEGLDisplayExtensionEnabled(mDisplay, | |
| "EGL_ANGLE_create_context_passthrough_shaders"); | |
| } | |
| EGLDisplay mDisplay; | |
| EGLConfig mConfig = EGL_NO_CONFIG_KHR; | |
| EGLSurface mSurface; | |
| }; | |
| // Test creating a context with passthrough shaders enabled and verify by querying translated | |
| // shaders source | |
| TEST_P(EGLContextPassthroughShadersTest, CreateContext) | |
| { | |
| ANGLE_SKIP_TEST_IF(!supportsPassthroughShadersExtension()); | |
| EGLint ctxAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_CONTEXT_PASSTHROUGH_SHADERS_ANGLE, | |
| EGL_TRUE, EGL_NONE}; | |
| EGLContext context = eglCreateContext(mDisplay, mConfig, nullptr, ctxAttribs); | |
| EXPECT_NE(context, EGL_NO_CONTEXT); | |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, context)); | |
| constexpr const char kFragmentShader[] = R"( | |
| precision highp float; | |
| uniform sampler2D tex; | |
| varying vec2 texcoord; | |
| #define TEST_MACRO_THAT_WOULD_BE_REMOVED | |
| void main() | |
| { | |
| gl_FragColor = texture2D(tex, texcoord); | |
| } | |
| )"; | |
| GLuint shader = CompileShader(GL_FRAGMENT_SHADER, kFragmentShader); | |
| EXPECT_TRUE(EnsureGLExtensionEnabled("GL_ANGLE_translated_shader_source")); | |
| std::array<char, std::size(kFragmentShader) + 1> translatedSourceBuffer; | |
| glGetTranslatedShaderSourceANGLE(shader, static_cast<GLsizei>(translatedSourceBuffer.size()), | |
| nullptr, translatedSourceBuffer.data()); | |
| EXPECT_EQ(std::string(kFragmentShader), std::string(translatedSourceBuffer.data())); | |
| } | |
| // Regression test for a Skia shader which had assertion failures in CollectVariables | |
| TEST_P(EGLContextPassthroughShadersTest, ShaderRegressionTest) | |
| { | |
| ANGLE_SKIP_TEST_IF(!supportsPassthroughShadersExtension()); | |
| EGLint ctxAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_PASSTHROUGH_SHADERS_ANGLE, | |
| EGL_TRUE, EGL_NONE}; | |
| EGLContext context = eglCreateContext(mDisplay, mConfig, nullptr, ctxAttribs); | |
| EXPECT_NE(context, EGL_NO_CONTEXT); | |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, context)); | |
| constexpr const char kVS[] = R"(#version 300 es | |
| precision mediump float; | |
| precision mediump sampler2D; | |
| const highp float PRECISION = 4.0; | |
| const highp float MAX_FIXED_RESOLVE_LEVEL = 5.0; | |
| const highp float MAX_FIXED_SEGMENTS = 32.0; | |
| uniform highp vec4 sk_RTAdjust; | |
| uniform highp vec4 uaffineMatrix_S0; | |
| uniform highp vec2 utranslate_S0; | |
| in highp vec2 resolveLevel_and_idx; | |
| in highp vec4 p01; | |
| in highp vec4 p23; | |
| in highp vec2 fanPointAttrib; | |
| highp float wangs_formula_max_fdiff_p2_ff2f2f2f2f22(highp vec2 p0, highp vec2 p1, highp vec2 p2, highp vec2 p3, highp mat2 matrix) { | |
| highp vec2 d0 = matrix * (((vec2(-2.0)) * (p1) + (p2)) + p0); | |
| highp vec2 d1 = matrix * (((vec2(-2.0)) * (p2) + (p3)) + p1); | |
| return max(dot(d0, d0), dot(d1, d1)); | |
| } | |
| highp float wangs_formula_conic_p2_fff2f2f2f(highp float _precision_, highp vec2 p0, highp vec2 p1, highp vec2 p2, highp float w) { | |
| highp vec2 C = (min(min(p0, p1), p2) + max(max(p0, p1), p2)) * 0.5; | |
| p0 -= C; | |
| p1 -= C; | |
| p2 -= C; | |
| highp float m = sqrt(max(max(dot(p0, p0), dot(p1, p1)), dot(p2, p2))); | |
| highp vec2 dp = ((vec2(-2.0 * w)) * (p1) + (p0)) + p2; | |
| highp float dw = abs(((-2.0) * (w) + (2.0))); | |
| highp float rp_minus_1 = max(0.0, ((m) * (_precision_) + (-1.0))); | |
| highp float numer = length(dp) * _precision_ + rp_minus_1 * dw; | |
| highp float denom = 4.0 * min(w, 1.0); | |
| return numer / denom; | |
| } | |
| void main() { | |
| highp mat2 AFFINE_MATRIX = mat2(uaffineMatrix_S0.xy, uaffineMatrix_S0.zw); | |
| highp vec2 TRANSLATE = utranslate_S0; | |
| highp float resolveLevel = resolveLevel_and_idx.x; | |
| highp float idxInResolveLevel = resolveLevel_and_idx.y; | |
| highp vec2 localcoord; | |
| if (resolveLevel < 0.0) { | |
| localcoord = fanPointAttrib; | |
| } else { | |
| if (isinf(p23.z)) { | |
| localcoord = resolveLevel != 0.0 ? p01.zw : (idxInResolveLevel != 0.0 ? p23.xy : p01.xy); | |
| } else { | |
| highp vec2 p0 = p01.xy; | |
| highp vec2 p1 = p01.zw; | |
| highp vec2 p2 = p23.xy; | |
| highp vec2 p3 = p23.zw; | |
| highp float w = -1.0; | |
| highp float maxResolveLevel; | |
| if (isinf(p23.w)) { | |
| w = p3.x; | |
| highp float _0_n2 = wangs_formula_conic_p2_fff2f2f2f(PRECISION, AFFINE_MATRIX * p0, AFFINE_MATRIX * p1, AFFINE_MATRIX * p2, w); | |
| maxResolveLevel = ceil(log2(max(_0_n2, 1.0)) * 0.5); | |
| p1 *= w; | |
| p3 = p2; | |
| } else { | |
| highp float _1_m = wangs_formula_max_fdiff_p2_ff2f2f2f2f22(p0, p1, p2, p3, AFFINE_MATRIX); | |
| maxResolveLevel = ceil(log2(max(9.0 * _1_m, 1.0)) * 0.25); | |
| } | |
| if (resolveLevel > maxResolveLevel) { | |
| idxInResolveLevel = floor(idxInResolveLevel * exp2(maxResolveLevel - resolveLevel)); | |
| resolveLevel = maxResolveLevel; | |
| } | |
| highp float fixedVertexID = floor(0.5 + idxInResolveLevel * exp2(MAX_FIXED_RESOLVE_LEVEL - resolveLevel)); | |
| if (0.0 < fixedVertexID && fixedVertexID < MAX_FIXED_SEGMENTS) { | |
| highp float T = fixedVertexID * 0.03125; | |
| highp vec2 ab = mix(p0, p1, T); | |
| highp vec2 bc = mix(p1, p2, T); | |
| highp vec2 cd = mix(p2, p3, T); | |
| highp vec2 abc = mix(ab, bc, T); | |
| highp vec2 bcd = mix(bc, cd, T); | |
| highp vec2 abcd = mix(abc, bcd, T); | |
| highp float u = mix(1.0, w, T); | |
| highp float v = (w + 1.0) - u; | |
| highp float uv = mix(u, v, T); | |
| localcoord = w < 0.0 ? abcd : abc / uv; | |
| } else { | |
| localcoord = fixedVertexID == 0.0 ? p0 : p3; | |
| } | |
| } | |
| } | |
| highp vec2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE; | |
| gl_Position = vec4(vertexpos, 0.0, 1.0); | |
| gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w); | |
| } | |
| )"; | |
| constexpr const char kFS[] = R"(#version 300 es | |
| precision highp float; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| my_FragColor = vec4(1.0, 0.0, 0.0, 1.0); | |
| })"; | |
| ANGLE_GL_PROGRAM(program, kVS, kFS); | |
| } | |
| // Test creating two programs with the same vertex shader (but not the same fragment shader) in two | |
| // different contexts, one passthrough and one not. There shouldn't be a cache hit in the second | |
| // program creation. | |
| TEST_P(EGLContextPassthroughShadersTest, SameShaderWithAndWithoutPassthrough) | |
| { | |
| ANGLE_SKIP_TEST_IF(!supportsPassthroughShadersExtension()); | |
| // When passthrough is enabled, some extensions are automatically disabled. To remove | |
| // differences between the two contexts, mark all extensions disabled. | |
| // | |
| // Also, make sure a GLES2 context is created so that even more extensions that cannot normally | |
| // be disabled are disabled. | |
| std::vector<EGLint> ctxAttribs = {EGL_CONTEXT_MAJOR_VERSION, 2}; | |
| if (IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_create_context_extensions_enabled")) | |
| { | |
| ctxAttribs.push_back(EGL_EXTENSIONS_ENABLED_ANGLE); | |
| ctxAttribs.push_back(EGL_FALSE); | |
| } | |
| if (IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_create_context_backwards_compatible")) | |
| { | |
| ctxAttribs.push_back(EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE); | |
| ctxAttribs.push_back(EGL_FALSE); | |
| } | |
| ctxAttribs.push_back(EGL_CONTEXT_PASSTHROUGH_SHADERS_ANGLE); | |
| ctxAttribs.push_back(EGL_FALSE); | |
| ctxAttribs.push_back(EGL_NONE); | |
| // Create two identical contexts who only differe in EGL_CONTEXT_PASSTHROUGH_SHADERS_ANGLE. | |
| EGLContext noPassthroughContext = | |
| eglCreateContext(mDisplay, mConfig, nullptr, ctxAttribs.data()); | |
| EXPECT_NE(noPassthroughContext, EGL_NO_CONTEXT); | |
| ctxAttribs[ctxAttribs.size() - 2] = EGL_TRUE; | |
| EGLContext passthroughContext = eglCreateContext(mDisplay, mConfig, nullptr, ctxAttribs.data()); | |
| EXPECT_NE(passthroughContext, EGL_NO_CONTEXT); | |
| constexpr char kVS[] = R"(precision highp float; | |
| attribute vec4 position; | |
| varying vec4 color; | |
| void main() | |
| { | |
| gl_Position = position; | |
| color = vec4(1, 0, 0, 1); | |
| })"; | |
| constexpr char kFS1[] = R"(precision highp float; | |
| varying vec4 color; | |
| void main() | |
| { | |
| gl_FragColor = color; | |
| })"; | |
| constexpr char kFS2[] = R"(precision highp float; | |
| varying vec4 color; | |
| void main() | |
| { | |
| gl_FragColor = color + vec4(0, 1, 0, 0); | |
| })"; | |
| // Draw VS + FS1 in one context | |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, noPassthroughContext)); | |
| { | |
| ANGLE_GL_PROGRAM(program1, kVS, kFS1); | |
| drawQuad(program1, "position", 0); | |
| } | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); | |
| ASSERT_GL_NO_ERROR(); | |
| // Draw VS + FS2 in the other context | |
| EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, passthroughContext)); | |
| { | |
| ANGLE_GL_PROGRAM(program2, kVS, kFS2); | |
| drawQuad(program2, "position", 0); | |
| } | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextPassthroughShadersTest); | |
| ANGLE_INSTANTIATE_TEST(EGLContextPassthroughShadersTest, | |
| WithNoFixture(ES2_D3D11()), | |
| WithNoFixture(ES2_OPENGL()), | |
| WithNoFixture(ES2_OPENGLES()), | |
| WithNoFixture(ES2_VULKAN()), | |
| WithNoFixture(ES3_D3D11()), | |
| WithNoFixture(ES3_OPENGL()), | |
| WithNoFixture(ES3_OPENGLES()), | |
| WithNoFixture(ES3_VULKAN())); |