From 9e368c29181cfa0ce23cf61c9b5b654917842dd0 Mon Sep 17 00:00:00 2001 From: rdenno Date: Mon, 2 Nov 2026 08:07:37 -0500 Subject: shader: lower F2I from a half with a sign or high-half selector F2I from an F16 source was only lowered unsigned and from the low half. F2I.F16 (signed, bit 72) and F2I.U32.F16 Rd, Rs.H1 (bits 60-61) were rejected ("native float-to-integer conversion mode"); Hyrule Warriors: Age of Imprisonment has 24 such programs. Accept bit 72 as the other F2I forms do, and pick the high half for .H1. Assisted-by: Claude Opus 5.5 (diagnosis, fix and testing) --- src/shader_recompiler/frontend/ampere/lower_to_maxwell.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index 4ce374ce61..00ff585041 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -1440,12 +1440,13 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 break; case 0x305: case 0xb05: case 0xd05: { // F2I NTZ, register/constant-buffer/uniform source const u32 mode = insn.words[2] & ~(opcode == 0xd05 ? 0x08000000U : 0U); - const bool half = opcode == 0x305 && mode == 0x0010f000; + // F16 source: bit 72 signs the result as in the other forms, bits 60-61 pick H0/H1. + const bool half = opcode == 0x305 && (mode & ~0x100U) == 0x0010f000; const bool small = mode == 0x00212800; const bool floor = (mode & ~0x10100U) == 0x00207000; const bool nearest = (mode & ~0x10100U) == 0x00203000; if ((!half && !small && !floor && !nearest && (mode & ~0x100U) != 0x0021f000) || - (opcode != 0xb05 && (insn.words[1] & 0xffffff00U)) || + (opcode != 0xb05 && (insn.words[1] & (half ? 0xefffff00U : 0xffffff00U))) || (opcode == 0xd05 && uniform_register(insn.Bits(32, 8)) == ~0U)) { return fail(index, "native float-to-integer conversion mode"); } @@ -1453,7 +1454,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 u32 source = opcode == 0xb05 ? materialize(2, 0, 252, false, false) : opcode == 0xd05 ? uniform_register(insn.Bits(32, 8)) : insn.Bits(32, 8); if (half) { - extract_half({source, 2, false, false}, 0, 252); + extract_half({source, insn.Bits(60, 1) ? 3U : 2U, false, false}, 0, 252); source = 252; } if (!is_signed) { -- cgit v1.2.3 From 8b8d0fc85f1267dc93d069c5a331c4f24ac3a835 Mon Sep 17 00:00:00 2001 From: rdenno Date: Mon, 2 Nov 2026 08:07:36 -0500 Subject: shader: lower TLD and TLD4 with half-precision results TLD.SCR and TLD4 with .F16 (bit 79) return their components packed in pairs: components 0 and 1 in Rd, 2 and 3 in the second destination. The lowering rejected them ("native texel fetch mode", "native texture gather mode"), so the compute programs using them never ran. In Hyrule Warriors: Age of Imprisonment these are compute programs that run once gameplay starts, and gameplay stayed black (the pause menu, drawn another way, showed the scene). Lower the F32 form into R248/R249 and R246/R247, then pack each pair the way F2FP.PACK_AB is lowered. Assisted-by: Claude Opus 5.5 (diagnosis, fix and testing) --- .../frontend/ampere/lower_to_maxwell.h | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'src') diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index 00ff585041..a72c3525ce 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -88,6 +88,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 result.source_instruction_offsets.push_back(static_cast(result.instructions.size())); u32 opcode = insn.Bits(0, 12); if (insn.Bits(12, 4) == 15) continue; // @!PT: never executes (compiler padding) + u32 f16_dest = 255, f16_second = 255, f16_components = 0; std::optional active_link; u32 active_entry = 0; if (main_exited) { @@ -108,6 +109,19 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 const u32 reg = insn.Bits(bit, 8); if (reg >= (uses_uniform_bank ? high_uniform_base : 246U) && reg < 255) return fail(index, "native program uses lowering scratch registers"); } + // TLD/TLD4 .F16 (bit 79): the components come packed in pairs, (0, 1) in Rd and (2, 3) in + // the second destination. Lowered as the F32 form into R248/R249 and R246/R247, then packed. + if ((opcode == 0xb66 || opcode == 0xb63) && insn.Bits(79, 2) == 1) { + f16_dest = insn.Bits(16, 8); + f16_second = insn.Bits(64, 8); + f16_components = static_cast(std::popcount(insn.Bits(72, 4))); + if (f16_dest >= 246 || (f16_second != 255 && f16_second >= 246) || insn.Bits(61, 3) == 5 || + (f16_second == 255 && f16_components > 2)) + return fail(index, "native half-precision texture result registers"); + insn.words[2] &= ~(1U << 15); + insn.words[0] = (insn.words[0] & ~0x00ff0000U) | (f16_dest == 255 ? 0x00ff0000U : (248U << 16)); + if (f16_second != 255) insn.words[2] = (insn.words[2] & ~0xffU) | 246U; + } // UR0-UR21 live in R216-R237 and UR22-UR27 in R240-R245 (R239 holds the uniform // predicates), so even/odd pairs stay adjacent; UR28 and up below R216. Sites that // still check reg < 16 rely on UR0-UR15 being R216-R231. @@ -2613,6 +2627,21 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 return fail(index, "unsupported native opcode"); } if (!operand_error.empty()) return fail(index, operand_error); + if (f16_components != 0) { + // Each pair as F2FP.PACK_AB: the second value in the high half. + const u64 pred = u64{insn.Bits(12, 4)} << 16; + const auto pack = [&](u32 target, u32 low, u32 high) { + result.instructions.push_back(0x5ca8000000000000ULL | pred | 250 | (u64{1} << 8) | + (u64{2} << 10) | (u64{high} << 20)); + result.instructions.push_back(0x5ca8000000000000ULL | pred | 251 | (u64{1} << 8) | + (u64{2} << 10) | (u64{low} << 20)); + result.instructions.push_back(0x5c18000000000000ULL | pred | target | (u64{250} << 8) | + (u64{251} << 20) | (u64{16} << 39)); + }; + if (f16_dest != 255) pack(f16_dest, 248, f16_components > 1 ? 249U : 255U); + if (f16_second != 255 && f16_components > 2) + pack(f16_second, 246, f16_components > 3 ? 247U : 255U); + } if (output_mask) { emit(0x38e8000000070000ULL | 239 | (u64{239} << 8) | (u64{output_mask} << 20)); emit(0x38f0000000070000ULL | (u64{254} << 8) | (u64{output_mask} << 20)); -- cgit v1.2.3 From 7ac108e2571d7cb39585c58994524131dd9d8c22 Mon Sep 17 00:00:00 2001 From: rdenno Date: Mon, 2 Nov 2026 08:42:56 -0500 Subject: shader: lower TXD with any channel mask TXD was only lowered with all four channels (and rejected any instruction without a second destination, as RZ & 1 is set). Hyrule Warriors: Age of Imprisonment's gradient-sampling programs fetch one channel into Rd, or three channels with the third in the second destination, and were rejected ("native gradient texture mode"), so their draws were skipped. Pass the channel mask on to Maxwell TXD. The first two enabled channels go to Rd, Rd+1 and the others to the second destination; when those aren't consecutive, write to scratch and move them as the four-channel case already did. Assisted-by: Claude Opus 5.5 (diagnosis, fix and testing) --- .../frontend/ampere/lower_to_maxwell.h | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index a72c3525ce..f189f879b8 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -823,27 +823,33 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 // (x, y[, layer]) and the four gradients sit in the same registers for both. const u32 dimension = insn.Bits(61, 3); const u32 high = insn.Bits(64, 8); // destination of the third and fourth channels - // Bit 90 is forwarded below (Maxwell's NODEP); Splatoon Raiders sets it. - if ((insn.words[2] & ~(0xffU | (1U << 26))) != 0x001e0f00 || insn.Bits(59, 2) || + // Bit 90 is forwarded below (Maxwell's NODEP); Splatoon Raiders sets it. Bits 72-75 are + // the channel mask: the first two enabled channels go to Rd, Rd+1, the others to the + // second destination. + const u32 mask = insn.Bits(72, 4); + const u32 channels = static_cast(std::popcount(mask)); + if ((insn.words[2] & ~(0xffU | 0xf00U | (1U << 26))) != 0x001e0000 || insn.Bits(59, 2) || + mask == 0 || (channels > 2) != (high != 255) || (dimension != 1 && dimension != 5) || binding >= 18 || offset > 0x1fff || - (dest & 1) || dest >= 248 || (high & 1) || (high >= 248 && high != 255) || + (channels > 1 && (dest & 1)) || dest >= 248 || + (high != 255 && ((channels > 3 && (high & 1)) || high >= 248)) || coordinates >= 248 || derivatives >= 247) return fail(index, "native gradient texture mode"); if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) return fail(index, "native texture descriptor buffer conflict"); result.required_texture_buffer = binding; - // Maxwell writes four consecutive registers: when the second pair is elsewhere (Splatoon - // Raiders: R12/R18, R18/R44), write them to scratch and move each pair into place. - const bool consecutive = high == dest + 2; + // Maxwell writes the channels to consecutive registers: when the second destination + // doesn't follow Rd (Splatoon Raiders: R12/R18, R18/R44), write them to scratch and move + // each into place. + const bool consecutive = channels <= 2 || high == dest + 2; const u64 target = consecutive ? dest : 248; emit(0xde00000000000000ULL | predicate | target | (u64{coordinates} << 8) | - (u64{derivatives} << 20) | (u64{dimension == 5 ? 3U : 2U} << 28) | (u64{15} << 31) | + (u64{derivatives} << 20) | (u64{dimension == 5 ? 3U : 2U} << 28) | (u64{mask} << 31) | (u64{offset} << 36) | (u64{insn.Bits(90, 1)} << 49) | (u64{7} << 51)); if (!consecutive) { - for (u32 i = 0; i < 2; ++i) { - emit(0x5c98000000000000ULL | predicate | (dest + i) | (u64{15} << 39) | (u64{248 + i} << 20)); - if (high != 255) - emit(0x5c98000000000000ULL | predicate | (high + i) | (u64{15} << 39) | (u64{250 + i} << 20)); + for (u32 i = 0; i < channels; ++i) { + const u32 to = i < 2 ? static_cast(dest) + i : high + (i - 2); + emit(0x5c98000000000000ULL | predicate | to | (u64{15} << 39) | (u64{248 + i} << 20)); } } break; -- cgit v1.2.3