diff --git a/gtests/AST.FromFile.cpp b/gtests/AST.FromFile.cpp index 5e0b31e9..3f4819a3 100644 --- a/gtests/AST.FromFile.cpp +++ b/gtests/AST.FromFile.cpp @@ -44,7 +44,8 @@ using CompileToAstTest = GlslangTest<::testing::TestWithParam>; TEST_P(CompileToAstTest, FromFile) { loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam(), - Semantics::OpenGL, Target::AST); + Source::GLSL, Semantics::OpenGL, + Target::AST); } // clang-format off @@ -183,7 +184,7 @@ INSTANTIATE_TEST_CASE_P( "nonVulkan.frag", "spv.atomic.comp", })), - FileNameAsCustomTestName + FileNameAsCustomTestSuffix ); // clang-format on diff --git a/gtests/CMakeLists.txt b/gtests/CMakeLists.txt index de04ecee..d247a914 100644 --- a/gtests/CMakeLists.txt +++ b/gtests/CMakeLists.txt @@ -13,6 +13,7 @@ if (TARGET gmock) # Test related source files ${CMAKE_CURRENT_SOURCE_DIR}/AST.FromFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BuiltInResource.FromFile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Hlsl.FromFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pp.FromFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Spv.FromFile.cpp ) diff --git a/gtests/Hlsl.FromFile.cpp b/gtests/Hlsl.FromFile.cpp new file mode 100644 index 00000000..6105185b --- /dev/null +++ b/gtests/Hlsl.FromFile.cpp @@ -0,0 +1,81 @@ +// +// Copyright (C) 2016 Google, Inc. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include + +#include "TestFixture.h" + +namespace glslangtest { +namespace { + +struct FileNameEntryPointPair { + const char* fileName; + const char* entryPoint; +}; + +// We are using FileNameEntryPointPair objects as parameters for instantiating +// the template, so the global FileNameAsCustomTestSuffix() won't work since +// it assumes std::string as parameters. Thus, an overriding one here. +std::string FileNameAsCustomTestSuffix( + const ::testing::TestParamInfo& info) { + std::string name = info.param.fileName; + // A valid test case suffix cannot have '.' and '-' inside. + std::replace(name.begin(), name.end(), '.', '_'); + std::replace(name.begin(), name.end(), '-', '_'); + return name; +} + +using HlslCompileTest = GlslangTest<::testing::TestWithParam>; + +// Compiling HLSL to SPIR-V under Vulkan semantics. Expected to successfully +// generate SPIR-V. +TEST_P(HlslCompileTest, FromFile) +{ + loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam().fileName, + Source::HLSL, Semantics::Vulkan, + Target::BothASTAndSpv, GetParam().entryPoint); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + ToSpirv, HlslCompileTest, + ::testing::ValuesIn(std::vector{ + {"hlsl.frag", "PixelShaderFunction"}, + }), + FileNameAsCustomTestSuffix +); +// clang-format on + +} // anonymous namespace +} // namespace glslangtest diff --git a/gtests/Initializer.h b/gtests/Initializer.h index 3cd91a08..3cafb52a 100644 --- a/gtests/Initializer.h +++ b/gtests/Initializer.h @@ -69,7 +69,7 @@ public: InitializationToken acquire(EShMessages new_messages) { if ((lastMessages ^ new_messages) & - (EShMsgVulkanRules | EShMsgSpvRules)) { + (EShMsgVulkanRules | EShMsgSpvRules | EShMsgReadHlsl)) { glslang::FinalizeProcess(); glslang::InitializeProcess(); } diff --git a/gtests/Pp.FromFile.cpp b/gtests/Pp.FromFile.cpp index cfd987ba..01bdfc32 100644 --- a/gtests/Pp.FromFile.cpp +++ b/gtests/Pp.FromFile.cpp @@ -66,7 +66,7 @@ INSTANTIATE_TEST_CASE_P( "preprocessor.defined.vert", "preprocessor.many.endif.vert", })), - FileNameAsCustomTestName + FileNameAsCustomTestSuffix ); // clang-format on diff --git a/gtests/Spv.FromFile.cpp b/gtests/Spv.FromFile.cpp index 8a4d0e25..04e204e3 100644 --- a/gtests/Spv.FromFile.cpp +++ b/gtests/Spv.FromFile.cpp @@ -49,7 +49,8 @@ using VulkanSemantics = GlslangTest<::testing::TestWithParam>; TEST_P(CompileToSpirvTest, FromFile) { loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam(), - Semantics::Vulkan, Target::Spirv); + Source::GLSL, Semantics::Vulkan, + Target::Spv); } // GLSL-level Vulkan semantics test. Expected to error out before generating @@ -57,7 +58,8 @@ TEST_P(CompileToSpirvTest, FromFile) TEST_P(VulkanSemantics, FromFile) { loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam(), - Semantics::Vulkan, Target::Spirv); + Source::GLSL, Semantics::Vulkan, + Target::Spv); } // clang-format off @@ -173,7 +175,7 @@ INSTANTIATE_TEST_CASE_P( "spv.specConstant.comp", "spv.specConstantComposite.vert", })), - FileNameAsCustomTestName + FileNameAsCustomTestSuffix ); INSTANTIATE_TEST_CASE_P( @@ -183,7 +185,7 @@ INSTANTIATE_TEST_CASE_P( "vulkan.vert", "vulkan.comp", })), - FileNameAsCustomTestName + FileNameAsCustomTestSuffix ); // clang-format on diff --git a/gtests/TestFixture.cpp b/gtests/TestFixture.cpp index 744fa558..ca128174 100644 --- a/gtests/TestFixture.cpp +++ b/gtests/TestFixture.cpp @@ -36,7 +36,7 @@ namespace glslangtest { -std::string FileNameAsCustomTestName( +std::string FileNameAsCustomTestSuffix( const ::testing::TestParamInfo& info) { std::string name = info.param; @@ -46,7 +46,7 @@ std::string FileNameAsCustomTestName( return name; } -EShLanguage GetGlslLanguageForStage(const std::string& stage) +EShLanguage GetShaderStage(const std::string& stage) { if (stage == "vert") { return EShLangVertex; @@ -66,17 +66,27 @@ EShLanguage GetGlslLanguageForStage(const std::string& stage) } } -EShMessages GetSpirvMessageOptionsForSemanticsAndTarget(Semantics semantics, - Target target) +EShMessages DeriveOptions(Source source, Semantics semantics, Target target) { EShMessages result = EShMsgDefault; + switch (source) { + case Source::GLSL: + break; + case Source::HLSL: + result = EShMsgReadHlsl; + break; + } + switch (target) { case Target::AST: - result = EShMsgAST; + result = static_cast(result | EShMsgAST); break; - case Target::Spirv: - result = EShMsgSpvRules; + case Target::Spv: + result = static_cast(result | EShMsgSpvRules); + break; + case Target::BothASTAndSpv: + result = static_cast(result | EShMsgSpvRules | EShMsgAST); break; }; diff --git a/gtests/TestFixture.h b/gtests/TestFixture.h index 8f744441..a13a50b3 100644 --- a/gtests/TestFixture.h +++ b/gtests/TestFixture.h @@ -65,9 +65,14 @@ namespace glslangtest { // This function is used to provide custom test name suffixes based on the // shader source file names. Otherwise, the test name suffixes will just be // numbers, which are not quite obvious. -std::string FileNameAsCustomTestName( +std::string FileNameAsCustomTestSuffix( const ::testing::TestParamInfo& info); +enum class Source { + GLSL, + HLSL, +}; + // Enum for shader compilation semantics. enum class Semantics { OpenGL, @@ -77,13 +82,13 @@ enum class Semantics { // Enum for compilation target. enum class Target { AST, - Spirv, + Spv, + BothASTAndSpv, }; -EShLanguage GetGlslLanguageForStage(const std::string& stage); +EShLanguage GetShaderStage(const std::string& stage); -EShMessages GetSpirvMessageOptionsForSemanticsAndTarget(Semantics semantics, - Target target); +EShMessages DeriveOptions(Source, Semantics, Target); // Reads the content of the file at the given |path|. On success, returns true // and the contents; otherwise, returns false and an empty string. @@ -160,23 +165,23 @@ public: const std::string spirv; // Optional SPIR-V disassembly text. }; - // Compiles and linkes the given GLSL |source| code of the given shader + // Compiles and linkes the given source |code| of the given shader // |stage| into the given |target| under the given |semantics|. Returns // a GlslangResult instance containing all the information generated // during the process. If |target| is Target::Spirv, also disassembles // the result and returns disassembly text. - GlslangResult compileGlsl(const std::string& source, - const std::string& stage, Semantics semantics, - Target target) + GlslangResult compile(const std::string& code, Source source, + const std::string& stage, Semantics semantics, + Target target, const std::string& entryPointName) { - const char* shaderStrings = source.data(); - const int shaderLengths = static_cast(source.size()); - const EShLanguage language = GetGlslLanguageForStage(stage); + const char* shaderStrings = code.data(); + const int shaderLengths = static_cast(code.size()); + const EShLanguage kind = GetShaderStage(stage); - glslang::TShader shader(language); + glslang::TShader shader(kind); shader.setStringsWithLengths(&shaderStrings, &shaderLengths, 1); - const EShMessages messages = - GetSpirvMessageOptionsForSemanticsAndTarget(semantics, target); + if (!entryPointName.empty()) shader.setEntryPoint(entryPointName.c_str()); + const EShMessages messages = DeriveOptions(source, semantics, target); // Reinitialize glslang if the semantics change. GlslangInitializer::InitializationToken token = GlobalTestSettings.initializer->acquire(messages); @@ -190,9 +195,9 @@ public: spv::SpvBuildLogger logger; - if (success && target == Target::Spirv) { + if (success && (target == Target::Spv || target == Target::BothASTAndSpv)) { std::vector spirv_binary; - glslang::GlslangToSpv(*program.getIntermediate(language), + glslang::GlslangToSpv(*program.getIntermediate(kind), spirv_binary, &logger); std::ostringstream disassembly_stream; @@ -210,7 +215,10 @@ public: void loadFileCompileAndCheck(const std::string& testDir, const std::string& testName, - Semantics semantics, Target target) + Source source, + Semantics semantics, + Target target, + const std::string& entryPointName="") { const std::string inputFname = testDir + "/" + testName; const std::string expectedOutputFname = @@ -221,7 +229,8 @@ public: tryLoadFile(expectedOutputFname, "expected output", &expectedOutput); GlslangResult result = - compileGlsl(input, GetSuffix(testName), semantics, target); + compile(input, source, GetSuffix(testName), + semantics, target, entryPointName); // Generate the hybrid output in the way of glslangValidator. std::ostringstream stream; @@ -236,7 +245,7 @@ public: outputIfNotEmpty(result.linkingOutput); outputIfNotEmpty(result.linkingError); stream << result.spirvWarningsErrors; - if (target == Target::Spirv) { + if (target == Target::Spv || target == Target::BothASTAndSpv) { stream << (result.spirv.empty() ? "SPIR-V is not generated for failed compile or link\n" @@ -247,10 +256,10 @@ public: expectedOutputFname); } - // Preprocesses the given GLSL |source| code. On success, returns true, the + // Preprocesses the given |source| code. On success, returns true, the // preprocessed shader, and warning messages. Otherwise, returns false, an // empty string, and error messages. - std::tuple preprocessGlsl( + std::tuple preprocess( const std::string& source) { const char* shaderStrings = source.data(); @@ -290,7 +299,7 @@ public: bool ppOk; std::string output, error; - std::tie(ppOk, output, error) = preprocessGlsl(input); + std::tie(ppOk, output, error) = preprocess(input); if (!output.empty()) output += '\n'; if (!error.empty()) error += '\n';