From 9a20c6f7388907185649291f3b5e3f6d2b065f3f Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 25 Oct 2026 01:29:02 +0200 Subject: [PATCH] shader: disable DPP16 writes from EXEC-disabled sources without BOUND_CTRL --- .../backend/spirv/spirvEmitterFlow.cpp | 19 +++++- tests/ShaderRecompilerComputeTests.cpp | 67 ++++++++++++++----- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index ce7824a6d..5bd8a3bb3 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -147,9 +147,24 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl // Only row shifts (0x101-0x10f, 0x111-0x11f) can vacate a lane; other controls always read one. const bool row_shift = flags.control > 0x100u && flags.control < 0x120u && flags.control != 0x110u; - if (!flags.bound_control && row_shift) { + if (flags.bound_control || (flags.fetch_inactive && !row_shift)) { + return writable; + } + // Without BOUND_CTRL, a vacated source or, without FI, an EXEC-disabled one disables the write. + const auto target = EmitDppTargetLane(state, flags); + if (row_shift) { + writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable, target.valid); + } + if (!flags.fetch_inactive) { + // Vacated lanes target outside their row; wrapping keeps the ballot index in range. + auto source = target.lane; + if (row_shift) { + lane = lane != 0 ? lane : EmitSubgroupLocalInvocationId(state); + source = EmitNative( + state, lane, source, ConstantU32(state, 0), ConstantU32(state, 4)); + } writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable, - EmitDppTargetLane(state, flags).valid); + EmitSourceLaneActive(ctx, exec, source)); } return writable; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index dccaa3796..13d56631d 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -28298,25 +28298,56 @@ TestCase VectorDppBankMaskPreservesDestination() { return test; } -TestCase VectorDppBoundsControlZeroPreservesDestination() { +TestCase VectorDppInactiveSource(bool row_shift, bool bound_ctrl, u32 wave_size) { using O = ShaderOpcode; - + constexpr u32 sentinel = 0xaaaaaaaau; + // Each half mixes active, EXEC-disabled and (for row_shr:1) vacated sources. + const u32 masks[] = {0x5f5ff5f5u, 0xf5f55f5fu}; std::vector code; - AppendVMovLiteral(&code, 2, 0xaaaaaaaau); AppendVMovU32(&code, 1, 100); - code.push_back(EncodeVop2(0x25, 2, 250, 1)); - code.push_back(EncodeVop2Dpp(0, 0x111)); - code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 0)); - AppendBufferStoreDword(&code, 2, 3); + code.push_back(EncodeVop2(0x25, 1, Vgpr(0), 1)); + AppendVMovU32(&code, 7, 1000); + code.push_back(EncodeVop2(0x25, 7, Vgpr(0), 7)); + AppendVMovLiteral(&code, 2, sentinel); + AppendSMovLiteral(&code, 126, masks[0]); + if (wave_size == 64) AppendSMovLiteral(&code, 127, masks[1]); + // v_add_nc_u32_dpp v2, v1, v7 with row_shr:1 or quad_perm:[3,2,1,0]; bit 19 is BOUND_CTRL. + code.push_back(EncodeVop2(0x25, 2, 250, 7)); + code.push_back(EncodeVop2Dpp(1, row_shift ? 0x111u : 0x01bu) | + (bound_ctrl ? 1u << 19 : 0u)); + AppendSMovLiteral(&code, 126, 0xffffffffu); + if (wave_size == 64) AppendSMovLiteral(&code, 127, 0xffffffffu); + AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, 0); AppendEnd(&code); TestCase test; - test.name = "VectorDppBoundsControlZeroPreservesDestination"; - test.code = code; - test.expected = {0xaaaaaaaau, 100, 101, 102, 103, 104, 105, 106}; - test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32, - O::BUFFER_STORE_DWORD, O::S_ENDPGM}; - test.compute_info.threads_num[0] = 8; + const char *names[2][2][2] = { + {{"VectorDppQuadPermInactiveSourceKeepsWave32", + "VectorDppQuadPermInactiveSourceKeepsWave64"}, + {"VectorDppQuadPermInactiveSourceZeroWave32", + "VectorDppQuadPermInactiveSourceZeroWave64"}}, + {{"VectorDppRowShrInactiveSourceKeepsWave32", + "VectorDppRowShrInactiveSourceKeepsWave64"}, + {"VectorDppRowShrInactiveSourceZeroWave32", + "VectorDppRowShrInactiveSourceZeroWave64"}}}; + test.name = names[row_shift][bound_ctrl][wave_size == 64]; + test.code = std::move(code); + test.expected.resize(wave_size, sentinel); + for (u32 lane = 0; lane < wave_size; ++lane) { + const auto mask = masks[lane / 32u]; + if ((mask & (1u << (lane % 32u))) == 0) continue; + const bool vacated = row_shift && lane % 16u == 0; + const u32 source = row_shift ? lane - 1u : (lane & ~3u) | (3u - (lane & 3u)); + if (!vacated && (mask & (1u << (source % 32u))) != 0) { + test.expected[lane] = 1100u + source + lane; + } else if (bound_ctrl) { + test.expected[lane] = 1000u + lane; + } + } + test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::S_MOV_B32, + O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.compute_info.wave_size = wave_size; + test.compute_info.threads_num[0] = wave_size; test.compute_info.threads_num[1] = 1; test.compute_info.threads_num[2] = 1; test.compute_info.thread_ids_num = 1; @@ -44177,7 +44208,10 @@ std::vector MakeCases() { cases.push_back(ComputeWave64Minimum(false)); cases.push_back(ComputeWave64Minimum(true)); AddCase(VectorDppBankMaskPreservesDestination); - AddCase(VectorDppBoundsControlZeroPreservesDestination); + for (const bool row_shift : {false, true}) + for (const bool bound_ctrl : {false, true}) + for (const u32 wave_size : {32u, 64u}) + cases.push_back(VectorDppInactiveSource(row_shift, bound_ctrl, wave_size)); AddCase(Vop3FmacF32NegatedSourceAccumulates); AddCase(VectorMadF32RoundsProduct); AddCase(VectorMadF32FlushesDenorms); @@ -50614,7 +50648,10 @@ int main(int argc, char **argv) { RunCase(&vulkan, VectorReadFirstLaneExecMasks(32)); RunCase(&vulkan, VectorReadFirstLaneExecMasks(64)); RunCase(&vulkan, VectorDppBankMaskPreservesDestination()); - RunCase(&vulkan, VectorDppBoundsControlZeroPreservesDestination()); + for (const bool row_shift : {false, true}) + for (const bool bound_ctrl : {false, true}) + for (const u32 wave_size : {32u, 64u}) + RunCase(&vulkan, VectorDppInactiveSource(row_shift, bound_ctrl, wave_size)); for (const u32 op_sel : {0u, 1u, 2u}) for (const u32 wave_size : {32u, 64u}) RunCase(&vulkan, VectorPermlane16InactiveSource(op_sel, wave_size));