blob: 81a6edd84bc04c75eed03c4c3141a1cbd6746a7b [file] [edit]
// Copyright (C) 2026 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 The Khronos Group 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 "TestFixture.h"
#include "glslang/Public/ShaderLang.h"
#include "SPIRV/GlslangToSpv.h"
#include "SPIRV/disassemble.h"
#include <gtest/gtest.h>
#include <sstream>
#include <string>
#include <vector>
namespace glslangtest {
class DiscardTest : public ::testing::Test {
protected:
struct CompileResult {
bool success;
std::string spirvText;
std::string infoLog;
};
CompileResult compileGlsl(const std::string& source, glslang::EShTargetLanguageVersion spvVersion,
bool discardIsTerminate)
{
const char* const src = source.c_str();
glslang::TShader shader(EShLangFragment);
shader.setStrings(&src, 1);
shader.setEnvInput(glslang::EShSourceGlsl, EShLangFragment, glslang::EShClientVulkan, 100);
shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_4);
shader.setEnvTarget(glslang::EShTargetSpv, spvVersion);
shader.setAutoMapBindings(true);
shader.setAutoMapLocations(true);
shader.setDiscardIsTerminate(discardIsTerminate);
const EShMessages msgs = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);
if (!shader.parse(GetDefaultResources(), 450, false, msgs)) {
return {false, "", shader.getInfoLog()};
}
glslang::TProgram program;
program.addShader(&shader);
if (!program.link(msgs)) {
return {false, "", program.getInfoLog()};
}
std::vector<uint32_t> spirv;
glslang::GlslangToSpv(*program.getIntermediate(EShLangFragment), spirv);
std::ostringstream dis;
spv::Disassemble(dis, spirv);
return {true, dis.str(), ""};
}
// Helper function to check if the given SPIR-V string contains a specific pattern.
static bool containsPattern(const std::string& spirvText, const std::string& pattern)
{
return spirvText.find(pattern) != std::string::npos;
}
};
static const char kSimpleFrag[] = R"glsl(
#version 450
uniform sampler2D tex;
in vec2 texCoord;
out vec4 outColor;
void main() {
outColor = texture(tex, texCoord);
if (outColor.a == 0.0)
discard;
}
)glsl";
TEST_F(DiscardTest, DiscardMappedToOpKill)
{
const bool discardIsTerminate = false;
auto const r = compileGlsl(kSimpleFrag, glslang::EShTargetSpv_1_4, discardIsTerminate);
ASSERT_TRUE(r.success) << r.infoLog;
EXPECT_TRUE(containsPattern(r.spirvText, "Kill"))
<< "Expected OpKill in SPIR-V when SPIR-V version < 1.6 is used.\n"
<< r.spirvText;
EXPECT_FALSE(containsPattern(r.spirvText, "DemoteToHelperInvocation"))
<< "Did not expect OpDemoteToHelperInvocation in SPIR-V when SPIR-V version < 1.6 is used.\n"
<< r.spirvText;
EXPECT_FALSE(containsPattern(r.spirvText, "TerminateInvocation"))
<< "Did not expect OpTerminateInvocation in SPIR-V when SPIR-V version < 1.6 is used.\n"
<< r.spirvText;
}
TEST_F(DiscardTest, DiscardMappedToOpDemoteToHelperInvocation)
{
const bool discardIsTerminate = false;
auto const r = compileGlsl(kSimpleFrag, glslang::EShTargetSpv_1_6, discardIsTerminate);
ASSERT_TRUE(r.success) << r.infoLog;
EXPECT_FALSE(containsPattern(r.spirvText, "Kill"))
<< "Did not expect OpKill in SPIR-V when SPIR-V version >= 1.6 is used.\n"
<< r.spirvText;
EXPECT_TRUE(containsPattern(r.spirvText, "DemoteToHelperInvocation"))
<< "Expected OpDemoteToHelperInvocation in SPIR-V when setDiscardIsTerminate(false) is used.\n"
<< r.spirvText;
EXPECT_FALSE(containsPattern(r.spirvText, "TerminateInvocation"))
<< "Did not expect OpTerminateInvocation in SPIR-V when setDiscardIsTerminate(false) is used.\n"
<< r.spirvText;
}
TEST_F(DiscardTest, DiscardMappedToOpTerminateInvocation)
{
const bool discardIsTerminate = true;
auto const r = compileGlsl(kSimpleFrag, glslang::EShTargetSpv_1_6, discardIsTerminate);
ASSERT_TRUE(r.success) << r.infoLog;
EXPECT_FALSE(containsPattern(r.spirvText, "Kill"))
<< "Did not expect OpKill in SPIR-V when SPIR-V version >= 1.6 is used.\n"
<< r.spirvText;
EXPECT_FALSE(containsPattern(r.spirvText, "DemoteToHelperInvocation"))
<< "Did not expect OpDemoteToHelperInvocation in SPIR-V when setDiscardIsTerminate(true) is used.\n"
<< r.spirvText;
EXPECT_TRUE(containsPattern(r.spirvText, "TerminateInvocation"))
<< "Expected OpTerminateInvocation in SPIR-V when setDiscardIsTerminate(true) is used.\n"
<< r.spirvText;
}
} // namespace glslangtest