| // | |
| // Copyright (C) 2016-2017 Google, Inc. | |
| // Copyright (C) 2020 The Khronos Group 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 3Dlabs Inc. Ltd. 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 <algorithm> | |
| #include <gtest/gtest.h> | |
| #include "TestFixture.h" | |
| #include "glslang/MachineIndependent/iomapper.h" | |
| #include "glslang/MachineIndependent/reflection.h" | |
| namespace glslangtest { | |
| namespace { | |
| struct vkRelaxedData { | |
| std::vector<std::string> fileNames; | |
| std::vector<std::vector<std::string>> resourceSetBindings; | |
| }; | |
| using VulkanRelaxedTest = GlslangTest <::testing::TestWithParam<vkRelaxedData>>; | |
| template<class T> | |
| std::string interfaceName(T symbol) { | |
| return symbol.getType()->getBasicType() == glslang::EbtBlock ? std::string(symbol.getType()->getTypeName().c_str()) : symbol.name; | |
| } | |
| bool verifyIOMapping(std::string& linkingError, glslang::TProgram& program) { | |
| bool success = true; | |
| // Verify IO Mapping by generating reflection for each stage individually | |
| // and comparing layout qualifiers on the results | |
| int reflectionOptions = EShReflectionDefault; | |
| //reflectionOptions |= EShReflectionStrictArraySuffix; | |
| //reflectionOptions |= EShReflectionBasicArraySuffix; | |
| reflectionOptions |= EShReflectionIntermediateIO; | |
| reflectionOptions |= EShReflectionSeparateBuffers; | |
| reflectionOptions |= EShReflectionAllBlockVariables; | |
| //reflectionOptions |= EShReflectionUnwrapIOBlocks; | |
| success &= program.buildReflection(reflectionOptions); | |
| // check that the reflection output from the individual stages all makes sense.. | |
| std::vector<glslang::TReflection> stageReflections; | |
| for (int s = 0; s < EShLangCount; ++s) { | |
| if (program.getIntermediate((EShLanguage)s)) { | |
| stageReflections.emplace_back((EShReflectionOptions)reflectionOptions, (EShLanguage)s, (EShLanguage)s); | |
| success &= stageReflections.back().addStage((EShLanguage)s, *program.getIntermediate((EShLanguage)s)); | |
| } | |
| } | |
| // check that input/output locations match between stages | |
| auto it = stageReflections.begin(); | |
| auto nextIt = it + 1; | |
| for (; nextIt != stageReflections.end(); it++, nextIt++) { | |
| int numOut = it->getNumPipeOutputs(); | |
| std::map<std::string, const glslang::TObjectReflection*> pipeOut; | |
| for (int i = 0; i < numOut; i++) { | |
| const glslang::TObjectReflection& out = it->getPipeOutput(i); | |
| std::string name = interfaceName(out); | |
| pipeOut[name] = &out; | |
| } | |
| int numIn = nextIt->getNumPipeInputs(); | |
| for (int i = 0; i < numIn; i++) { | |
| auto in = nextIt->getPipeInput(i); | |
| std::string name = interfaceName(in); | |
| auto out = pipeOut.find(name); | |
| if (out != pipeOut.end()) { | |
| auto inQualifier = in.getType()->getQualifier(); | |
| auto outQualifier = out->second->getType()->getQualifier(); | |
| success &= outQualifier.layoutLocation == inQualifier.layoutLocation; | |
| // These are not part of a matched interface. Other cases still need to be added. | |
| } else if (name != "gl_FrontFacing" && name != "gl_FragCoord") { | |
| success &= false; | |
| } | |
| } | |
| } | |
| // compare uniforms in each stage to the program | |
| { | |
| int totalUniforms = program.getNumUniformVariables(); | |
| std::map<std::string, const glslang::TObjectReflection*> programUniforms; | |
| for (int i = 0; i < totalUniforms; i++) { | |
| const glslang::TObjectReflection& uniform = program.getUniform(i); | |
| std::string name = interfaceName(uniform); | |
| programUniforms[name] = &uniform; | |
| } | |
| it = stageReflections.begin(); | |
| for (; it != stageReflections.end(); it++) { | |
| int numUniform = it->getNumUniforms(); | |
| std::map<std::string, glslang::TObjectReflection> uniforms; | |
| for (int i = 0; i < numUniform; i++) { | |
| glslang::TObjectReflection uniform = it->getUniform(i); | |
| std::string name = interfaceName(uniform); | |
| auto programUniform = programUniforms.find(name); | |
| if (programUniform != programUniforms.end()) { | |
| auto stageQualifier = uniform.getType()->getQualifier(); | |
| auto programQualifier = programUniform->second->getType()->getQualifier(); | |
| success &= stageQualifier.layoutLocation == programQualifier.layoutLocation; | |
| success &= stageQualifier.layoutBinding == programQualifier.layoutBinding; | |
| success &= stageQualifier.layoutSet == programQualifier.layoutSet; | |
| } | |
| else { | |
| success &= false; | |
| } | |
| } | |
| } | |
| } | |
| // compare uniform blocks in each stage to the program table | |
| { | |
| int totalUniforms = program.getNumUniformBlocks(); | |
| std::map<std::string, const glslang::TObjectReflection*> programUniforms; | |
| for (int i = 0; i < totalUniforms; i++) { | |
| const glslang::TObjectReflection& uniform = program.getUniformBlock(i); | |
| std::string name = interfaceName(uniform); | |
| programUniforms[name] = &uniform; | |
| } | |
| it = stageReflections.begin(); | |
| for (; it != stageReflections.end(); it++) { | |
| int numUniform = it->getNumUniformBlocks(); | |
| std::map<std::string, glslang::TObjectReflection> uniforms; | |
| for (int i = 0; i < numUniform; i++) { | |
| glslang::TObjectReflection uniform = it->getUniformBlock(i); | |
| std::string name = interfaceName(uniform); | |
| auto programUniform = programUniforms.find(name); | |
| if (programUniform != programUniforms.end()) { | |
| auto stageQualifier = uniform.getType()->getQualifier(); | |
| auto programQualifier = programUniform->second->getType()->getQualifier(); | |
| success &= stageQualifier.layoutLocation == programQualifier.layoutLocation; | |
| success &= stageQualifier.layoutBinding == programQualifier.layoutBinding; | |
| success &= stageQualifier.layoutSet == programQualifier.layoutSet; | |
| } | |
| else { | |
| success &= false; | |
| } | |
| } | |
| } | |
| } | |
| if (!success) { | |
| linkingError += "Mismatched cross-stage IO\n"; | |
| } | |
| return success; | |
| } | |
| TEST_P(VulkanRelaxedTest, FromFile) | |
| { | |
| const auto& fileNames = GetParam().fileNames; | |
| const auto& resourceSetBindings = GetParam().resourceSetBindings; | |
| Semantics semantics = Semantics::Vulkan; | |
| const size_t fileCount = fileNames.size(); | |
| const EShMessages controls = DeriveOptions(Source::GLSL, semantics, Target::BothASTAndSpv); | |
| GlslangResult result; | |
| // Compile each input shader file. | |
| bool success = true; | |
| std::vector<std::unique_ptr<glslang::TShader>> shaders; | |
| for (size_t i = 0; i < fileCount; ++i) { | |
| std::string contents; | |
| tryLoadFile(GlobalTestSettings.testRoot + "/" + fileNames[i], | |
| "input", &contents); | |
| shaders.emplace_back( | |
| new glslang::TShader(GetShaderStage(GetSuffix(fileNames[i])))); | |
| auto* shader = shaders.back().get(); | |
| shader->setAutoMapLocations(true); | |
| shader->setAutoMapBindings(true); | |
| shader->setEnvInput(glslang::EShSourceGlsl, shader->getStage(), glslang::EShClientVulkan, 100); | |
| shader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); | |
| shader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0); | |
| // Use vulkan relaxed option | |
| shader->setEnvInputVulkanRulesRelaxed(); | |
| success &= compile(shader, contents, "", controls); | |
| result.shaderResults.push_back( | |
| { fileNames[i], shader->getInfoLog(), shader->getInfoDebugLog() }); | |
| } | |
| // Link all of them. | |
| glslang::TProgram program; | |
| for (const auto& shader : shaders) program.addShader(shader.get()); | |
| success &= program.link(controls); | |
| result.linkingOutput = program.getInfoLog(); | |
| result.linkingError = program.getInfoDebugLog(); | |
| if (!resourceSetBindings.empty()) { | |
| assert(resourceSetBindings.size() == fileNames.size()); | |
| for (size_t i = 0; i < shaders.size(); i++) | |
| shaders[i]->setResourceSetBinding(resourceSetBindings[i]); | |
| } | |
| glslang::TIoMapResolver *resolver; | |
| for (unsigned stage = 0; stage < EShLangCount; stage++) { | |
| resolver = program.getGlslIoResolver((EShLanguage)stage); | |
| if (resolver) | |
| break; | |
| } | |
| glslang::TIoMapper *ioMapper = glslang::GetGlslIoMapper(); | |
| if (success) { | |
| success &= program.mapIO(resolver, ioMapper); | |
| result.linkingOutput = program.getInfoLog(); | |
| result.linkingError = program.getInfoDebugLog(); | |
| } | |
| delete ioMapper; | |
| delete resolver; | |
| success &= verifyIOMapping(result.linkingError, program); | |
| result.validationResult = success; | |
| if (success && (controls & EShMsgSpvRules)) { | |
| for (int stage = 0; stage < EShLangCount; ++stage) { | |
| if (program.getIntermediate((EShLanguage)stage)) { | |
| spv::SpvBuildLogger logger; | |
| std::vector<uint32_t> spirv_binary; | |
| options().disableOptimizer = false; | |
| glslang::GlslangToSpv(*program.getIntermediate((EShLanguage)stage), | |
| spirv_binary, &logger, &options()); | |
| std::ostringstream disassembly_stream; | |
| spv::Disassemble(disassembly_stream, spirv_binary); | |
| result.spirvWarningsErrors += logger.getAllMessages(); | |
| result.spirv += disassembly_stream.str(); | |
| result.validationResult &= !options().validate || logger.getAllMessages().empty(); | |
| } | |
| } | |
| } | |
| std::ostringstream stream; | |
| outputResultToStream(&stream, result, controls); | |
| // Check with expected results. | |
| const std::string expectedOutputFname = | |
| GlobalTestSettings.testRoot + "/baseResults/" + fileNames.front() + ".out"; | |
| std::string expectedOutput; | |
| tryLoadFile(expectedOutputFname, "expected output", &expectedOutput); | |
| checkEqAndUpdateIfRequested(expectedOutput, stream.str(), expectedOutputFname, | |
| result.spirvWarningsErrors); | |
| } | |
| // clang-format off | |
| INSTANTIATE_TEST_SUITE_P( | |
| Glsl, VulkanRelaxedTest, | |
| ::testing::ValuesIn(std::vector<vkRelaxedData>({ | |
| {{"vk.relaxed.frag"}, {}}, | |
| {{"vk.relaxed.link1.frag", "vk.relaxed.link2.frag"}, {}}, | |
| {{"vk.relaxed.stagelink.0.0.vert", "vk.relaxed.stagelink.0.1.vert", "vk.relaxed.stagelink.0.2.vert", "vk.relaxed.stagelink.0.0.frag", "vk.relaxed.stagelink.0.1.frag", "vk.relaxed.stagelink.0.2.frag"}, {}}, | |
| {{"vk.relaxed.stagelink.vert", "vk.relaxed.stagelink.frag"}, {}}, | |
| {{"vk.relaxed.errorcheck.vert", "vk.relaxed.errorcheck.frag"}, {}}, | |
| {{"vk.relaxed.changeSet.vert", "vk.relaxed.changeSet.frag" }, { {"0"}, {"1"} } }, | |
| {{"vk.relaxed.syntaxerror.vert", "vk.relaxed.syntaxerror.frag"}, {}}, | |
| {{"vk.relaxed.buffer_reference.frag"}, {}}, | |
| })) | |
| ); | |
| // clang-format on | |
| } // anonymous namespace | |
| } // namespace glslangtest |