test(agc): cover terminate invocation specialization, stage boundaries and disabled feature

This commit is contained in:
abnergtz authored and Adrià Franch committed 2026-10-16 13:10:28 +02:00
1 parent 0e8ce286e1
commit 0fe8527a0a
2 files changed
+58

No files matched your search

@@ -25,6 +25,7 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/GuestHeap.hpp"
#include "ImageTableAbi.hpp"
#include "SpirvBackend/SpirvSpecialization.hpp"
#include <spirv/unified1/spirv.hpp>
#include <algorithm>
#include <array>
@@ -5708,6 +5709,41 @@ void orderedPixelShaderTests() {
if ((terminated[at] & 0xffffu) == spv::OpExtension && std::string_view(reinterpret_cast<const char*>(&terminated[at + 1])) == "SPV_KHR_terminate_invocation") ++terminateDeclarations;
}
Require(terminateDeclarations == 1u, "the module declared SPV_KHR_terminate_invocation " + std::to_string(terminateDeclarations) + " times");
const auto countOps = [](std::span<const std::uint32_t> words) {
std::map<spv::Op, std::size_t> counts;
for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) ++counts[static_cast<spv::Op>(words[at] & 0xffffu)];
return counts;
};
const std::array<std::uint32_t, 4> bdaCapabilities{spv::CapabilityShader, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess};
auto unordered = orderedPixelRequest(0x20600u, bdaCapabilities, OrderedBdaPixelCode);
unordered.target.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version;
unordered.target.supportedExtensions = terminateExtensions;
const auto unorderedOps = countOps(ShaderRecompiler::Recompile(unordered).spirv.Words());
Require(!unorderedOps.contains(spv::OpBeginInvocationInterlockEXT), "a pixel shader without PRIMITIVE_ORDERED_PIXEL_SHADER began the interlock");
Require(unorderedOps.at(spv::OpTerminateInvocation) == 5u && unorderedOps.at(spv::OpKill) == 1u, "a pixel shader with BDA reads and SPV_KHR_terminate_invocation did not terminate on a fault");
unordered.target.supportedExtensions = bdaExtensions;
const auto unorderedKills = countOps(ShaderRecompiler::Recompile(unordered).spirv.Words());
Require(!unorderedKills.contains(spv::OpTerminateInvocation) && unorderedKills.at(spv::OpKill) == 6u, "a pixel shader with BDA reads and no SPV_KHR_terminate_invocation did not kill on a fault");
constexpr std::array<std::uint32_t, 12> vertexBdaCode{0x7e040f00u, 0x7e060280u, 0xdc208001u, 0x047d0002u, 0xdc308008u, 0x057d0002u, 0xbf8c3f70u, 0x4a080b04u, 0x7e0e0d04u, 0xf80008cfu, 0x07070707u, 0xbf810000u};
const auto vertexWords = [&](std::span<const std::string_view> extensions) {
ShaderRecompiler::RecompileRequest request{};
request.shader = {ShaderRecompiler::ShaderStage::Vertex, 0x10000u, vertexBdaCode, 0, {}};
request.context.waveSize = 64;
request.context.vertex = ShaderRecompiler::ShaderVertexStageInfo{};
request.target.vulkanVersion = 0x00401000u;
request.target.spirvVersion = 0x00010300u;
request.target.subgroupSize = 64;
request.target.supportedCapabilities = bdaCapabilities;
request.target.supportedExtensions = extensions;
request.target.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version;
request.layout.pushConstantSizeBytes = 128;
request.useCache = false;
return ShaderRecompiler::Recompile(request).spirv.Words();
};
const auto vertexStops = vertexWords(bdaExtensions);
const auto vertexTerminate = vertexWords(terminateExtensions);
const auto vertexOps = countOps(vertexTerminate);
Require(vertexStops == vertexTerminate && !vertexOps.contains(spv::OpTerminateInvocation) && !vertexOps.contains(spv::OpKill) && vertexOps.at(spv::OpReturn) > 1u, "a vertex shader with BDA reads changed its fault exit when SPV_KHR_terminate_invocation was advertised");
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(orderedPixelRequest(0x30600u, plain))); }, "fragmentShaderPixelInterlock");
}
@@ -6717,6 +6753,17 @@ void validationTests() {
vertex.spirv = makeModule({.terminateInvocation = true, .omitTerminateExtension = true});
pixel.spirv = makeModule({.fragment = true});
expectFailure([&] { validateTerminate(true); }, "OpTerminateInvocation requires enabled shaderTerminateInvocation in a fragment shader");
vertex.spirv = makeModule({});
pixel.spirv = ShaderRecompiler::SpecializeSpirv(makeModule({.fragment = true, .terminateInvocation = true}));
std::size_t terminates = 0;
std::size_t declarations = 0;
for (std::size_t at = 5; at < pixel.spirv.size(); at += pixel.spirv[at] >> 16u) {
const auto op = pixel.spirv[at] & 0xffffu;
if (op == spv::OpTerminateInvocation) ++terminates;
if (op == spv::OpExtension && std::string_view(reinterpret_cast<const char*>(&pixel.spirv[at + 1])) == "SPV_KHR_terminate_invocation") ++declarations;
}
Require(terminates == 1u && declarations == 1u, "specialization dropped OpTerminateInvocation or its extension declaration");
validateTerminate(true);
}
{
ShaderRecompiler::RecompileResult vertex;
@@ -106,6 +106,17 @@ void CheckProfile() {
enabled.indexing.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
const AgcDriver::ShaderDeviceProfile accepted(enabled.Target(), enabled.device, enabled.limits);
Require(accepted.Target().supportedCapabilities.size() == 6u, "enabled descriptor indexing was rejected");
ProfileInput terminating;
VkPhysicalDeviceShaderTerminateInvocationFeaturesKHR terminate{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TERMINATE_INVOCATION_FEATURES_KHR, &terminating.bda, VK_TRUE};
terminating.device.pNext = &terminate;
terminating.extensions.push_back("SPV_KHR_terminate_invocation");
const AgcDriver::ShaderDeviceProfile terminated(terminating.Target(), terminating.device, terminating.limits);
Require(std::ranges::find(terminated.Target().supportedExtensions, "SPV_KHR_terminate_invocation") != terminated.Target().supportedExtensions.end(), "enabled shaderTerminateInvocation was rejected");
ProfileInput disabled;
VkPhysicalDeviceShaderTerminateInvocationFeaturesKHR disabledTerminate{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TERMINATE_INVOCATION_FEATURES_KHR, &disabled.bda, VK_FALSE};
disabled.device.pNext = &disabledTerminate;
disabled.extensions.push_back("SPV_KHR_terminate_invocation");
Reject([&] { AgcDriver::ShaderDeviceProfile rejected(disabled.Target(), disabled.device, disabled.limits); }, "shaderTerminateInvocation");
}
void CheckAbi() {