// // Copyright 2020 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. // // EXT_clip_cull_distance_test.cpp: // Test for EXT_clip_cull_distance // #include "tests/test_utils/ShaderExtensionTest.h" namespace { const char EXTPragma[] = "#extension GL_EXT_clip_cull_distance : require\n"; // Shader using gl_ClipDistance and gl_CullDistance const char VertexShaderCompileSucceeds1[] = R"( uniform vec4 uPlane; in vec4 aPosition; void main() { gl_Position = aPosition; gl_ClipDistance[1] = dot(aPosition, uPlane); gl_CullDistance[1] = dot(aPosition, uPlane); })"; // Shader redeclares gl_ClipDistance and gl_CullDistance const char VertexShaderCompileSucceeds2[] = R"( uniform vec4 uPlane; in vec4 aPosition; out highp float gl_ClipDistance[4]; out highp float gl_CullDistance[4]; void main() { gl_Position = aPosition; gl_ClipDistance[gl_MaxClipDistances - 6 + 1] = dot(aPosition, uPlane); gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)] = dot(aPosition, uPlane); gl_CullDistance[gl_MaxCullDistances - 6 + 1] = dot(aPosition, uPlane); gl_CullDistance[gl_MaxCullDistances - int(aPosition.x)] = dot(aPosition, uPlane); })"; #if defined(ANGLE_ENABLE_VULKAN) // Shader using gl_ClipDistance and gl_CullDistance // But, the sum of the sizes is greater than gl_MaxCombinedClipAndCullDistances const char VertexShaderCompileFails1[] = R"( uniform vec4 uPlane; in vec4 aPosition; void main() { gl_Position = aPosition; gl_ClipDistance[5] = dot(aPosition, uPlane); gl_CullDistance[4] = dot(aPosition, uPlane); })"; // Shader redeclares gl_ClipDistance and gl_CullDistance // But, the sum of the sizes is greater than gl_MaxCombinedClipAndCullDistances const char VertexShaderCompileFails2[] = R"( uniform vec4 uPlane; in vec4 aPosition; out highp float gl_ClipDistance[5]; out highp float gl_CullDistance[4]; void main() { gl_Position = aPosition; gl_ClipDistance[gl_MaxClipDistances - 6 + 1] = dot(aPosition, uPlane); gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)] = dot(aPosition, uPlane); gl_CullDistance[gl_MaxCullDistances - 6 + 1] = dot(aPosition, uPlane); gl_CullDistance[gl_MaxCullDistances - int(aPosition.x)] = dot(aPosition, uPlane); })"; // Shader redeclares gl_ClipDistance // But, the array size is greater than gl_MaxClipDistances const char VertexShaderCompileFails3[] = R"( uniform vec4 uPlane; in vec4 aPosition; out highp float gl_ClipDistance[gl_MaxClipDistances + 1]; void main() { gl_Position = aPosition; gl_ClipDistance[gl_MaxClipDistances - 6 + 1] = dot(aPosition, uPlane); gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)] = dot(aPosition, uPlane); })"; // Access gl_CullDistance with integral constant index // But, the index is greater than gl_MaxCullDistances const char VertexShaderCompileFails4[] = R"( uniform vec4 uPlane; in vec4 aPosition; void main() { gl_Position = aPosition; gl_CullDistance[gl_MaxCullDistances] = dot(aPosition, uPlane); })"; const char VertexShaderCompileFails5[] = R"( uniform vec4 uPlane; attribute vec4 aPosition; void main() { gl_Position = aPosition; gl_CullDistance[0] = 0.0; })"; const char VertexShaderCompileFailes6[] = R"( uniform vec4 uPlane; attribute vec4 aPosition; varying float gl_ClipDistance[1]; void main() { gl_Position = aPosition; gl_ClipDistance[0] = 0.0; })"; #endif // Shader using gl_ClipDistance and gl_CullDistance const char FragmentShaderCompileSucceeds1[] = R"( out highp vec4 fragColor; void main() { fragColor = vec4(gl_ClipDistance[0], gl_CullDistance[0], 0, 1); })"; // Shader redeclares gl_ClipDistance and gl_CullDistance const char FragmentShaderCompileSucceeds2[] = R"( in highp float gl_ClipDistance[4]; in highp float gl_CullDistance[4]; in highp vec4 aPosition; out highp vec4 fragColor; void main() { fragColor.x = gl_ClipDistance[gl_MaxClipDistances - 6 + 1]; fragColor.y = gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)]; fragColor.z = gl_CullDistance[gl_MaxCullDistances - 6 + 1]; fragColor.w = gl_CullDistance[gl_MaxCullDistances - int(aPosition.x)]; })"; #if defined(ANGLE_ENABLE_VULKAN) // Shader using gl_ClipDistance and gl_CullDistance // But, the sum of the sizes is greater than gl_MaxCombinedClipAndCullDistances const char FragmentShaderCompileFails1[] = R"( out highp vec4 fragColor; void main() { fragColor = vec4(gl_ClipDistance[4], gl_CullDistance[5], 0, 1); })"; // Shader redeclares gl_ClipDistance and gl_CullDistance // But, the sum of the sizes is greater than gl_MaxCombinedClipAndCullDistances const char FragmentShaderCompileFails2[] = R"( in highp float gl_ClipDistance[5]; in highp float gl_CullDistance[4]; in highp vec4 aPosition; out highp vec4 fragColor; void main() { fragColor.x = gl_ClipDistance[gl_MaxClipDistances - 6 + 1]; fragColor.y = gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)]; fragColor.z = gl_CullDistance[gl_MaxCullDistances - 6 + 1]; fragColor.w = gl_CullDistance[gl_MaxCullDistances - int(aPosition.x)]; })"; // In fragment shader, writing to gl_ClipDistance should be denied. const char FragmentShaderCompileFails3[] = R"( out highp vec4 fragColor; void main() { gl_ClipDistance[0] = 0.0f; fragColor = vec4(1, gl_ClipDistance[0], 0, 1); })"; // In fragment shader, writing to gl_CullDistance should be denied even if redeclaring it with the // array size const char FragmentShaderCompileFails4[] = R"( out highp vec4 fragColor; in highp float gl_CullDistance[1]; void main() { gl_CullDistance[0] = 0.0f; fragColor = vec4(1, gl_CullDistance[0], 0, 1); })"; // Accessing to gl_Clip/CullDistance with non-const index should be denied if the size of // gl_Clip/CullDistance is not decided. const char FragmentShaderCompileFails5[] = R"( out highp vec4 fragColor; void main() { medium float color[3]; for(int i = 0 ; i < 3 ; i++) { color[i] = gl_CullDistance[i]; } fragColor = vec4(color[0], color[1], color[2], 1.0f); })"; #endif class EXTClipCullDistanceTest : public sh::ShaderExtensionTest { public: void InitializeCompiler(ShShaderOutput shaderOutputType, GLenum shaderType) { DestroyCompiler(); mCompiler = sh::ConstructCompiler(shaderType, testing::get<0>(GetParam()), shaderOutputType, &mResources); ASSERT_TRUE(mCompiler != nullptr) << "Compiler could not be constructed."; } testing::AssertionResult TestShaderCompile(const char *pragma) { const char *shaderStrings[] = {testing::get<1>(GetParam()), pragma, testing::get<2>(GetParam())}; bool success = sh::Compile(mCompiler, shaderStrings, 3, SH_VARIABLES | SH_OBJECT_CODE); if (success) { return ::testing::AssertionSuccess() << "Compilation success"; } return ::testing::AssertionFailure() << sh::GetInfoLog(mCompiler); } void SetExtensionEnable(bool enable) { // GL_APPLE_clip_distance is implicitly enabled when GL_EXT_clip_cull_distance is enabled #if defined(ANGLE_ENABLE_VULKAN) mResources.APPLE_clip_distance = enable; #endif mResources.EXT_clip_cull_distance = enable; } }; class EXTClipCullDistanceForVertexShaderTest : public EXTClipCullDistanceTest { public: void InitializeCompiler() { InitializeCompiler(SH_GLSL_450_CORE_OUTPUT); } void InitializeCompiler(ShShaderOutput shaderOutputType) { EXTClipCullDistanceTest::InitializeCompiler(shaderOutputType, GL_VERTEX_SHADER); } }; class EXTClipCullDistanceForFragmentShaderTest : public EXTClipCullDistanceTest { public: void InitializeCompiler() { InitializeCompiler(SH_GLSL_450_CORE_OUTPUT); } void InitializeCompiler(ShShaderOutput shaderOutputType) { EXTClipCullDistanceTest::InitializeCompiler(shaderOutputType, GL_FRAGMENT_SHADER); } }; // Extension flag is required to compile properly. Expect failure when it is // not present. TEST_P(EXTClipCullDistanceForVertexShaderTest, CompileFailsWithoutExtension) { SetExtensionEnable(false); InitializeCompiler(); EXPECT_FALSE(TestShaderCompile(EXTPragma)); } // Extension directive is required to compile properly. Expect failure when // it is not present. TEST_P(EXTClipCullDistanceForVertexShaderTest, CompileFailsWithExtensionWithoutPragma) { SetExtensionEnable(true); InitializeCompiler(); EXPECT_FALSE(TestShaderCompile("")); } #if defined(ANGLE_ENABLE_VULKAN) // With extension flag and extension directive, compiling using TranslatorVulkan succeeds. TEST_P(EXTClipCullDistanceForVertexShaderTest, CompileSucceedsVulkan) { SetExtensionEnable(true); mResources.MaxClipDistances = 8; mResources.MaxCullDistances = 8; mResources.MaxCombinedClipAndCullDistances = 8; InitializeCompiler(SH_SPIRV_VULKAN_OUTPUT); EXPECT_TRUE(TestShaderCompile(EXTPragma)); EXPECT_FALSE(TestShaderCompile("")); EXPECT_TRUE(TestShaderCompile(EXTPragma)); } #endif // Extension flag is required to compile properly. Expect failure when it is // not present. TEST_P(EXTClipCullDistanceForFragmentShaderTest, CompileFailsWithoutExtension) { SetExtensionEnable(false); InitializeCompiler(); EXPECT_FALSE(TestShaderCompile(EXTPragma)); } // Extension directive is required to compile properly. Expect failure when // it is not present. TEST_P(EXTClipCullDistanceForFragmentShaderTest, CompileFailsWithExtensionWithoutPragma) { SetExtensionEnable(true); InitializeCompiler(); EXPECT_FALSE(TestShaderCompile("")); } #if defined(ANGLE_ENABLE_VULKAN) // With extension flag and extension directive, compiling using TranslatorVulkan succeeds. // // Test is disabled due to translation bug. http://anglebug.com/5747 TEST_P(EXTClipCullDistanceForFragmentShaderTest, DISABLED_CompileSucceedsVulkan) { SetExtensionEnable(true); mResources.MaxClipDistances = 8; mResources.MaxCullDistances = 8; mResources.MaxCombinedClipAndCullDistances = 8; InitializeCompiler(SH_SPIRV_VULKAN_OUTPUT); EXPECT_TRUE(TestShaderCompile(EXTPragma)); EXPECT_FALSE(TestShaderCompile("")); EXPECT_TRUE(TestShaderCompile(EXTPragma)); } class EXTClipCullDistanceForVertexShaderCompileFailureTest : public EXTClipCullDistanceForVertexShaderTest {}; class EXTClipCullDistanceForFragmentShaderCompileFailureTest : public EXTClipCullDistanceForFragmentShaderTest {}; TEST_P(EXTClipCullDistanceForVertexShaderCompileFailureTest, CompileFails) { SetExtensionEnable(true); mResources.MaxClipDistances = 8; mResources.MaxCullDistances = 8; mResources.MaxCombinedClipAndCullDistances = 8; InitializeCompiler(SH_SPIRV_VULKAN_OUTPUT); EXPECT_FALSE(TestShaderCompile(EXTPragma)); } TEST_P(EXTClipCullDistanceForFragmentShaderCompileFailureTest, CompileFails) { SetExtensionEnable(true); mResources.MaxClipDistances = 8; mResources.MaxCullDistances = 8; mResources.MaxCombinedClipAndCullDistances = 8; InitializeCompiler(SH_SPIRV_VULKAN_OUTPUT); EXPECT_FALSE(TestShaderCompile(EXTPragma)); } #endif INSTANTIATE_TEST_SUITE_P(CorrectESSL300Shaders, EXTClipCullDistanceForVertexShaderTest, Combine(Values(SH_GLES3_SPEC), Values(sh::ESSLVersion300), Values(VertexShaderCompileSucceeds1, VertexShaderCompileSucceeds2))); INSTANTIATE_TEST_SUITE_P(CorrectESSL300Shaders, EXTClipCullDistanceForFragmentShaderTest, Combine(Values(SH_GLES3_SPEC), Values(sh::ESSLVersion300), Values(FragmentShaderCompileSucceeds1, FragmentShaderCompileSucceeds2))); // The corresponding TEST_Ps are defined only when ANGLE_ENABLE_VULKAN is // defined. #if defined(ANGLE_ENABLE_VULKAN) INSTANTIATE_TEST_SUITE_P(IncorrectESSL100Shaders, EXTClipCullDistanceForVertexShaderCompileFailureTest, Combine(Values(SH_GLES2_SPEC), Values(sh::ESSLVersion100), Values(VertexShaderCompileFails5, VertexShaderCompileFailes6))); INSTANTIATE_TEST_SUITE_P(IncorrectESSL300Shaders, EXTClipCullDistanceForVertexShaderCompileFailureTest, Combine(Values(SH_GLES3_SPEC), Values(sh::ESSLVersion300), Values(VertexShaderCompileFails1, VertexShaderCompileFails2, VertexShaderCompileFails3, VertexShaderCompileFails4))); INSTANTIATE_TEST_SUITE_P(IncorrectESSL300Shaders, EXTClipCullDistanceForFragmentShaderCompileFailureTest, Combine(Values(SH_GLES3_SPEC), Values(sh::ESSLVersion300), Values(FragmentShaderCompileFails1, FragmentShaderCompileFails2, FragmentShaderCompileFails3, FragmentShaderCompileFails4, FragmentShaderCompileFails5))); #endif } // anonymous namespace