Merge pull request #284 from antiagainst/gtest-hlsl

Tests: Add support for testing file-based HLSL source code in GTest.
This commit is contained in:
John Kessenich 2016-05-16 16:56:40 -06:00
commit e0a24778dd
8 changed files with 142 additions and 38 deletions

View File

@ -44,7 +44,8 @@ using CompileToAstTest = GlslangTest<::testing::TestWithParam<std::string>>;
TEST_P(CompileToAstTest, FromFile) TEST_P(CompileToAstTest, FromFile)
{ {
loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam(), loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam(),
Semantics::OpenGL, Target::AST); Source::GLSL, Semantics::OpenGL,
Target::AST);
} }
// clang-format off // clang-format off
@ -183,7 +184,7 @@ INSTANTIATE_TEST_CASE_P(
"nonVulkan.frag", "nonVulkan.frag",
"spv.atomic.comp", "spv.atomic.comp",
})), })),
FileNameAsCustomTestName FileNameAsCustomTestSuffix
); );
// clang-format on // clang-format on

View File

@ -13,6 +13,7 @@ if (TARGET gmock)
# Test related source files # Test related source files
${CMAKE_CURRENT_SOURCE_DIR}/AST.FromFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AST.FromFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BuiltInResource.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}/Pp.FromFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Spv.FromFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Spv.FromFile.cpp
) )

81
gtests/Hlsl.FromFile.cpp Normal file
View File

@ -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 <gtest/gtest.h>
#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<FileNameEntryPointPair>& 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<FileNameEntryPointPair>>;
// 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<FileNameEntryPointPair>{
{"hlsl.frag", "PixelShaderFunction"},
}),
FileNameAsCustomTestSuffix
);
// clang-format on
} // anonymous namespace
} // namespace glslangtest

View File

@ -69,7 +69,7 @@ public:
InitializationToken acquire(EShMessages new_messages) InitializationToken acquire(EShMessages new_messages)
{ {
if ((lastMessages ^ new_messages) & if ((lastMessages ^ new_messages) &
(EShMsgVulkanRules | EShMsgSpvRules)) { (EShMsgVulkanRules | EShMsgSpvRules | EShMsgReadHlsl)) {
glslang::FinalizeProcess(); glslang::FinalizeProcess();
glslang::InitializeProcess(); glslang::InitializeProcess();
} }

View File

@ -66,7 +66,7 @@ INSTANTIATE_TEST_CASE_P(
"preprocessor.defined.vert", "preprocessor.defined.vert",
"preprocessor.many.endif.vert", "preprocessor.many.endif.vert",
})), })),
FileNameAsCustomTestName FileNameAsCustomTestSuffix
); );
// clang-format on // clang-format on

View File

@ -49,7 +49,8 @@ using VulkanSemantics = GlslangTest<::testing::TestWithParam<std::string>>;
TEST_P(CompileToSpirvTest, FromFile) TEST_P(CompileToSpirvTest, FromFile)
{ {
loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam(), 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 // GLSL-level Vulkan semantics test. Expected to error out before generating
@ -57,7 +58,8 @@ TEST_P(CompileToSpirvTest, FromFile)
TEST_P(VulkanSemantics, FromFile) TEST_P(VulkanSemantics, FromFile)
{ {
loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam(), loadFileCompileAndCheck(GLSLANG_TEST_DIRECTORY, GetParam(),
Semantics::Vulkan, Target::Spirv); Source::GLSL, Semantics::Vulkan,
Target::Spv);
} }
// clang-format off // clang-format off
@ -173,7 +175,7 @@ INSTANTIATE_TEST_CASE_P(
"spv.specConstant.comp", "spv.specConstant.comp",
"spv.specConstantComposite.vert", "spv.specConstantComposite.vert",
})), })),
FileNameAsCustomTestName FileNameAsCustomTestSuffix
); );
INSTANTIATE_TEST_CASE_P( INSTANTIATE_TEST_CASE_P(
@ -183,7 +185,7 @@ INSTANTIATE_TEST_CASE_P(
"vulkan.vert", "vulkan.vert",
"vulkan.comp", "vulkan.comp",
})), })),
FileNameAsCustomTestName FileNameAsCustomTestSuffix
); );
// clang-format on // clang-format on

View File

@ -36,7 +36,7 @@
namespace glslangtest { namespace glslangtest {
std::string FileNameAsCustomTestName( std::string FileNameAsCustomTestSuffix(
const ::testing::TestParamInfo<std::string>& info) const ::testing::TestParamInfo<std::string>& info)
{ {
std::string name = info.param; std::string name = info.param;
@ -46,7 +46,7 @@ std::string FileNameAsCustomTestName(
return name; return name;
} }
EShLanguage GetGlslLanguageForStage(const std::string& stage) EShLanguage GetShaderStage(const std::string& stage)
{ {
if (stage == "vert") { if (stage == "vert") {
return EShLangVertex; return EShLangVertex;
@ -66,17 +66,27 @@ EShLanguage GetGlslLanguageForStage(const std::string& stage)
} }
} }
EShMessages GetSpirvMessageOptionsForSemanticsAndTarget(Semantics semantics, EShMessages DeriveOptions(Source source, Semantics semantics, Target target)
Target target)
{ {
EShMessages result = EShMsgDefault; EShMessages result = EShMsgDefault;
switch (source) {
case Source::GLSL:
break;
case Source::HLSL:
result = EShMsgReadHlsl;
break;
}
switch (target) { switch (target) {
case Target::AST: case Target::AST:
result = EShMsgAST; result = static_cast<EShMessages>(result | EShMsgAST);
break; break;
case Target::Spirv: case Target::Spv:
result = EShMsgSpvRules; result = static_cast<EShMessages>(result | EShMsgSpvRules);
break;
case Target::BothASTAndSpv:
result = static_cast<EShMessages>(result | EShMsgSpvRules | EShMsgAST);
break; break;
}; };

View File

@ -65,9 +65,14 @@ namespace glslangtest {
// This function is used to provide custom test name suffixes based on the // 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 // shader source file names. Otherwise, the test name suffixes will just be
// numbers, which are not quite obvious. // numbers, which are not quite obvious.
std::string FileNameAsCustomTestName( std::string FileNameAsCustomTestSuffix(
const ::testing::TestParamInfo<std::string>& info); const ::testing::TestParamInfo<std::string>& info);
enum class Source {
GLSL,
HLSL,
};
// Enum for shader compilation semantics. // Enum for shader compilation semantics.
enum class Semantics { enum class Semantics {
OpenGL, OpenGL,
@ -77,13 +82,13 @@ enum class Semantics {
// Enum for compilation target. // Enum for compilation target.
enum class Target { enum class Target {
AST, AST,
Spirv, Spv,
BothASTAndSpv,
}; };
EShLanguage GetGlslLanguageForStage(const std::string& stage); EShLanguage GetShaderStage(const std::string& stage);
EShMessages GetSpirvMessageOptionsForSemanticsAndTarget(Semantics semantics, EShMessages DeriveOptions(Source, Semantics, Target);
Target target);
// Reads the content of the file at the given |path|. On success, returns true // Reads the content of the file at the given |path|. On success, returns true
// and the contents; otherwise, returns false and an empty string. // and the contents; otherwise, returns false and an empty string.
@ -160,23 +165,23 @@ public:
const std::string spirv; // Optional SPIR-V disassembly text. 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 // |stage| into the given |target| under the given |semantics|. Returns
// a GlslangResult instance containing all the information generated // a GlslangResult instance containing all the information generated
// during the process. If |target| is Target::Spirv, also disassembles // during the process. If |target| is Target::Spirv, also disassembles
// the result and returns disassembly text. // the result and returns disassembly text.
GlslangResult compileGlsl(const std::string& source, GlslangResult compile(const std::string& code, Source source,
const std::string& stage, Semantics semantics, const std::string& stage, Semantics semantics,
Target target) Target target, const std::string& entryPointName)
{ {
const char* shaderStrings = source.data(); const char* shaderStrings = code.data();
const int shaderLengths = static_cast<int>(source.size()); const int shaderLengths = static_cast<int>(code.size());
const EShLanguage language = GetGlslLanguageForStage(stage); const EShLanguage kind = GetShaderStage(stage);
glslang::TShader shader(language); glslang::TShader shader(kind);
shader.setStringsWithLengths(&shaderStrings, &shaderLengths, 1); shader.setStringsWithLengths(&shaderStrings, &shaderLengths, 1);
const EShMessages messages = if (!entryPointName.empty()) shader.setEntryPoint(entryPointName.c_str());
GetSpirvMessageOptionsForSemanticsAndTarget(semantics, target); const EShMessages messages = DeriveOptions(source, semantics, target);
// Reinitialize glslang if the semantics change. // Reinitialize glslang if the semantics change.
GlslangInitializer::InitializationToken token = GlslangInitializer::InitializationToken token =
GlobalTestSettings.initializer->acquire(messages); GlobalTestSettings.initializer->acquire(messages);
@ -190,9 +195,9 @@ public:
spv::SpvBuildLogger logger; spv::SpvBuildLogger logger;
if (success && target == Target::Spirv) { if (success && (target == Target::Spv || target == Target::BothASTAndSpv)) {
std::vector<uint32_t> spirv_binary; std::vector<uint32_t> spirv_binary;
glslang::GlslangToSpv(*program.getIntermediate(language), glslang::GlslangToSpv(*program.getIntermediate(kind),
spirv_binary, &logger); spirv_binary, &logger);
std::ostringstream disassembly_stream; std::ostringstream disassembly_stream;
@ -210,7 +215,10 @@ public:
void loadFileCompileAndCheck(const std::string& testDir, void loadFileCompileAndCheck(const std::string& testDir,
const std::string& testName, 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 inputFname = testDir + "/" + testName;
const std::string expectedOutputFname = const std::string expectedOutputFname =
@ -221,7 +229,8 @@ public:
tryLoadFile(expectedOutputFname, "expected output", &expectedOutput); tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
GlslangResult result = 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. // Generate the hybrid output in the way of glslangValidator.
std::ostringstream stream; std::ostringstream stream;
@ -236,7 +245,7 @@ public:
outputIfNotEmpty(result.linkingOutput); outputIfNotEmpty(result.linkingOutput);
outputIfNotEmpty(result.linkingError); outputIfNotEmpty(result.linkingError);
stream << result.spirvWarningsErrors; stream << result.spirvWarningsErrors;
if (target == Target::Spirv) { if (target == Target::Spv || target == Target::BothASTAndSpv) {
stream stream
<< (result.spirv.empty() << (result.spirv.empty()
? "SPIR-V is not generated for failed compile or link\n" ? "SPIR-V is not generated for failed compile or link\n"
@ -247,10 +256,10 @@ public:
expectedOutputFname); 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 // preprocessed shader, and warning messages. Otherwise, returns false, an
// empty string, and error messages. // empty string, and error messages.
std::tuple<bool, std::string, std::string> preprocessGlsl( std::tuple<bool, std::string, std::string> preprocess(
const std::string& source) const std::string& source)
{ {
const char* shaderStrings = source.data(); const char* shaderStrings = source.data();
@ -290,7 +299,7 @@ public:
bool ppOk; bool ppOk;
std::string output, error; std::string output, error;
std::tie(ppOk, output, error) = preprocessGlsl(input); std::tie(ppOk, output, error) = preprocess(input);
if (!output.empty()) output += '\n'; if (!output.empty()) output += '\n';
if (!error.empty()) error += '\n'; if (!error.empty()) error += '\n';