diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index 534057e8d..791cae053 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -132,7 +132,7 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl enable(flags.bank_mask, 2u); enable(flags.row_mask, 4u); // Only row shifts (0x101-0x10f, 0x111-0x11f) can vacate a lane; other controls always read one. - const bool row_shift = !flags.dpp8 && flags.control > 0x100u && flags.control < 0x120u && + const bool row_shift = flags.control > 0x100u && flags.control < 0x120u && flags.control != 0x110u; if (!flags.bound_control && row_shift) { writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable, diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index c148e901c..2020ec64a 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -319,7 +319,10 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) { case Decoder::OperandKind::Vgpr: { const auto reg = static_cast(operand.reg); const auto old = ir.GetVectorReg(reg); - if (operand.dpp) { + // DPP skips EXEC lanes only through row/bank masks, or for invalid sources without + // BOUND_CTRL (dpp8 reads zero); otherwise it is a plain masked write. + if (operand.dpp && (operand.dpp_row_mask != 0xfu || operand.dpp_bank_mask != 0xfu || + (!operand.dpp_bound_ctrl && !operand.dpp8))) { value = IR::U32(ir.Emit(IR::ValueOpcode::DppUpdateU32, {value, old, ir.GetExec()}, DppFlags(operand))); } else { diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 37c67fe2c..8519eaec0 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -7045,6 +7045,17 @@ void TestPixelAncillaryLayerInput() { CheckSpirvBinaryValidates(wqm_result.spirv); } + uint32_t dpp[] = { + EncodeVop3Word0(0x148, 6), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11), + EncodeVop1(0x01, 5, 250), EncodeVop1Dpp(6) | (1u << 19u), // Bound-zero DPP overwrite. + EncodeExp0(0x00, 0x1), EncodeExp1(5, 0, 0, 0), EncodeSopp(0x01), + }; + auto dpp_result = RecompileForTest(dpp, options); + Check(ProgramHasInput(dpp_result.program, StageInputKind::Layer) && + !ProgramHasInput(dpp_result.program, StageInputKind::PackedAncillary), + "DPP write retained the old ancillary register value"); + CheckSpirvBinaryValidates(dpp_result.spirv); + const uint32_t loop[] = { EncodeSop1(0x04, 8, 126), // Save the original EXEC mask. EncodeSop1(0x0a, 126, 8), // Widen for helper lanes. @@ -7098,6 +7109,9 @@ void TestPixelAncillaryLayerInput() { shader[5] = EncodeExp1(6, 7, 5, 0); ExpectFatal([&] { (void)RecompileForTest(shader, options); }, "direct raw ancillary export was silently replaced after field lowering"); + dpp[3] = EncodeVop1Dpp(6); // Without BOUND_CTRL, lanes with inactive sources keep the value. + ExpectFatal([&] { (void)RecompileForTest(dpp, options); }, + "DPP write without BOUND_CTRL silently replaced the ancillary value"); #endif }