diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 13:55:29 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 13:55:29 -0400 |
| commit | d7818f67b810e44cd3318e828e892040cc92c7b7 (patch) | |
| tree | d3a3f6bddce355ecd98be88f621674042b4d816d /src | |
| parent | ae3c7a57aa6769df07086af92a98dd8721389157 (diff) | |
Switch 2 shaders: centroid IPA, 8/16-bit LDG, UP0-UP6, PIXLD.COVMASK, USHF.R, 2D_ARRAY surfaces
Title 1 (768 dumped programs): 386 lowered cleanly, now 701.
- IPA: bits 76-77 are the sample mode (most varyings are centroid), mapped to
Maxwell bits 52-53.
- LDG: U8/S8/U16/S16 sizes as Maxwell's.
- Uniform predicates UP0-UP6 (UISETP outputs, USEL/VOTEU, uniform execution
guards) in R239 bit 6-n, swapped into P(6-n).
- PIXLD.COVMASK reads all-ones (no sample mask input; a mask write ANDs it).
- USHF.R.U32 of the low or high register.
- SULD/SUST 2D_ARRAY (dimension 4, as Maxwell's).
Diffstat (limited to 'src')
| -rw-r--r-- | src/shader_recompiler/frontend/ampere/lower_to_maxwell.h | 66 |
1 files changed, 46 insertions, 20 deletions
diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index 307be32a96..841ccdc1e3 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -129,9 +129,9 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { const bool uniform_predication = opcode == 0xabb || opcode == 0xab9 || opcode == 0xc82 || opcode == 0x882; const u32 native_predicate = insn.Bits(12, 4); const bool guarded_uniform = uniform_predication && native_predicate != 7; - if (guarded_uniform && (native_predicate & 7) != 0) - return fail(index, "native uniform execution predicate"); - u64 predicate = u64{guarded_uniform ? ((native_predicate & 8) | 6) : native_predicate} << 16; + // A uniform guard UPn lives in R239 bit 6-n and is swapped into P(6-n) for the op. + const u32 guard_slot = 6 - (native_predicate & 7); + u64 predicate = u64{guarded_uniform ? ((native_predicate & 8) | guard_slot) : native_predicate} << 16; const u64 dest = insn.Bits(16, 8); const auto base = [&](u64 opcode_bits) { return opcode_bits | predicate | dest; }; // NVN2 fullscreen vertex shaders read bank 24, which the game never binds (the console reads @@ -149,15 +149,16 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { }; std::string_view operand_error; const auto emit = [&](u64 word) { result.instructions.push_back(word); }; + const u32 guard_mask = 1U << guard_slot; const auto save_predicate = [&] { - emit(0x38e8000000070000ULL | 253 | (u64{255} << 8) | (u64{0x40} << 20)); + emit(0x38e8000000070000ULL | 253 | (u64{255} << 8) | (u64{guard_mask} << 20)); }; const auto restore_predicate = [&] { - emit(0x38f0000000070000ULL | (u64{253} << 8) | (u64{0x40} << 20)); + emit(0x38f0000000070000ULL | (u64{253} << 8) | (u64{guard_mask} << 20)); }; if (guarded_uniform) { save_predicate(); - emit(0x38f0000000070000ULL | (u64{239} << 8) | (u64{0x40} << 20)); + emit(0x38f0000000070000ULL | (u64{239} << 8) | (u64{guard_mask} << 20)); } const auto mov_imm = [&](u32 reg, u32 value) { emit(0x0100000000000000ULL | predicate | reg | (u64{15} << 12) | (u64{value} << 20)); @@ -507,7 +508,7 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { case 0x886: { // VOTEU ballot and independent uniform predicate output. const u32 output = insn.Bits(81, 3), target = insn.Bits(16, 8); if ((target >= 16 && target != 63) || insn.words[1] || - (output > 1 && output != 7) || insn.Bits(72, 2) > 2 || native_predicate != 7) + insn.Bits(72, 2) > 2 || native_predicate != 7) return fail(index, "native uniform vote mode"); const u32 temporary = output == 7 ? 7 : 6 - output; const u32 mask = 1U << temporary; @@ -606,6 +607,15 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { (u64{high + 216} << 8) | (u64{insn.words[1]} << 20) | (u64{1} << 48)); break; } + // Logical right shifts of one register (bit 76 right, bit 80 high word, bits 73-74 + // U32): USHF.R.U32 URd, URa, n, URZ and USHF.R.U32.HI URd, URZ, n, URb. + const bool right_low = insn.words[2] == 0x0800163f && low < 16; + const bool right_high = insn.words[2] == (0x08011600U | high) && low == 63 && high < 16; + if ((right_low || right_high) && dst < 16 && native_predicate == 7 && insn.words[1] <= 32) { + emit(0x3828000000000000ULL | predicate | (dst + 216) | + (u64{(right_high ? high : low) + 216} << 8) | (u64{insn.words[1]} << 20)); + break; + } const bool high_result = insn.words[2] == (0x08010600U | high) && low == 63 && high < 16; const bool low_result = insn.words[2] == 0x0800063f && low < 16; if (dst >= 16 || native_predicate != 7 || insn.words[1] > 32 || (!high_result && !low_result)) @@ -763,26 +773,28 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { (u64{1} << 48)); break; } - case 0xb97: { // SULD.P.2D.IGN, direct descriptor, channel mask at 72-75. + case 0xb97: { // SULD.P.2D/2D_ARRAY.IGN, direct descriptor, channel mask at 72-75. const u32 binding = insn.Bits(54, 5), offset = insn.Bits(40, 14); const u32 coordinate = insn.Bits(24, 8); const u32 channels = insn.Bits(72, 4); - if ((insn.words[2] & ~0xf00U) != 0x001e0000 || channels == 0 || insn.Bits(61, 3) != 3 || - binding >= 18 || offset > 0x1fff || insn.Bits(59, 2) || dest == 255 || coordinate > 253) + const u32 dimension = insn.Bits(61, 3); // Maxwell's codes: 3 = 2D, 4 = 2D_ARRAY + if ((insn.words[2] & ~0xf00U) != 0x001e0000 || channels == 0 || (dimension != 3 && dimension != 4) || + binding >= 18 || offset > 0x1fff || insn.Bits(59, 2) || dest == 255 || + coordinate > (dimension == 4 ? 252U : 253U)) return fail(index, "native formatted surface load mode"); if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) return fail(index, "native surface descriptor buffer conflict"); result.required_texture_buffer = binding; emit(base(0xeb00000000000000ULL) | (u64{coordinate} << 8) | - (u64{channels} << 20) | (u64{3} << 33) | (u64{offset} << 36) | (u64{1} << 51)); + (u64{channels} << 20) | (u64{dimension} << 33) | (u64{offset} << 36) | (u64{1} << 51)); break; } case 0xb9b: { // SUST.P.2D/3D.IGN, direct descriptor and all four channels. const u32 binding = insn.Bits(54, 5), offset = insn.Bits(40, 14); const u32 data = insn.Bits(32, 8), coordinate = insn.Bits(24, 8); const u32 dimension = insn.Bits(61, 3); - if (insn.words[2] != 0x00100f00 || (dimension != 3 && dimension != 5) || - binding >= 18 || offset > 0x1fff || insn.Bits(59, 2) || data > 252 || coordinate > (dimension == 5 ? 252U : 253U)) + if (insn.words[2] != 0x00100f00 || dimension < 3 || dimension > 5 || + binding >= 18 || offset > 0x1fff || insn.Bits(59, 2) || data > 252 || coordinate > (dimension >= 4 ? 252U : 253U)) return fail(index, "native formatted surface store mode"); if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) return fail(index, "native surface descriptor buffer conflict"); @@ -1025,7 +1037,7 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { case 0x28c: case 0x88c: { // UISETP, signed (bit 73) or unsigned, with UPT accumulation. const u32 output = insn.Bits(81, 3); const u32 a = uniform_register(insn.Bits(24, 8)), b = opcode == 0x88c ? 252U : uniform_register(insn.Bits(32, 8)); - if (output > 1 || insn.Bits(84, 3) != 7 || insn.Bits(87, 4) != 7 || + if (output > 6 || insn.Bits(84, 3) != 7 || insn.Bits(87, 4) != 7 || (insn.Bits(72, 4) & ~2U) || insn.Bits(12, 4) != 7 || a == ~0U || b == ~0U) return fail(index, "native uniform comparison mode"); const u32 temporary = 6 - output, mask = 1U << temporary; @@ -1042,7 +1054,7 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { const u32 condition = insn.Bits(87, 3); const u32 target = uniform_register(static_cast<u32>(dest)); const u32 a = uniform_register(insn.Bits(24, 8)), b = opcode == 0x887 ? 252U : uniform_register(insn.Bits(32, 8)); - if (condition > 1 || target == ~0U || a == ~0U || b == ~0U || + if (condition > 6 || target == ~0U || a == ~0U || b == ~0U || insn.Bits(12, 4) != 7 || (insn.words[2] & ~0x07800000U) != 0x08000000U) return fail(index, "native uniform select mode"); if (opcode == 0x887 && index > 0 && condition == 0 && insn.words[1] <= 0x7ffff && @@ -1064,10 +1076,11 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { emit(0x38f0000000070000ULL | (u64{253} << 8) | (u64{mask} << 20)); break; } - case 0x381: case 0x981: { // LDG.E of 32/64/128 bits, register or uniform 64-bit address. + case 0x381: case 0x981: { // LDG.E of 8 to 128 bits, register or uniform 64-bit address. + // Sizes as Maxwell's: U8, S8, U16, S16, 32, 64, 128. const u32 size = insn.Bits(73, 3); u32 address = insn.Bits(24, 8); - if (size < 4 || size > 6 || (opcode == 0x381 && insn.Bits(32, 8)) || + if (size > 6 || (opcode == 0x381 && insn.Bits(32, 8)) || (size == 5 && (dest & 1)) || (size == 6 && (dest & 3))) return fail(index, "native global load mode"); if (opcode == 0x381) { @@ -1580,9 +1593,22 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) { (u64{insn.Bits(64, 8)} << 30) | (u64{1} << 38) | (u64{insn.Bits(78, 1)} << 55)); break; } - case 0x326: { // IPA.PASS / CONSTANT, default sample location - if (insn.Bits(32, 8) != 255 || (insn.Bits(76, 4) != 0 && insn.Bits(76, 4) != 4) || insn.Bits(81, 3) != 7) return fail(index, "native IPA interpolation mode"); - emit(base(0xe000000000000000ULL) | (u64{255} << 8) | (u64{255} << 20) | (u64{insn.Bits(64, 8)} << 30) | (u64{insn.Bits(78, 1)} << 55)); + case 0x326: { // IPA.PASS / CONSTANT, default or centroid sample location + // Bits 76-77 are the sample mode (title 1 interpolates most varyings at the centroid), + // bit 78 the constant mode; Maxwell keeps them at bits 52-53 and 55. + if (insn.Bits(32, 8) != 255 || insn.Bits(76, 2) > 1 || insn.Bits(79, 1) || + insn.Bits(81, 3) != 7) + return fail(index, "native IPA interpolation mode"); + emit(base(0xe000000000000000ULL) | (u64{255} << 8) | (u64{255} << 20) | (u64{insn.Bits(64, 8)} << 30) | + (u64{insn.Bits(76, 2)} << 52) | (u64{insn.Bits(78, 1)} << 55)); + break; + } + case 0x923: { // PIXLD.COVMASK into a register, PT destination predicate + // Eden has no sample mask input; all bits set is what a mask write would AND with + // the real coverage anyway (title 1's fragment shaders start with this). + if (insn.Bits(24, 8) != 255 || insn.words[1] || insn.words[2] != 0x00007800 || dest >= 255) + return fail(index, "native pixel load mode"); + mov_imm(static_cast<u32>(dest), 0xffffffffU); break; } case 0x805: { // CS2R SRZ |
