xref: /aosp_15_r20/external/angle/third_party/glslang/src/gtests/TestFixture.h (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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 <algorithm>
39 #include <cstdint>
40 #include <fstream>
41 #include <sstream>
42 #include <streambuf>
43 #include <tuple>
44 #include <string>
45 
46 #include <gtest/gtest.h>
47 
48 #include "SPIRV/GlslangToSpv.h"
49 #include "SPIRV/disassemble.h"
50 #include "SPIRV/doc.h"
51 #include "SPIRV/SPVRemapper.h"
52 #include "glslang/Include/Types.h"
53 #include "glslang/Public/ResourceLimits.h"
54 #include "glslang/Public/ShaderLang.h"
55 
56 #include "Initializer.h"
57 #include "Settings.h"
58 
59 namespace glslangtest {
60 
61 // This function is used to provide custom test name suffixes based on the
62 // shader source file names. Otherwise, the test name suffixes will just be
63 // numbers, which are not quite obvious.
64 std::string FileNameAsCustomTestSuffix(
65     const ::testing::TestParamInfo<std::string>& info);
66 
67 enum class Source {
68   GLSL,
69   HLSL,
70 };
71 
72 // Enum for shader compilation semantics.
73 enum class Semantics {
74     OpenGL,
75     Vulkan
76 };
77 
78 // Enum for compilation target.
79 enum class Target {
80     AST,
81     Spv,
82     BothASTAndSpv,
83 };
84 
85 EShLanguage GetShaderStage(const std::string& stage);
86 
87 EShMessages DeriveOptions(Source, Semantics, Target);
88 
89 // Reads the content of the file at the given |path|. On success, returns true
90 // and the contents; otherwise, returns false and an empty string.
91 std::pair<bool, std::string> ReadFile(const std::string& path);
92 std::pair<bool, std::vector<std::uint32_t> > ReadSpvBinaryFile(const std::string& path);
93 
94 // Writes the given |contents| into the file at the given |path|. Returns true
95 // on successful output.
96 bool WriteFile(const std::string& path, const std::string& contents);
97 
98 // Returns the suffix of the given |name|.
99 std::string GetSuffix(const std::string& name);
100 
101 // Base class for glslang integration tests. It contains many handy utility-like
102 // methods such as reading shader source files, compiling into AST/SPIR-V, and
103 // comparing with expected outputs.
104 //
105 // To write value-Parameterized tests:
106 //   using ValueParamTest = GlslangTest<::testing::TestWithParam<std::string>>;
107 // To use as normal fixture:
108 //   using FixtureTest = GlslangTest<::testing::Test>;
109 template <typename GT>
110 class GlslangTest : public GT {
111 public:
GlslangTest()112     GlslangTest()
113         : defaultVersion(100),
114           defaultProfile(ENoProfile),
115           forceVersionProfile(false),
116           isForwardCompatible(false) {
117         // Perform validation by default.
118         spirvOptions.validate = true;
119     }
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.
tryLoadFile(const std::string & path,const std::string & tag,std::string * contents)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     // Tries to load the contents from the file at the given |path|. On success,
132     // writes the contents into |contents|. On failure, errors out.
tryLoadSpvFile(const std::string & path,const std::string & tag,std::vector<uint32_t> & contents)133     void tryLoadSpvFile(const std::string& path, const std::string& tag,
134                         std::vector<uint32_t>& contents)
135     {
136         bool fileReadOk;
137         std::tie(fileReadOk, contents) = ReadSpvBinaryFile(path);
138         ASSERT_TRUE(fileReadOk) << "Cannot open " << tag << " file: " << path;
139     }
140 
141     // Checks the equality of |expected| and |real|. If they are not equal,
142     // write |real| to the given file named as |fname| if update mode is on.
143     void checkEqAndUpdateIfRequested(const std::string& expected,
144                                      const std::string& real,
145                                      const std::string& fname,
146                                      const std::string& errorsAndWarnings = "")
147     {
148         // In order to output the message we want under proper circumstances,
149         // we need the following operator<< stuff.
150         EXPECT_EQ(expected, real)
151             << (GlobalTestSettings.updateMode
152                     ? ("Mismatch found and update mode turned on - "
153                        "flushing expected result output.\n")
154                     : "")
155             << "The following warnings/errors occurred:\n"
156             << errorsAndWarnings;
157 
158         // Update the expected output file if requested.
159         // It looks weird to duplicate the comparison between expected_output
160         // and stream.str(). However, if creating a variable for the comparison
161         // result, we cannot have pretty print of the string diff in the above.
162         if (GlobalTestSettings.updateMode && expected != real) {
163             EXPECT_TRUE(WriteFile(fname, real)) << "Flushing failed";
164         }
165     }
166 
167     struct ShaderResult {
168         std::string shaderName;
169         std::string output;
170         std::string error;
171     };
172 
173     // A struct for holding all the information returned by glslang compilation
174     // and linking.
175     struct GlslangResult {
176         std::vector<ShaderResult> shaderResults;
177         std::string linkingOutput;
178         std::string linkingError;
179         bool validationResult;
180         std::string spirvWarningsErrors;
181         std::string spirv;  // Optional SPIR-V disassembly text.
182     };
183 
184     // Compiles and the given source |code| of the given shader |stage| into
185     // the target under the semantics conveyed via |controls|. Returns true
186     // and modifies |shader| on success.
187     bool compile(glslang::TShader* shader, const std::string& code,
188                  const std::string& entryPointName, EShMessages controls,
189                  const TBuiltInResource* resources=nullptr,
190                  const std::string* shaderName=nullptr)
191     {
192         const char* shaderStrings = code.data();
193         const int shaderLengths = static_cast<int>(code.size());
194         const char* shaderNames = nullptr;
195 
196         if ((controls & EShMsgDebugInfo) && shaderName != nullptr) {
197             shaderNames = shaderName->data();
198             shader->setStringsWithLengthsAndNames(
199                     &shaderStrings, &shaderLengths, &shaderNames, 1);
200         } else
201             shader->setStringsWithLengths(&shaderStrings, &shaderLengths, 1);
202         if (!entryPointName.empty()) shader->setEntryPoint(entryPointName.c_str());
203 
204         // A includer that always assumes header name is a relative path to the test folder.
205         class GlslangTestIncluder : public glslang::TShader::Includer {
206         public:
includeLocal(const char * headerName,const char *,size_t)207             virtual IncludeResult* includeLocal(const char* headerName, const char* /*includerName*/,
208                                                 size_t /*inclusionDepth*/) override
209             {
210                 std::string path = GLSLANG_TEST_DIRECTORY;
211                 path += '/';
212                 path += headerName;
213                 std::replace(path.begin(), path.end(), '\\', '/');
214 
215                 auto [success, fileContent] = ReadFile(path);
216                 if (success) {
217                     auto buffer = new char[fileContent.size() + 1];
218                     std::copy(fileContent.begin(), fileContent.end(), buffer);
219                     buffer[fileContent.size()] = '\0';
220 
221                     return new IncludeResult(headerName, buffer, fileContent.size(), buffer);
222                 }
223 
224                 return nullptr;
225             }
226 
releaseInclude(IncludeResult * result)227             virtual void releaseInclude(IncludeResult* result) override
228             {
229                 if (result != nullptr) {
230                     delete[] static_cast<char*>(result->userData);
231                     delete result;
232                 }
233             }
234         };
235 
236         GlslangTestIncluder includer;
237         return shader->parse((resources ? resources : GetDefaultResources()), defaultVersion, isForwardCompatible,
238                              controls, includer);
239     }
240 
241     // Compiles and links the given source |code| of the given shader
242     // |stage| into the target under the semantics specified via |controls|.
243     // Returns a GlslangResult instance containing all the information generated
244     // during the process. If the target includes SPIR-V, also disassembles
245     // the result and returns disassembly text.
246     GlslangResult compileAndLink(
247             const std::string& shaderName, const std::string& code,
248             const std::string& entryPointName, EShMessages controls,
249             glslang::EShTargetClientVersion clientTargetVersion,
250             glslang::EShTargetLanguageVersion targetLanguageVersion,
251             bool flattenUniformArrays = false,
252             EShTextureSamplerTransformMode texSampTransMode = EShTexSampTransKeep,
253             bool enableOptimizer = false,
254             bool enableDebug = false,
255             bool enableNonSemanticShaderDebugInfo = false,
256             bool automap = true)
257     {
258         const EShLanguage stage = GetShaderStage(GetSuffix(shaderName));
259 
260         glslang::TShader shader(stage);
261         if (automap) {
262             shader.setAutoMapLocations(true);
263             shader.setAutoMapBindings(true);
264         }
265 
266         if (enableDebug) {
267             shader.setDebugInfo(true);
268         }
269         if (enableNonSemanticShaderDebugInfo) {
270             assert(enableDebug && "Debug must be on for non-semantic debug info");
271         }
272 
273         shader.setTextureSamplerTransformMode(texSampTransMode);
274 #ifdef ENABLE_HLSL
275         shader.setFlattenUniformArrays(flattenUniformArrays);
276 #endif
277 
278         if (controls & EShMsgSpvRules) {
279             if (controls & EShMsgVulkanRules) {
280                 shader.setEnvInput((controls & EShMsgReadHlsl) ? glslang::EShSourceHlsl
281                                                                : glslang::EShSourceGlsl,
282                                     stage, glslang::EShClientVulkan, 100);
283                 shader.setEnvClient(glslang::EShClientVulkan, clientTargetVersion);
284                 shader.setEnvTarget(glslang::EShTargetSpv, targetLanguageVersion);
285             } else {
286                 shader.setEnvInput((controls & EShMsgReadHlsl) ? glslang::EShSourceHlsl
287                                                                : glslang::EShSourceGlsl,
288                                     stage, glslang::EShClientOpenGL, 100);
289                 shader.setEnvClient(glslang::EShClientOpenGL, clientTargetVersion);
290                 shader.setEnvTarget(glslang::EshTargetSpv, glslang::EShTargetSpv_1_0);
291             }
292         }
293 
294         if (options().compileOnly)
295             shader.setCompileOnly();
296 
297         bool success = compile(
298                 &shader, code, entryPointName, controls, nullptr, &shaderName);
299 
300         glslang::TProgram program;
301         spv::SpvBuildLogger logger;
302         std::vector<uint32_t> spirv_binary;
303 
304         if (!options().compileOnly) {
305             program.addShader(&shader);
306             success &= program.link(controls);
307             if (success)
308                 program.mapIO();
309 
310             if (success && (controls & EShMsgSpvRules)) {
311                 options().disableOptimizer = !enableOptimizer;
312                 options().generateDebugInfo = enableDebug;
313                 options().emitNonSemanticShaderDebugInfo = enableNonSemanticShaderDebugInfo;
314                 options().emitNonSemanticShaderDebugSource = enableNonSemanticShaderDebugInfo;
315                 glslang::GlslangToSpv(*program.getIntermediate(stage), spirv_binary, &logger, &options());
316             } else {
317                 return {{
318                             {shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},
319                         },
320                         program.getInfoLog(),
321                         program.getInfoDebugLog(),
322                         true,
323                         "",
324                         ""};
325             }
326         } else {
327             options().disableOptimizer = !enableOptimizer;
328             options().generateDebugInfo = enableDebug;
329             options().emitNonSemanticShaderDebugInfo = enableNonSemanticShaderDebugInfo;
330             options().emitNonSemanticShaderDebugSource = enableNonSemanticShaderDebugInfo;
331             glslang::GlslangToSpv(*shader.getIntermediate(), spirv_binary, &logger, &options());
332         }
333 
334         std::ostringstream disassembly_stream;
335         spv::Disassemble(disassembly_stream, spirv_binary);
336         bool validation_result = !options().validate || logger.getAllMessages().empty();
337         return {{
338                     {shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},
339                 },
340                 program.getInfoLog(),
341                 program.getInfoDebugLog(),
342                 validation_result,
343                 logger.getAllMessages(),
344                 disassembly_stream.str()};
345     }
346 
347     // Compiles and links the given source |code| of the given shader
348     // |stage| into the target under the semantics specified via |controls|.
349     // Returns a GlslangResult instance containing all the information generated
350     // during the process. If the target includes SPIR-V, also disassembles
351     // the result and returns disassembly text.
compileLinkIoMap(const std::string shaderName,const std::string & code,const std::string & entryPointName,EShMessages controls,int baseSamplerBinding,int baseTextureBinding,int baseImageBinding,int baseUboBinding,int baseSsboBinding,bool autoMapBindings,bool flattenUniformArrays)352     GlslangResult compileLinkIoMap(
353             const std::string shaderName, const std::string& code,
354             const std::string& entryPointName, EShMessages controls,
355             int baseSamplerBinding,
356             int baseTextureBinding,
357             int baseImageBinding,
358             int baseUboBinding,
359             int baseSsboBinding,
360             bool autoMapBindings,
361             bool flattenUniformArrays)
362     {
363         const EShLanguage stage = GetShaderStage(GetSuffix(shaderName));
364 
365         glslang::TShader shader(stage);
366         shader.setShiftSamplerBinding(baseSamplerBinding);
367         shader.setShiftTextureBinding(baseTextureBinding);
368         shader.setShiftImageBinding(baseImageBinding);
369         shader.setShiftUboBinding(baseUboBinding);
370         shader.setShiftSsboBinding(baseSsboBinding);
371         shader.setAutoMapBindings(autoMapBindings);
372         shader.setAutoMapLocations(true);
373 #ifdef ENABLE_HLSL
374         shader.setFlattenUniformArrays(flattenUniformArrays);
375 #endif
376 
377         bool success = compile(&shader, code, entryPointName, controls);
378 
379         glslang::TProgram program;
380         program.addShader(&shader);
381 
382         success &= program.link(controls);
383         if (success)
384             program.mapIO();
385 
386         spv::SpvBuildLogger logger;
387 
388         if (success && (controls & EShMsgSpvRules)) {
389             std::vector<uint32_t> spirv_binary;
390             glslang::GlslangToSpv(*program.getIntermediate(stage),
391                                   spirv_binary, &logger, &options());
392 
393             std::ostringstream disassembly_stream;
394             spv::Disassemble(disassembly_stream, spirv_binary);
395             bool validation_result = !options().validate || logger.getAllMessages().empty();
396             return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
397                     program.getInfoLog(), program.getInfoDebugLog(),
398                     validation_result, logger.getAllMessages(), disassembly_stream.str()};
399         } else {
400             return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
401                     program.getInfoLog(), program.getInfoDebugLog(), true, "", ""};
402         }
403     }
404 
405     // This is like compileAndLink but with remapping of the SPV binary
406     // through spirvbin_t::remap().  While technically this could be merged
407     // with compileAndLink() above (with the remap step optionally being a no-op)
408     // it is given separately here for ease of future extraction.
409     GlslangResult compileLinkRemap(
410             const std::string shaderName, const std::string& code,
411             const std::string& entryPointName, EShMessages controls,
412             const unsigned int remapOptions = spv::spirvbin_t::NONE)
413     {
414         const EShLanguage stage = GetShaderStage(GetSuffix(shaderName));
415 
416         glslang::TShader shader(stage);
417         shader.setAutoMapBindings(true);
418         shader.setAutoMapLocations(true);
419 
420         bool success = compile(&shader, code, entryPointName, controls);
421 
422         glslang::TProgram program;
423         program.addShader(&shader);
424         success &= program.link(controls);
425         if (success)
426             program.mapIO();
427 
428         if (success && (controls & EShMsgSpvRules)) {
429         spv::SpvBuildLogger logger;
430             std::vector<std::string> whiteListStrings;
431             std::vector<uint32_t> spirv_binary;
432             glslang::GlslangToSpv(*program.getIntermediate(stage),
433                                   spirv_binary, &logger, &options());
434 
435             spv::spirvbin_t(0 /*verbosity*/).remap(spirv_binary, whiteListStrings, remapOptions);
436 
437             std::ostringstream disassembly_stream;
438             spv::Disassemble(disassembly_stream, spirv_binary);
439             bool validation_result = !options().validate || logger.getAllMessages().empty();
440             return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
441                     program.getInfoLog(), program.getInfoDebugLog(),
442                     validation_result, logger.getAllMessages(), disassembly_stream.str()};
443         } else {
444             return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
445                     program.getInfoLog(), program.getInfoDebugLog(), true, "", ""};
446         }
447     }
448 
449     // remap the binary in 'code' with the options in remapOptions
450     GlslangResult remap(
451             const std::string shaderName, const std::vector<uint32_t>& code,
452             EShMessages controls,
453             const unsigned int remapOptions = spv::spirvbin_t::NONE)
454     {
455         if ((controls & EShMsgSpvRules)) {
456             std::vector<uint32_t> spirv_binary(code); // scratch copy
457             std::vector<std::string> whiteListStrings;
458             spv::spirvbin_t(0 /*verbosity*/).remap(spirv_binary, whiteListStrings, remapOptions);
459 
460             std::ostringstream disassembly_stream;
461             spv::Disassemble(disassembly_stream, spirv_binary);
462 
463             return {{{shaderName, "", ""},},
464                     "", "",
465                     true, "", disassembly_stream.str()};
466         } else {
467             return {{{shaderName, "", ""},}, "", "", true, "", ""};
468         }
469     }
470 
outputResultToStream(std::ostringstream * stream,const GlslangResult & result,EShMessages controls)471     void outputResultToStream(std::ostringstream* stream,
472                               const GlslangResult& result,
473                               EShMessages controls)
474     {
475         const auto outputIfNotEmpty = [&stream](const std::string& str) {
476             if (!str.empty()) *stream << str << "\n";
477         };
478 
479         for (const auto& shaderResult : result.shaderResults) {
480             *stream << shaderResult.shaderName << "\n";
481             outputIfNotEmpty(shaderResult.output);
482             outputIfNotEmpty(shaderResult.error);
483         }
484         outputIfNotEmpty(result.linkingOutput);
485         outputIfNotEmpty(result.linkingError);
486         if (!result.validationResult) {
487           *stream << "Validation failed\n";
488         }
489 
490         if (controls & EShMsgSpvRules) {
491             *stream
492                 << (result.spirv.empty()
493                         ? "SPIR-V is not generated for failed compile or link\n"
494                         : result.spirv);
495         }
496     }
497 
498     void loadFileCompileAndCheck(const std::string& testDir,
499                                  const std::string& testName,
500                                  Source source,
501                                  Semantics semantics,
502                                  glslang::EShTargetClientVersion clientTargetVersion,
503                                  glslang::EShTargetLanguageVersion targetLanguageVersion,
504                                  Target target,
505                                  bool automap = true,
506                                  const std::string& entryPointName="",
507                                  const std::string& baseDir="/baseResults/",
508                                  const bool enableOptimizer = false,
509                                  const bool enableDebug = false,
510                                  const bool enableNonSemanticShaderDebugInfo = false)
511     {
512         const std::string inputFname = testDir + "/" + testName;
513         const std::string expectedOutputFname =
514             testDir + baseDir + testName + ".out";
515         std::string input, expectedOutput;
516 
517         tryLoadFile(inputFname, "input", &input);
518         tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
519 
520         EShMessages controls = DeriveOptions(source, semantics, target);
521         if (enableOptimizer)
522             controls = static_cast<EShMessages>(controls & ~EShMsgHlslLegalization);
523         if (enableDebug)
524             controls = static_cast<EShMessages>(controls | EShMsgDebugInfo);
525         GlslangResult result = compileAndLink(testName, input, entryPointName, controls, clientTargetVersion,
526             targetLanguageVersion, false, EShTexSampTransKeep, enableOptimizer, enableDebug,
527             enableNonSemanticShaderDebugInfo, automap);
528 
529         // Generate the hybrid output in the way of glslang.
530         std::ostringstream stream;
531         outputResultToStream(&stream, result, controls);
532 
533         checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
534                                     expectedOutputFname, result.spirvWarningsErrors);
535     }
536 
537     void loadFileCompileAndCheckWithOptions(const std::string &testDir,
538                                             const std::string &testName,
539                                             Source source,
540                                             Semantics semantics,
541                                             glslang::EShTargetClientVersion clientTargetVersion,
542                                             glslang::EShTargetLanguageVersion targetLanguageVersion,
543                                             Target target, bool automap = true, const std::string &entryPointName = "",
544                                             const std::string &baseDir = "/baseResults/",
545                                             const EShMessages additionalOptions = EShMessages::EShMsgDefault)
546     {
547         const std::string inputFname = testDir + "/" + testName;
548         const std::string expectedOutputFname = testDir + baseDir + testName + ".out";
549         std::string input, expectedOutput;
550 
551         tryLoadFile(inputFname, "input", &input);
552         tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
553 
554         EShMessages controls = DeriveOptions(source, semantics, target);
555         controls = static_cast<EShMessages>(controls | additionalOptions);
556         GlslangResult result = compileAndLink(testName, input, entryPointName, controls, clientTargetVersion,
557             targetLanguageVersion, false, EShTexSampTransKeep, false, automap);
558 
559         // Generate the hybrid output in the way of glslang.
560         std::ostringstream stream;
561         outputResultToStream(&stream, result, controls);
562 
563         checkEqAndUpdateIfRequested(expectedOutput, stream.str(), expectedOutputFname);
564     }
565 
566     void loadFileCompileFlattenUniformsAndCheck(const std::string& testDir,
567                                                 const std::string& testName,
568                                                 Source source,
569                                                 Semantics semantics,
570                                                 Target target,
571                                                 const std::string& entryPointName="")
572     {
573         const std::string inputFname = testDir + "/" + testName;
574         const std::string expectedOutputFname =
575             testDir + "/baseResults/" + testName + ".out";
576         std::string input, expectedOutput;
577 
578         tryLoadFile(inputFname, "input", &input);
579         tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
580 
581         const EShMessages controls = DeriveOptions(source, semantics, target);
582         GlslangResult result = compileAndLink(testName, input, entryPointName, controls,
583                                               glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, true);
584 
585         // Generate the hybrid output in the way of glslang.
586         std::ostringstream stream;
587         outputResultToStream(&stream, result, controls);
588 
589         checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
590                                     expectedOutputFname, result.spirvWarningsErrors);
591     }
592 
loadFileCompileIoMapAndCheck(const std::string & testDir,const std::string & testName,Source source,Semantics semantics,Target target,const std::string & entryPointName,int baseSamplerBinding,int baseTextureBinding,int baseImageBinding,int baseUboBinding,int baseSsboBinding,bool autoMapBindings,bool flattenUniformArrays)593     void loadFileCompileIoMapAndCheck(const std::string& testDir,
594                                       const std::string& testName,
595                                       Source source,
596                                       Semantics semantics,
597                                       Target target,
598                                       const std::string& entryPointName,
599                                       int baseSamplerBinding,
600                                       int baseTextureBinding,
601                                       int baseImageBinding,
602                                       int baseUboBinding,
603                                       int baseSsboBinding,
604                                       bool autoMapBindings,
605                                       bool flattenUniformArrays)
606     {
607         const std::string inputFname = testDir + "/" + testName;
608         const std::string expectedOutputFname =
609             testDir + "/baseResults/" + testName + ".out";
610         std::string input, expectedOutput;
611 
612         tryLoadFile(inputFname, "input", &input);
613         tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
614 
615         const EShMessages controls = DeriveOptions(source, semantics, target);
616         GlslangResult result = compileLinkIoMap(testName, input, entryPointName, controls,
617                                                 baseSamplerBinding, baseTextureBinding, baseImageBinding,
618                                                 baseUboBinding, baseSsboBinding,
619                                                 autoMapBindings,
620                                                 flattenUniformArrays);
621 
622         // Generate the hybrid output in the way of glslang.
623         std::ostringstream stream;
624         outputResultToStream(&stream, result, controls);
625 
626         checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
627                                     expectedOutputFname, result.spirvWarningsErrors);
628     }
629 
630     void loadFileCompileRemapAndCheck(const std::string& testDir,
631                                       const std::string& testName,
632                                       Source source,
633                                       Semantics semantics,
634                                       Target target,
635                                       const std::string& entryPointName="",
636                                       const unsigned int remapOptions = spv::spirvbin_t::NONE)
637     {
638         const std::string inputFname = testDir + "/" + testName;
639         const std::string expectedOutputFname =
640             testDir + "/baseResults/" + testName + ".out";
641         std::string input, expectedOutput;
642 
643         tryLoadFile(inputFname, "input", &input);
644         tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
645 
646         const EShMessages controls = DeriveOptions(source, semantics, target);
647         GlslangResult result = compileLinkRemap(testName, input, entryPointName, controls, remapOptions);
648 
649         // Generate the hybrid output in the way of glslang.
650         std::ostringstream stream;
651         outputResultToStream(&stream, result, controls);
652 
653         checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
654                                     expectedOutputFname, result.spirvWarningsErrors);
655     }
656 
657     void loadFileRemapAndCheck(const std::string& testDir,
658                                const std::string& testName,
659                                Source source,
660                                Semantics semantics,
661                                Target target,
662                                const unsigned int remapOptions = spv::spirvbin_t::NONE)
663     {
664         const std::string inputFname = testDir + "/" + testName;
665         const std::string expectedOutputFname =
666             testDir + "/baseResults/" + testName + ".out";
667         std::vector<std::uint32_t> input;
668         std::string expectedOutput;
669 
670         tryLoadSpvFile(inputFname, "input", input);
671         tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
672 
673         const EShMessages controls = DeriveOptions(source, semantics, target);
674         GlslangResult result = remap(testName, input, controls, remapOptions);
675 
676         // Generate the hybrid output in the way of glslang.
677         std::ostringstream stream;
678         outputResultToStream(&stream, result, controls);
679 
680         checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
681                                     expectedOutputFname, result.spirvWarningsErrors);
682     }
683 
684     // Preprocesses the given |source| code. On success, returns true, the
685     // preprocessed shader, and warning messages. Otherwise, returns false, an
686     // empty string, and error messages.
preprocess(const std::string & source)687     std::tuple<bool, std::string, std::string> preprocess(
688         const std::string& source)
689     {
690         const char* shaderStrings = source.data();
691         const int shaderLengths = static_cast<int>(source.size());
692 
693         glslang::TShader shader(EShLangVertex);
694         shader.setStringsWithLengths(&shaderStrings, &shaderLengths, 1);
695         std::string ppShader;
696         glslang::TShader::ForbidIncluder includer;
697         const bool success = shader.preprocess(
698             GetDefaultResources(), defaultVersion, defaultProfile, forceVersionProfile, isForwardCompatible,
699             (EShMessages)(EShMsgOnlyPreprocessor | EShMsgCascadingErrors),
700             &ppShader, includer);
701 
702         std::string log = shader.getInfoLog();
703         log += shader.getInfoDebugLog();
704         if (success) {
705             return std::make_tuple(true, ppShader, log);
706         } else {
707             return std::make_tuple(false, "", log);
708         }
709     }
710 
loadFilePreprocessAndCheck(const std::string & testDir,const std::string & testName)711     void loadFilePreprocessAndCheck(const std::string& testDir,
712                                     const std::string& testName)
713     {
714         const std::string inputFname = testDir + "/" + testName;
715         const std::string expectedOutputFname =
716             testDir + "/baseResults/" + testName + ".out";
717         const std::string expectedErrorFname =
718             testDir + "/baseResults/" + testName + ".err";
719         std::string input, expectedOutput, expectedError;
720 
721         tryLoadFile(inputFname, "input", &input);
722         tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
723         tryLoadFile(expectedErrorFname, "expected error", &expectedError);
724 
725         bool ppOk;
726         std::string output, error;
727         std::tie(ppOk, output, error) = preprocess(input);
728         if (!output.empty()) output += '\n';
729         if (!error.empty()) error += '\n';
730 
731         checkEqAndUpdateIfRequested(expectedOutput, output,
732                                     expectedOutputFname);
733         checkEqAndUpdateIfRequested(expectedError, error,
734                                     expectedErrorFname);
735     }
736 
737     void loadCompileUpgradeTextureToSampledTextureAndDropSamplersAndCheck(const std::string& testDir,
738                                                                           const std::string& testName,
739                                                                           Source source,
740                                                                           Semantics semantics,
741                                                                           Target target,
742                                                                           const std::string& entryPointName = "")
743     {
744         const std::string inputFname = testDir + "/" + testName;
745         const std::string expectedOutputFname = testDir + "/baseResults/" + testName + ".out";
746         std::string input, expectedOutput;
747 
748         tryLoadFile(inputFname, "input", &input);
749         tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
750 
751         const EShMessages controls = DeriveOptions(source, semantics, target);
752         GlslangResult result = compileAndLink(testName, input, entryPointName, controls,
753                                               glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, false,
754                                               EShTexSampTransUpgradeTextureRemoveSampler);
755 
756         // Generate the hybrid output in the way of glslang.
757         std::ostringstream stream;
758         outputResultToStream(&stream, result, controls);
759 
760         checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
761                                     expectedOutputFname, result.spirvWarningsErrors);
762     }
763 
options()764     glslang::SpvOptions& options() { return spirvOptions; }
765 
766 private:
767     const int defaultVersion;
768     const EProfile defaultProfile;
769     const bool forceVersionProfile;
770     const bool isForwardCompatible;
771     glslang::SpvOptions spirvOptions;
772 };
773 
774 }  // namespace glslangtest
775 
776 #endif  // GLSLANG_GTESTS_TEST_FIXTURE_H
777