| // |
| // 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 IoMapData { |
| std::vector<std::string> fileNames; |
| Semantics semantics; |
| }; |
| |
| using GlslMapIOTest = GlslangTest <::testing::TestWithParam<IoMapData>>; |
| |
| 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; |
| } |
| else { |
| if (!in.getType()->isStruct()) { |
| bool found = false; |
| for (auto outIt : pipeOut) { |
| if (outIt.second->getType()->isStruct()) { |
| unsigned int baseLoc = outIt.second->getType()->getQualifier().hasLocation() ? |
| outIt.second->getType()->getQualifier().layoutLocation : |
| std::numeric_limits<unsigned int>::max(); |
| for (size_t j = 0; j < outIt.second->getType()->getStruct()->size(); j++) { |
| baseLoc = (*outIt.second->getType()->getStruct())[j].type->getQualifier().hasLocation() ? |
| (*outIt.second->getType()->getStruct())[j].type->getQualifier().layoutLocation : baseLoc; |
| if (baseLoc != std::numeric_limits<unsigned int>::max()) { |
| if (baseLoc == in.getType()->getQualifier().layoutLocation) { |
| found = true; |
| break; |
| } |
| baseLoc += glslang::TIntermediate::computeTypeLocationSize(*(*outIt.second->getType()->getStruct())[j].type, EShLangVertex); |
| } |
| } |
| if (found) { |
| break; |
| } |
| } |
| } |
| success &= found; |
| } |
| else { |
| unsigned int baseLoc = in.getType()->getQualifier().hasLocation() ? in.getType()->getQualifier().layoutLocation : -1; |
| for (size_t j = 0; j < in.getType()->getStruct()->size(); j++) { |
| baseLoc = (*in.getType()->getStruct())[j].type->getQualifier().hasLocation() ? |
| (*in.getType()->getStruct())[j].type->getQualifier().layoutLocation : baseLoc; |
| if (baseLoc != std::numeric_limits<unsigned int>::max()) { |
| bool isMemberFound = false; |
| for (auto outIt : pipeOut) { |
| if (baseLoc == outIt.second->getType()->getQualifier().layoutLocation) { |
| isMemberFound = true; |
| break; |
| } |
| } |
| if (!isMemberFound) { |
| success &= false; |
| break; |
| } |
| baseLoc += glslang::TIntermediate::computeTypeLocationSize(*(*in.getType()->getStruct())[j].type, EShLangVertex); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // 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(GlslMapIOTest, FromFile) |
| { |
| const auto& fileNames = GetParam().fileNames; |
| Semantics semantics = GetParam().semantics; |
| 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); |
| |
| if (controls & EShMsgSpvRules) { |
| if (controls & EShMsgVulkanRules) { |
| shader->setEnvInput((controls & EShMsgReadHlsl) ? glslang::EShSourceHlsl |
| : glslang::EShSourceGlsl, |
| shader->getStage(), glslang::EShClientVulkan, 100); |
| shader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); |
| shader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0); |
| } else { |
| shader->setEnvInput((controls & EShMsgReadHlsl) ? glslang::EShSourceHlsl |
| : glslang::EShSourceGlsl, |
| shader->getStage(), glslang::EShClientOpenGL, 100); |
| shader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450); |
| shader->setEnvTarget(glslang::EshTargetSpv, glslang::EShTargetSpv_1_0); |
| } |
| } |
| |
| 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(); |
| |
| unsigned int stage = 0; |
| glslang::TIntermediate* firstIntermediate = nullptr; |
| while (!program.getIntermediate((EShLanguage)stage) && stage < EShLangCount) { stage++; } |
| firstIntermediate = program.getIntermediate((EShLanguage)stage); |
| |
| glslang::TDefaultGlslIoResolver resolver(*firstIntermediate); |
| glslang::TGlslIoMapper ioMapper; |
| |
| if (success) { |
| success &= program.mapIO(&resolver, &ioMapper); |
| result.linkingOutput = program.getInfoLog(); |
| result.linkingError = program.getInfoDebugLog(); |
| } |
| |
| 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::Parameterize(); |
| 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, GlslMapIOTest, |
| ::testing::ValuesIn(std::vector<IoMapData>({ |
| {{"iomap.crossStage.vert", "iomap.crossStage.frag" }, Semantics::OpenGL}, |
| {{"iomap.crossStage.2.vert", "iomap.crossStage.2.geom", "iomap.crossStage.2.frag" }, Semantics::OpenGL}, |
| {{"iomap.blockOutVariableIn.vert", "iomap.blockOutVariableIn.frag"}, Semantics::OpenGL}, |
| {{"iomap.variableOutBlockIn.vert", "iomap.variableOutBlockIn.frag"}, Semantics::OpenGL}, |
| {{"iomap.blockOutVariableIn.2.vert", "iomap.blockOutVariableIn.geom"}, Semantics::OpenGL}, |
| {{"iomap.variableOutBlockIn.2.vert", "iomap.variableOutBlockIn.geom"}, Semantics::OpenGL}, |
| // vulkan semantics |
| {{"iomap.crossStage.vk.vert", "iomap.crossStage.vk.geom", "iomap.crossStage.vk.frag" }, Semantics::Vulkan}, |
| })) |
| ); |
| // clang-format on |
| |
| } // anonymous namespace |
| } // namespace glslangtest |