mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 00:53:02 -05:00
fix(agc): require demote for masked subgroup reads
ReadFirstLane, Ballot and PermuteU32 can consume uncovered helper lanes after a fragment explicitly enables EXEC. Classify those operations as neighbour reads so a partial quad mask refuses their kill fallback when demote is unavailable. ReadFirstLane also stays protected when a constant whole-quad instruction folds away. The CPU regression covers explicit EXEC, constant WQM, a comparison ballot and ds_permute. Unmasked programs still compile, supported masked programs retain demote and their subgroup operation, and constant colour keeps the kill fallback. The recovered source fails the readfirstlane and ballot checks; the corrected source passes both the focused check and shader-memory suite. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
1 parent
1ffc5f672b
commit
dbbe282c14
3 files changed
+67
No files matched your search
@@ -1446,6 +1446,11 @@ if(BUILD_TESTING)
|
||||
add_test(NAME agc_shader_srt_read_patching COMMAND agc_shader_memory_tests --srt-read-patching)
|
||||
set_tests_properties(agc_shader_srt_read_patching PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
add_test(NAME agc_shader_frontend_pair COMMAND agc_shader_memory_tests --frontend-pair)
|
||||
add_test(NAME agc_shader_quad_mask_no_demote COMMAND agc_shader_memory_tests --quad-mask-no-demote)
|
||||
set_tests_properties(agc_shader_quad_mask_no_demote PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_quad_mask_no_demote PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_shader_frontend_pair_off COMMAND agc_shader_memory_tests --frontend-pair)
|
||||
set_tests_properties(agc_shader_frontend_pair PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
set_tests_properties(agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_FRONTEND_PAIR=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
|
||||
@@ -4920,6 +4920,59 @@ void verifyGuardedNullPointers() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void verifyQuadMaskNeighbourReads() {
|
||||
using namespace ShaderRecompiler;
|
||||
constexpr std::array<std::uint32_t, 6> capabilities{spv::CapabilityShader, spv::CapabilityInt64, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityGroupNonUniformArithmetic};
|
||||
constexpr std::array<std::uint32_t, 7> demoteCapabilities{spv::CapabilityShader, spv::CapabilityInt64, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityGroupNonUniformArithmetic, spv::CapabilityDemoteToHelperInvocation};
|
||||
constexpr std::array<std::string_view, 1> demoteExtensions{"SPV_EXT_demote_to_helper_invocation"};
|
||||
constexpr std::array<std::uint32_t, 4> constant{0x7e0202f2u, 0xf800180fu, 0x01010101u, 0xbf810000u};
|
||||
constexpr std::array<std::uint32_t, 6> explicitExec{0xbefe04c1u, 0x7e000500u, 0x7e020200u, 0xf800180fu, 0x01010101u, 0xbf810000u};
|
||||
constexpr std::array<std::uint32_t, 6> constantWqm{0xbefe0ac1u, 0x7e000500u, 0x7e020200u, 0xf800180fu, 0x01010101u, 0xbf810000u};
|
||||
constexpr std::array<std::uint32_t, 7> permute{0xbefe04c1u, 0x7e040284u, 0xdac80000u, 0x01000002u, 0xf800180fu, 0x01010101u, 0xbf810000u};
|
||||
constexpr std::array<std::uint32_t, 9> comparisonBallot{0xbe8003ffu, 0x3f000000u, 0xbefe04c1u, 0x7d840000u, 0xbe80046au, 0x7e020200u, 0xf800180fu, 0x01010101u, 0xbf810000u};
|
||||
const auto compile = [&](std::span<const std::uint32_t> code, bool masked, bool demote) {
|
||||
RecompileRequest request{};
|
||||
request.shader = {ShaderStage::Fragment, 0x58000u, code, 0, {}};
|
||||
request.context.waveSize = 32u;
|
||||
request.context.pixel = ShaderPixelStageInfo{};
|
||||
request.context.pixel->inputAddr = PixelInputBit(PixelInput::PositionX);
|
||||
request.context.pixel->posX = true;
|
||||
request.context.pixel->quadPixelMask = masked ? 0x2u : 0xfu;
|
||||
request.context.pixel->targetOutputMode[0] = 9u;
|
||||
request.context.pixel->targetExportMapping.fill(0xe4u);
|
||||
request.target.vulkanVersion = 0x00401000u;
|
||||
request.target.spirvVersion = 0x00010300u;
|
||||
request.target.subgroupSize = 32u;
|
||||
request.target.supportedCapabilities = demote ? std::span<const std::uint32_t>(demoteCapabilities) : std::span<const std::uint32_t>(capabilities);
|
||||
if (demote) request.target.supportedExtensions = demoteExtensions;
|
||||
request.layout.pushConstantSizeBytes = 128u;
|
||||
request.useCache = false;
|
||||
return Recompile(request);
|
||||
};
|
||||
const auto declares = [](const RecompileResult& result, spv::Op opcode) {
|
||||
const auto& words = result.spirv.Words();
|
||||
for (std::size_t cursor = 5; cursor < words.size();) {
|
||||
const auto count = words[cursor] >> 16u;
|
||||
require(count != 0 && count <= words.size() - cursor, "quad mask: truncated SPIR-V instruction");
|
||||
if ((words[cursor] & 0xffffu) == static_cast<std::uint32_t>(opcode)) return true;
|
||||
cursor += count;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
require(declares(compile(constant, true, false), spv::OpKill), "a quad mask without neighbour reads did not retain its kill fallback");
|
||||
for (const auto code : {std::span<const std::uint32_t>(explicitExec), std::span<const std::uint32_t>(constantWqm), std::span<const std::uint32_t>(permute)}) {
|
||||
const auto unmasked = compile(code, false, false);
|
||||
require(declares(unmasked, spv::OpGroupNonUniformShuffle), "the neighbour-read test did not retain its cross-lane shuffle");
|
||||
expectFailure([&] { static_cast<void>(compile(code, true, false)); }, "shaderDemoteToHelperInvocation", "a quad mask killed helper lanes before a cross-lane read with EXEC enabled for those lanes");
|
||||
const auto masked = compile(code, true, true);
|
||||
require(declares(masked, spv::OpDemoteToHelperInvocation) && declares(masked, spv::OpGroupNonUniformShuffle), "a quad mask with demote did not preserve its cross-lane shuffle path");
|
||||
}
|
||||
require(declares(compile(comparisonBallot, false, false), spv::OpGroupNonUniformBallot), "the comparison test did not retain its subgroup ballot");
|
||||
expectFailure([&] { static_cast<void>(compile(comparisonBallot, true, false)); }, "shaderDemoteToHelperInvocation", "a quad mask killed helper lanes before a ballot with EXEC enabled for those lanes");
|
||||
const auto maskedBallot = compile(comparisonBallot, true, true);
|
||||
require(declares(maskedBallot, spv::OpDemoteToHelperInvocation) && declares(maskedBallot, spv::OpGroupNonUniformBallot), "a quad mask with demote did not preserve its ballot path");
|
||||
}
|
||||
|
||||
void verifyCubeDepthCompareMode() {
|
||||
using namespace ShaderRecompiler;
|
||||
ImageResource image{};
|
||||
@@ -4975,6 +5028,11 @@ void verifySrgbDepthCompareModes() {
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
using namespace ShaderRecompiler;
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--quad-mask-no-demote") {
|
||||
verifyQuadMaskNeighbourReads();
|
||||
std::cout << "Quad masks preserve neighbour-lane helpers or refuse without demote\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--frontend-pair") {
|
||||
verifyFrontendPair();
|
||||
std::cout << "Prepared frontend reuse preserves full and partial-thread programs\n";
|
||||
@@ -5058,6 +5116,7 @@ int main(int argc, char** argv) {
|
||||
verifyCubeDepthCompareMode();
|
||||
verifyVolumeArrayLoadMode();
|
||||
verifySrgbDepthCompareModes();
|
||||
verifyQuadMaskNeighbourReads();
|
||||
verifyCoherentGds();
|
||||
#if ANYPS5_ENABLE_SPIRV_TOOLS
|
||||
const std::vector<std::uint32_t> minimalSpirv{
|
||||
|
||||
@@ -17,8 +17,11 @@ bool ReadsNeighbourLanes(const IrProgram& program, const IrValue& inst) {
|
||||
case IrOpcode::DppUpdateU32:
|
||||
case IrOpcode::SwizzleU32:
|
||||
case IrOpcode::BpermuteU32:
|
||||
case IrOpcode::PermuteU32:
|
||||
case IrOpcode::Permlane16U32:
|
||||
case IrOpcode::ReadLane:
|
||||
case IrOpcode::ReadFirstLane:
|
||||
case IrOpcode::Ballot:
|
||||
case IrOpcode::WqmU64:
|
||||
return true;
|
||||
case IrOpcode::ImageSampleImplicitLod:
|
||||
|
||||
Reference in new issue
Block a user