| // Copyright 2015 The Shaderc Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "libshaderc_util/compiler.h" |
| |
| #include <sstream> |
| |
| #include <gmock/gmock.h> |
| |
| #include "death_test.h" |
| #include "libshaderc_util/counting_includer.h" |
| |
| namespace { |
| |
| using shaderc_util::Compiler; |
| using ::testing::HasSubstr; |
| using ::testing::Eq; |
| |
| // A trivial vertex shader |
| const char kVertexShader[] = |
| "#version 140\n" |
| "void main() {}"; |
| |
| // A shader that compiles under OpenGL compatibility profile rules, |
| // but not OpenGL core profile rules. |
| const char kOpenGLCompatibilityFragShader[] = |
| R"(#version 140 |
| uniform highp sampler2D tex; |
| void main() { |
| gl_FragColor = texture2D(tex, vec2(0.0,0.0)); |
| })"; |
| |
| // A shader that compiles under OpenGL compatibility profile rules, |
| // but not OpenGL core profile rules, even when deducing the stage. |
| const char kOpenGLCompatibilityFragShaderDeducibleStage[] = |
| R"(#version 140 |
| #pragma shader_stage(fragment) |
| uniform highp sampler2D tex; |
| void main() { |
| gl_FragColor = texture2D(tex, vec2(0.0,0.0)); |
| })"; |
| |
| // A shader that compiles under OpenGL core profile rules. |
| const char kOpenGLVertexShader[] = |
| R"(#version 330 |
| void main() { int t = gl_VertexID; })"; |
| |
| // A shader that compiles under OpenGL core profile rules, even when |
| // deducing the stage. |
| const char kOpenGLVertexShaderDeducibleStage[] = |
| R"(#version 330 |
| #pragma shader_stage(vertex) |
| void main() { int t = gl_VertexID; })"; |
| |
| // A shader that compiles under Vulkan rules. |
| // See the GL_KHR_vuklan_glsl extension to GLSL. |
| const char kVulkanVertexShader[] = |
| R"(#version 310 es |
| void main() { int t = gl_VertexIndex; })"; |
| |
| // A shader that needs valueless macro predefinition E, to be compiled |
| // successfully. |
| const std::string kValuelessPredefinitionShader = |
| "#version 140\n" |
| "#ifdef E\n" |
| "void main(){}\n" |
| "#else\n" |
| "#error\n" |
| "#endif"; |
| |
| // An HLSL vertex shader. |
| const char kHlslVertexShader[] = |
| R"(float4 EntryPoint(uint index : SV_VERTEXID) : SV_POSITION |
| { return float4(1.0, 2.0, 3.0, 4.0); })"; |
| |
| // A CountingIncluder that never returns valid content for a requested |
| // file inclusion. |
| class DummyCountingIncluder : public shaderc_util::CountingIncluder { |
| private: |
| // Returns a pair of empty strings. |
| virtual glslang::TShader::Includer::IncludeResult* include_delegate( |
| const char*, glslang::TShader::Includer::IncludeType, const char*, |
| size_t) override { |
| return nullptr; |
| } |
| virtual void release_delegate( |
| glslang::TShader::Includer::IncludeResult*) override {} |
| }; |
| |
| // A test fixture for compiling GLSL shaders. |
| class CompilerTest : public testing::Test { |
| public: |
| // Returns true if the given compiler successfully compiles the given shader |
| // source for the given shader stage to the specified output type. No |
| // includes are permitted, and shader stage deduction falls back to an invalid |
| // shader stage. |
| bool SimpleCompilationSucceedsForOutputType( |
| std::string source, EShLanguage stage, Compiler::OutputType output_type) { |
| std::stringstream errors; |
| size_t total_warnings = 0; |
| size_t total_errors = 0; |
| shaderc_util::GlslangInitializer initializer; |
| bool result = false; |
| DummyCountingIncluder dummy_includer; |
| std::tie(result, std::ignore, std::ignore) = compiler_.Compile( |
| source, stage, "shader", "main", dummy_stage_callback_, dummy_includer, |
| Compiler::OutputType::SpirvBinary, &errors, &total_warnings, |
| &total_errors, &initializer); |
| errors_ = errors.str(); |
| return result; |
| } |
| |
| // Returns the result of SimpleCompilationSucceedsForOutputType, where |
| // the output type is a SPIR-V binary module. |
| bool SimpleCompilationSucceeds(std::string source, EShLanguage stage) { |
| return SimpleCompilationSucceedsForOutputType( |
| source, stage, Compiler::OutputType::SpirvBinary); |
| } |
| |
| protected: |
| Compiler compiler_; |
| // The error string from the most recent compilation. |
| std::string errors_; |
| std::function<EShLanguage(std::ostream*, const shaderc_util::string_piece&)> |
| dummy_stage_callback_ = |
| [](std::ostream*, const shaderc_util::string_piece&) { |
| return EShLangCount; |
| }; |
| }; |
| |
| TEST_F(CompilerTest, SimpleVertexShaderCompilesSuccessfullyToBinary) { |
| EXPECT_TRUE(SimpleCompilationSucceeds(kVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, SimpleVertexShaderCompilesSuccessfullyToAssembly) { |
| EXPECT_TRUE(SimpleCompilationSucceedsForOutputType( |
| kVertexShader, EShLangVertex, Compiler::OutputType::SpirvAssemblyText)); |
| } |
| |
| TEST_F(CompilerTest, SimpleVertexShaderPreprocessesSuccessfully) { |
| EXPECT_TRUE(SimpleCompilationSucceedsForOutputType( |
| kVertexShader, EShLangVertex, Compiler::OutputType::PreprocessedText)); |
| } |
| |
| TEST_F(CompilerTest, BadVertexShaderFailsCompilation) { |
| EXPECT_FALSE(SimpleCompilationSucceeds(" bogus ", EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, SimpleVulkanShaderCompilesWithDefaultCompilerSettings) { |
| EXPECT_TRUE(SimpleCompilationSucceeds(kVulkanVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, RespectTargetEnvOnOpenGLCompatibilityShader) { |
| const EShLanguage stage = EShLangFragment; |
| |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGLCompat); |
| EXPECT_TRUE(SimpleCompilationSucceeds(kOpenGLCompatibilityFragShader, stage)); |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGL); |
| EXPECT_FALSE( |
| SimpleCompilationSucceeds(kOpenGLCompatibilityFragShader, stage)); |
| compiler_.SetTargetEnv(Compiler::TargetEnv::Vulkan); |
| EXPECT_FALSE( |
| SimpleCompilationSucceeds(kOpenGLCompatibilityFragShader, stage)); |
| // Default compiler. |
| compiler_ = Compiler(); |
| EXPECT_FALSE( |
| SimpleCompilationSucceeds(kOpenGLCompatibilityFragShader, stage)); |
| } |
| |
| TEST_F(CompilerTest, |
| RespectTargetEnvOnOpenGLCompatibilityShaderWhenDeducingStage) { |
| const EShLanguage stage = EShLangCount; |
| |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGLCompat); |
| EXPECT_TRUE(SimpleCompilationSucceeds( |
| kOpenGLCompatibilityFragShaderDeducibleStage, stage)) |
| << errors_; |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGL); |
| EXPECT_FALSE(SimpleCompilationSucceeds( |
| kOpenGLCompatibilityFragShaderDeducibleStage, stage)) |
| << errors_; |
| compiler_.SetTargetEnv(Compiler::TargetEnv::Vulkan); |
| EXPECT_FALSE(SimpleCompilationSucceeds( |
| kOpenGLCompatibilityFragShaderDeducibleStage, stage)) |
| << errors_; |
| // Default compiler. |
| compiler_ = Compiler(); |
| EXPECT_FALSE(SimpleCompilationSucceeds( |
| kOpenGLCompatibilityFragShaderDeducibleStage, stage)) |
| << errors_; |
| } |
| |
| TEST_F(CompilerTest, RespectTargetEnvOnOpenGLShader) { |
| const EShLanguage stage = EShLangVertex; |
| |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGLCompat); |
| EXPECT_TRUE(SimpleCompilationSucceeds(kOpenGLVertexShader, stage)); |
| |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGL); |
| EXPECT_TRUE(SimpleCompilationSucceeds(kOpenGLVertexShader, stage)); |
| } |
| |
| TEST_F(CompilerTest, RespectTargetEnvOnOpenGLShaderWhenDeducingStage) { |
| const EShLanguage stage = EShLangCount; |
| |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGLCompat); |
| EXPECT_TRUE( |
| SimpleCompilationSucceeds(kOpenGLVertexShaderDeducibleStage, stage)); |
| |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGL); |
| EXPECT_TRUE( |
| SimpleCompilationSucceeds(kOpenGLVertexShaderDeducibleStage, stage)); |
| } |
| |
| TEST_F(CompilerTest, RespectTargetEnvOnVulkanShader) { |
| compiler_.SetTargetEnv(Compiler::TargetEnv::Vulkan); |
| EXPECT_TRUE(SimpleCompilationSucceeds(kVulkanVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, VulkanSpecificShaderFailsUnderOpenGLCompatibilityRules) { |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGLCompat); |
| EXPECT_FALSE(SimpleCompilationSucceeds(kVulkanVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, VulkanSpecificShaderFailsUnderOpenGLRules) { |
| compiler_.SetTargetEnv(Compiler::TargetEnv::OpenGL); |
| EXPECT_FALSE(SimpleCompilationSucceeds(kVulkanVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, OpenGLCompatibilitySpecificShaderFailsUnderDefaultRules) { |
| EXPECT_FALSE(SimpleCompilationSucceeds(kOpenGLCompatibilityFragShader, |
| EShLangFragment)); |
| } |
| |
| TEST_F(CompilerTest, OpenGLSpecificShaderFailsUnderDefaultRules) { |
| EXPECT_FALSE(SimpleCompilationSucceeds(kOpenGLVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, OpenGLCompatibilitySpecificShaderFailsUnderVulkanRules) { |
| compiler_.SetTargetEnv(Compiler::TargetEnv::Vulkan); |
| EXPECT_FALSE(SimpleCompilationSucceeds(kOpenGLCompatibilityFragShader, |
| EShLangFragment)); |
| } |
| |
| TEST_F(CompilerTest, OpenGLSpecificShaderFailsUnderVulkanRules) { |
| compiler_.SetTargetEnv(Compiler::TargetEnv::Vulkan); |
| EXPECT_FALSE(SimpleCompilationSucceeds(kOpenGLVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, AddMacroDefinition) { |
| const std::string kMinimalExpandedShader = "#version 140\nvoid E(){}"; |
| compiler_.AddMacroDefinition("E", 1u, "main", 4u); |
| EXPECT_TRUE(SimpleCompilationSucceeds(kMinimalExpandedShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, AddValuelessMacroDefinitionNullPointer) { |
| compiler_.AddMacroDefinition("E", 1u, nullptr, 100u); |
| EXPECT_TRUE( |
| SimpleCompilationSucceeds(kValuelessPredefinitionShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, AddValuelessMacroDefinitionZeroLength) { |
| compiler_.AddMacroDefinition("E", 1u, "something", 0u); |
| EXPECT_TRUE( |
| SimpleCompilationSucceeds(kValuelessPredefinitionShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, AddMacroDefinitionNotNullTerminated) { |
| const std::string kMinimalExpandedShader = "#version 140\nvoid E(){}"; |
| compiler_.AddMacroDefinition("EFGH", 1u, "mainnnnnn", 4u); |
| EXPECT_TRUE(SimpleCompilationSucceeds(kMinimalExpandedShader, EShLangVertex)); |
| } |
| |
| // A convert-string-to-vector test case consists of 1) an input string; 2) an |
| // expected vector after the conversion. |
| struct ConvertStringToVectorTestCase { |
| std::string input_str; |
| std::vector<uint32_t> expected_output_vec; |
| }; |
| |
| // Test the shaderc_util::ConvertStringToVector() function. The content of the |
| // input string, including the null terminator, should be packed into uint32_t |
| // cells and stored in the returned vector of uint32_t. In case extra bytes are |
| // required to complete the ending uint32_t element, bytes with value 0x00 |
| // should be used to fill the space. |
| using ConvertStringToVectorTestFixture = |
| testing::TestWithParam<ConvertStringToVectorTestCase>; |
| |
| TEST_P(ConvertStringToVectorTestFixture, VariousStringSize) { |
| const ConvertStringToVectorTestCase& test_case = GetParam(); |
| EXPECT_EQ(test_case.expected_output_vec, |
| shaderc_util::ConvertStringToVector(test_case.input_str)) |
| << "test_case.input_str: " << test_case.input_str << std::endl; |
| } |
| |
| INSTANTIATE_TEST_CASE_P( |
| ConvertStringToVectorTest, ConvertStringToVectorTestFixture, |
| testing::ValuesIn(std::vector<ConvertStringToVectorTestCase>{ |
| {"", {0x00000000}}, |
| {"1", {0x00000031}}, |
| {"12", {0x00003231}}, |
| {"123", {0x00333231}}, |
| {"1234", {0x34333231, 0x00000000}}, |
| {"12345", {0x34333231, 0x00000035}}, |
| {"123456", {0x34333231, 0x00003635}}, |
| {"1234567", {0x34333231, 0x00373635}}, |
| {"12345678", {0x34333231, 0x38373635, 0x00000000}}, |
| {"123456789", {0x34333231, 0x38373635, 0x00000039}}, |
| })); |
| |
| TEST_F(CompilerTest, SetSourceLanguageToGLSLSucceeds) { |
| compiler_.SetSourceLanguage(Compiler::SourceLanguage::GLSL); |
| EXPECT_TRUE(SimpleCompilationSucceeds(kVulkanVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, SetSourceLanguageToGLSLFailsOnHLSL) { |
| compiler_.SetSourceLanguage(Compiler::SourceLanguage::GLSL); |
| EXPECT_FALSE(SimpleCompilationSucceeds(kHlslVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, SetSourceLanguageToHLSLSucceeds) { |
| compiler_.SetSourceLanguage(Compiler::SourceLanguage::HLSL); |
| EXPECT_TRUE(SimpleCompilationSucceeds(kHlslVertexShader, EShLangVertex)) |
| << errors_; |
| } |
| |
| TEST_F(CompilerTest, SetSourceLanguageToHLSLFailsOnGLSL) { |
| compiler_.SetSourceLanguage(Compiler::SourceLanguage::HLSL); |
| EXPECT_FALSE(SimpleCompilationSucceeds(kVulkanVertexShader, EShLangVertex)); |
| } |
| |
| TEST_F(CompilerTest, EntryPointParameterTakesEffectForHLSL) { |
| compiler_.SetSourceLanguage(Compiler::SourceLanguage::HLSL); |
| std::stringstream errors; |
| size_t total_warnings = 0; |
| size_t total_errors = 0; |
| shaderc_util::GlslangInitializer initializer; |
| bool result = false; |
| DummyCountingIncluder dummy_includer; |
| std::vector<uint32_t> words; |
| std::tie(result, words, std::ignore) = |
| compiler_.Compile(kHlslVertexShader, EShLangVertex, "shader", |
| "EntryPoint", dummy_stage_callback_, dummy_includer, |
| Compiler::OutputType::SpirvAssemblyText, &errors, |
| &total_warnings, &total_errors, &initializer); |
| EXPECT_TRUE(result); |
| std::string assembly(reinterpret_cast<char*>(words.data())); |
| EXPECT_THAT(assembly, |
| HasSubstr("OpEntryPoint Vertex %EntryPoint \"EntryPoint\"")) |
| << assembly; |
| } |
| |
| // A test case for setting resource limits. |
| struct SetLimitCase { |
| Compiler::Limit limit; |
| int default_value; |
| int value; |
| }; |
| |
| using LimitTest = testing::TestWithParam<SetLimitCase>; |
| |
| TEST_P(LimitTest, Sample) { |
| Compiler compiler; |
| EXPECT_THAT(compiler.GetLimit(GetParam().limit), |
| Eq(GetParam().default_value)); |
| compiler.SetLimit(GetParam().limit, GetParam().value); |
| EXPECT_THAT(compiler.GetLimit(GetParam().limit), Eq(GetParam().value)); |
| } |
| |
| #define CASE(LIMIT, DEFAULT, NEW) \ |
| { Compiler::Limit::LIMIT, DEFAULT, NEW } |
| INSTANTIATE_TEST_CASE_P( |
| CompilerTest, LimitTest, |
| // See resources.cc for the defaults. |
| testing::ValuesIn(std::vector<SetLimitCase>{ |
| // clang-format off |
| // This is just a sampling of the possible values. |
| CASE(MaxLights, 8, 99), |
| CASE(MaxClipPlanes, 6, 10929), |
| CASE(MaxTessControlAtomicCounters, 0, 72), |
| CASE(MaxSamples, 4, 8), |
| // clang-format on |
| }), ); |
| #undef CASE |
| |
| // Returns a fragment shader accessing a texture with the given |
| // offset. |
| std::string ShaderWithTexOffset(int offset) { |
| std::ostringstream oss; |
| oss << "#version 150\n" |
| "uniform sampler1D tex;\n" |
| "void main() { vec4 x = textureOffset(tex, 1.0, " |
| << offset << "); }\n"; |
| return oss.str(); |
| } |
| |
| // Ensure compilation is sensitive to limit setting. Sample just |
| // two particular limits. The default minimum texel offset is -8, |
| // and the default maximum texel offset is 7. |
| TEST_F(CompilerTest, TexelOffsetDefaults) { |
| const EShLanguage stage = EShLangFragment; |
| EXPECT_FALSE(SimpleCompilationSucceeds(ShaderWithTexOffset(-9), stage)); |
| EXPECT_TRUE(SimpleCompilationSucceeds(ShaderWithTexOffset(-8), stage)); |
| EXPECT_TRUE(SimpleCompilationSucceeds(ShaderWithTexOffset(7), stage)); |
| EXPECT_FALSE(SimpleCompilationSucceeds(ShaderWithTexOffset(8), stage)); |
| } |
| |
| TEST_F(CompilerTest, TexelOffsetLowerTheMinimum) { |
| const EShLanguage stage = EShLangFragment; |
| compiler_.SetLimit(Compiler::Limit::MinProgramTexelOffset, -99); |
| EXPECT_FALSE(SimpleCompilationSucceeds(ShaderWithTexOffset(-100), stage)); |
| EXPECT_TRUE(SimpleCompilationSucceeds(ShaderWithTexOffset(-99), stage)); |
| } |
| |
| TEST_F(CompilerTest, TexelOffsetRaiseTheMaximum) { |
| const EShLanguage stage = EShLangFragment; |
| compiler_.SetLimit(Compiler::Limit::MaxProgramTexelOffset, 100); |
| EXPECT_TRUE(SimpleCompilationSucceeds(ShaderWithTexOffset(100), stage)); |
| EXPECT_FALSE(SimpleCompilationSucceeds(ShaderWithTexOffset(101), stage)); |
| } |
| |
| } // anonymous namespace |