Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 1 | // |
| 2 | // Copyright (C) 2016 Google, Inc. |
| 3 | // |
| 4 | // All rights reserved. |
| 5 | // |
| 6 | // Redistribution and use in source and binary forms, with or without |
| 7 | // modification, are permitted provided that the following conditions |
| 8 | // are met: |
| 9 | // |
| 10 | // Redistributions of source code must retain the above copyright |
| 11 | // notice, this list of conditions and the following disclaimer. |
| 12 | // |
| 13 | // Redistributions in binary form must reproduce the above |
| 14 | // copyright notice, this list of conditions and the following |
| 15 | // disclaimer in the documentation and/or other materials provided |
| 16 | // with the distribution. |
| 17 | // |
| 18 | // Neither the name of Google Inc. nor the names of its |
| 19 | // contributors may be used to endorse or promote products derived |
| 20 | // from this software without specific prior written permission. |
| 21 | // |
| 22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 23 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 24 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| 25 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| 26 | // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| 27 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| 28 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 29 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| 30 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 31 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| 32 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 33 | // POSSIBILITY OF SUCH DAMAGE. |
| 34 | |
| 35 | #ifndef GLSLANG_GTESTS_TEST_FIXTURE_H |
| 36 | #define GLSLANG_GTESTS_TEST_FIXTURE_H |
| 37 | |
| 38 | #include <stdint.h> |
| 39 | #include <fstream> |
| 40 | #include <sstream> |
| 41 | #include <streambuf> |
| 42 | #include <tuple> |
| 43 | |
| 44 | #include <gtest/gtest.h> |
| 45 | |
| 46 | #include "SPIRV/GlslangToSpv.h" |
| 47 | #include "SPIRV/disassemble.h" |
| 48 | #include "SPIRV/doc.h" |
Lei Zhang | 8a9b1ee | 2016-05-19 13:31:43 -0400 | [diff] [blame] | 49 | #include "StandAlone/ResourceLimits.h" |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 50 | #include "glslang/Public/ShaderLang.h" |
| 51 | |
| 52 | #include "Initializer.h" |
| 53 | #include "Settings.h" |
| 54 | |
| 55 | // We need CMake to provide us the absolute path to the directory containing |
| 56 | // test files, so we are certain to find those files no matter where the test |
| 57 | // harness binary is generated. This provides out-of-source build capability. |
| 58 | #ifndef GLSLANG_TEST_DIRECTORY |
| 59 | #error \ |
| 60 | "GLSLANG_TEST_DIRECTORY needs to be defined for gtest to locate test files." |
| 61 | #endif |
| 62 | |
| 63 | namespace glslangtest { |
| 64 | |
| 65 | // This function is used to provide custom test name suffixes based on the |
| 66 | // shader source file names. Otherwise, the test name suffixes will just be |
| 67 | // numbers, which are not quite obvious. |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 68 | std::string FileNameAsCustomTestSuffix( |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 69 | const ::testing::TestParamInfo<std::string>& info); |
| 70 | |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 71 | enum class Source { |
| 72 | GLSL, |
| 73 | HLSL, |
| 74 | }; |
| 75 | |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 76 | // Enum for shader compilation semantics. |
| 77 | enum class Semantics { |
| 78 | OpenGL, |
| 79 | Vulkan, |
| 80 | }; |
| 81 | |
| 82 | // Enum for compilation target. |
| 83 | enum class Target { |
| 84 | AST, |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 85 | Spv, |
| 86 | BothASTAndSpv, |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 87 | }; |
| 88 | |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 89 | EShLanguage GetShaderStage(const std::string& stage); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 90 | |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 91 | EShMessages DeriveOptions(Source, Semantics, Target); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 92 | |
| 93 | // Reads the content of the file at the given |path|. On success, returns true |
| 94 | // and the contents; otherwise, returns false and an empty string. |
| 95 | std::pair<bool, std::string> ReadFile(const std::string& path); |
| 96 | |
| 97 | // Writes the given |contents| into the file at the given |path|. Returns true |
| 98 | // on successful output. |
| 99 | bool WriteFile(const std::string& path, const std::string& contents); |
| 100 | |
| 101 | // Returns the suffix of the given |name|. |
| 102 | std::string GetSuffix(const std::string& name); |
| 103 | |
| 104 | // Base class for glslang integration tests. It contains many handy utility-like |
| 105 | // methods such as reading shader source files, compiling into AST/SPIR-V, and |
| 106 | // comparing with expected outputs. |
| 107 | // |
| 108 | // To write value-Parameterized tests: |
| 109 | // using ValueParamTest = GlslangTest<::testing::TestWithParam<std::string>>; |
| 110 | // To use as normal fixture: |
| 111 | // using FixtureTest = GlslangTest<::testing::Test>; |
| 112 | template <typename GT> |
| 113 | class GlslangTest : public GT { |
| 114 | public: |
| 115 | GlslangTest() |
| 116 | : defaultVersion(100), |
| 117 | defaultProfile(ENoProfile), |
| 118 | forceVersionProfile(false), |
| 119 | isForwardCompatible(false) {} |
| 120 | |
| 121 | // Tries to load the contents from the file at the given |path|. On success, |
| 122 | // writes the contents into |contents|. On failure, errors out. |
| 123 | void tryLoadFile(const std::string& path, const std::string& tag, |
| 124 | std::string* contents) |
| 125 | { |
| 126 | bool fileReadOk; |
| 127 | std::tie(fileReadOk, *contents) = ReadFile(path); |
| 128 | ASSERT_TRUE(fileReadOk) << "Cannot open " << tag << " file: " << path; |
| 129 | } |
| 130 | |
| 131 | // Checks the equality of |expected| and |real|. If they are not equal, |
Lei Zhang | fc697cc | 2016-05-17 16:43:05 -0400 | [diff] [blame] | 132 | // write |real| to the given file named as |fname| if update mode is on. |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 133 | void checkEqAndUpdateIfRequested(const std::string& expected, |
| 134 | const std::string& real, |
| 135 | const std::string& fname) |
| 136 | { |
Lei Zhang | fc697cc | 2016-05-17 16:43:05 -0400 | [diff] [blame] | 137 | // In order to output the message we want under proper circumstances, |
| 138 | // we need the following operator<< stuff. |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 139 | EXPECT_EQ(expected, real) |
| 140 | << (GlobalTestSettings.updateMode |
| 141 | ? ("Mismatch found and update mode turned on - " |
| 142 | "flushing expected result output.") |
| 143 | : ""); |
| 144 | |
| 145 | // Update the expected output file if requested. |
| 146 | // It looks weird to duplicate the comparison between expected_output |
Lei Zhang | fc697cc | 2016-05-17 16:43:05 -0400 | [diff] [blame] | 147 | // and stream.str(). However, if creating a variable for the comparison |
| 148 | // result, we cannot have pretty print of the string diff in the above. |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 149 | if (GlobalTestSettings.updateMode && expected != real) { |
| 150 | EXPECT_TRUE(WriteFile(fname, real)) << "Flushing failed"; |
| 151 | } |
| 152 | } |
| 153 | |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 154 | struct ShaderResult { |
| 155 | std::string shaderName; |
| 156 | std::string output; |
| 157 | std::string error; |
| 158 | }; |
| 159 | |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 160 | // A struct for holding all the information returned by glslang compilation |
| 161 | // and linking. |
| 162 | struct GlslangResult { |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 163 | std::vector<ShaderResult> shaderResults; |
| 164 | std::string linkingOutput; |
| 165 | std::string linkingError; |
| 166 | std::string spirvWarningsErrors; |
| 167 | std::string spirv; // Optional SPIR-V disassembly text. |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 168 | }; |
| 169 | |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 170 | // Compiles and the given source |code| of the given shader |stage| into |
| 171 | // the target under the semantics conveyed via |controls|. Returns true |
| 172 | // and modifies |shader| on success. |
| 173 | bool compile(glslang::TShader* shader, const std::string& code, |
Lei Zhang | 1b14172 | 2016-05-19 13:50:49 -0400 | [diff] [blame] | 174 | const std::string& entryPointName, EShMessages controls, |
| 175 | const TBuiltInResource* resources=nullptr) |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 176 | { |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 177 | const char* shaderStrings = code.data(); |
| 178 | const int shaderLengths = static_cast<int>(code.size()); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 179 | |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 180 | shader->setStringsWithLengths(&shaderStrings, &shaderLengths, 1); |
| 181 | if (!entryPointName.empty()) shader->setEntryPoint(entryPointName.c_str()); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 182 | // Reinitialize glslang if the semantics change. |
John Kessenich | 7f349c7 | 2016-07-08 22:09:10 -0600 | [diff] [blame] | 183 | GlobalTestSettings.initializer->acquire(controls); |
Lei Zhang | 1b14172 | 2016-05-19 13:50:49 -0400 | [diff] [blame] | 184 | return shader->parse( |
| 185 | (resources ? resources : &glslang::DefaultTBuiltInResource), |
| 186 | defaultVersion, isForwardCompatible, controls); |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 187 | } |
| 188 | |
| 189 | // Compiles and links the given source |code| of the given shader |
| 190 | // |stage| into the target under the semantics specified via |controls|. |
| 191 | // Returns a GlslangResult instance containing all the information generated |
| 192 | // during the process. If the target includes SPIR-V, also disassembles |
| 193 | // the result and returns disassembly text. |
| 194 | GlslangResult compileAndLink( |
| 195 | const std::string shaderName, const std::string& code, |
| 196 | const std::string& entryPointName, EShMessages controls) |
| 197 | { |
| 198 | const EShLanguage kind = GetShaderStage(GetSuffix(shaderName)); |
| 199 | |
| 200 | glslang::TShader shader(kind); |
| 201 | bool success = compile(&shader, code, entryPointName, controls); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 202 | |
| 203 | glslang::TProgram program; |
| 204 | program.addShader(&shader); |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 205 | success &= program.link(controls); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 206 | |
Lei Zhang | 17535f7 | 2016-05-04 15:55:59 -0400 | [diff] [blame] | 207 | spv::SpvBuildLogger logger; |
Lei Zhang | 09caf12 | 2016-05-02 18:11:54 -0400 | [diff] [blame] | 208 | |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 209 | if (success && (controls & EShMsgSpvRules)) { |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 210 | std::vector<uint32_t> spirv_binary; |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 211 | glslang::GlslangToSpv(*program.getIntermediate(kind), |
Lei Zhang | 17535f7 | 2016-05-04 15:55:59 -0400 | [diff] [blame] | 212 | spirv_binary, &logger); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 213 | |
| 214 | std::ostringstream disassembly_stream; |
| 215 | spv::Parameterize(); |
| 216 | spv::Disassemble(disassembly_stream, spirv_binary); |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 217 | return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},}, |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 218 | program.getInfoLog(), program.getInfoDebugLog(), |
Lei Zhang | 17535f7 | 2016-05-04 15:55:59 -0400 | [diff] [blame] | 219 | logger.getAllMessages(), disassembly_stream.str()}; |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 220 | } else { |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 221 | return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},}, |
| 222 | program.getInfoLog(), program.getInfoDebugLog(), "", ""}; |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | void outputResultToStream(std::ostringstream* stream, |
| 227 | const GlslangResult& result, |
| 228 | EShMessages controls) |
| 229 | { |
| 230 | const auto outputIfNotEmpty = [&stream](const std::string& str) { |
| 231 | if (!str.empty()) *stream << str << "\n"; |
| 232 | }; |
| 233 | |
| 234 | for (const auto& shaderResult : result.shaderResults) { |
| 235 | *stream << shaderResult.shaderName << "\n"; |
| 236 | outputIfNotEmpty(shaderResult.output); |
| 237 | outputIfNotEmpty(shaderResult.error); |
| 238 | } |
| 239 | outputIfNotEmpty(result.linkingOutput); |
| 240 | outputIfNotEmpty(result.linkingError); |
| 241 | *stream << result.spirvWarningsErrors; |
| 242 | |
| 243 | if (controls & EShMsgSpvRules) { |
| 244 | *stream |
| 245 | << (result.spirv.empty() |
| 246 | ? "SPIR-V is not generated for failed compile or link\n" |
| 247 | : result.spirv); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 248 | } |
| 249 | } |
| 250 | |
| 251 | void loadFileCompileAndCheck(const std::string& testDir, |
| 252 | const std::string& testName, |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 253 | Source source, |
| 254 | Semantics semantics, |
| 255 | Target target, |
| 256 | const std::string& entryPointName="") |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 257 | { |
| 258 | const std::string inputFname = testDir + "/" + testName; |
| 259 | const std::string expectedOutputFname = |
| 260 | testDir + "/baseResults/" + testName + ".out"; |
| 261 | std::string input, expectedOutput; |
| 262 | |
| 263 | tryLoadFile(inputFname, "input", &input); |
| 264 | tryLoadFile(expectedOutputFname, "expected output", &expectedOutput); |
| 265 | |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 266 | const EShMessages controls = DeriveOptions(source, semantics, target); |
John Kessenich | a86836e | 2016-07-09 14:50:57 -0600 | [diff] [blame] | 267 | GlslangResult result = compileAndLink(testName, input, entryPointName, controls); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 268 | |
| 269 | // Generate the hybrid output in the way of glslangValidator. |
| 270 | std::ostringstream stream; |
Lei Zhang | 2f1ee45 | 2016-05-17 23:03:28 -0400 | [diff] [blame] | 271 | outputResultToStream(&stream, result, controls); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 272 | |
| 273 | checkEqAndUpdateIfRequested(expectedOutput, stream.str(), |
| 274 | expectedOutputFname); |
| 275 | } |
| 276 | |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 277 | // Preprocesses the given |source| code. On success, returns true, the |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 278 | // preprocessed shader, and warning messages. Otherwise, returns false, an |
| 279 | // empty string, and error messages. |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 280 | std::tuple<bool, std::string, std::string> preprocess( |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 281 | const std::string& source) |
| 282 | { |
| 283 | const char* shaderStrings = source.data(); |
| 284 | const int shaderLengths = static_cast<int>(source.size()); |
| 285 | |
| 286 | glslang::TShader shader(EShLangVertex); |
| 287 | shader.setStringsWithLengths(&shaderStrings, &shaderLengths, 1); |
| 288 | std::string ppShader; |
| 289 | glslang::TShader::ForbidInclude includer; |
| 290 | const bool success = shader.preprocess( |
| 291 | &glslang::DefaultTBuiltInResource, defaultVersion, defaultProfile, |
John Kessenich | a86836e | 2016-07-09 14:50:57 -0600 | [diff] [blame] | 292 | forceVersionProfile, isForwardCompatible, (EShMessages)(EShMsgOnlyPreprocessor | EShMsgCascadingErrors), |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 293 | &ppShader, includer); |
| 294 | |
| 295 | std::string log = shader.getInfoLog(); |
| 296 | log += shader.getInfoDebugLog(); |
| 297 | if (success) { |
| 298 | return std::make_tuple(true, ppShader, log); |
| 299 | } else { |
| 300 | return std::make_tuple(false, "", log); |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | void loadFilePreprocessAndCheck(const std::string& testDir, |
| 305 | const std::string& testName) |
| 306 | { |
| 307 | const std::string inputFname = testDir + "/" + testName; |
| 308 | const std::string expectedOutputFname = |
| 309 | testDir + "/baseResults/" + testName + ".out"; |
| 310 | const std::string expectedErrorFname = |
| 311 | testDir + "/baseResults/" + testName + ".err"; |
| 312 | std::string input, expectedOutput, expectedError; |
| 313 | |
| 314 | tryLoadFile(inputFname, "input", &input); |
| 315 | tryLoadFile(expectedOutputFname, "expected output", &expectedOutput); |
| 316 | tryLoadFile(expectedErrorFname, "expected error", &expectedError); |
| 317 | |
| 318 | bool ppOk; |
| 319 | std::string output, error; |
Lei Zhang | d6f0ed2 | 2016-05-16 12:50:30 -0400 | [diff] [blame] | 320 | std::tie(ppOk, output, error) = preprocess(input); |
Lei Zhang | 414eb60 | 2016-03-04 16:22:34 -0500 | [diff] [blame] | 321 | if (!output.empty()) output += '\n'; |
| 322 | if (!error.empty()) error += '\n'; |
| 323 | |
| 324 | checkEqAndUpdateIfRequested(expectedOutput, output, |
| 325 | expectedOutputFname); |
| 326 | checkEqAndUpdateIfRequested(expectedError, error, |
| 327 | expectedErrorFname); |
| 328 | } |
| 329 | |
| 330 | private: |
| 331 | const int defaultVersion; |
| 332 | const EProfile defaultProfile; |
| 333 | const bool forceVersionProfile; |
| 334 | const bool isForwardCompatible; |
| 335 | }; |
| 336 | |
| 337 | } // namespace glslangtest |
| 338 | |
| 339 | #endif // GLSLANG_GTESTS_TEST_FIXTURE_H |