aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-01 21:41:00 -0500
committerDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-01 21:54:48 -0500
commit43ab1ee42c70b073a839c2675fa33b210f2d8b46 (patch)
treea9a859dd5aa309882b8a96ae42f5337cbf532549 /src
parent44c0fa86e2d4cd0cfb04005b2653d7e94b70753f (diff)
shader: lower more conversion, comparison and memory forms
Other instruction forms Fire Emblem: Fortune's Weave's shaders use: - I2F.F32.U64/S64 from a register pair, and F2I.S32.TRUNC without FTZ. - FMNMX.NAN (an unordered pair gives the canonical NaN). - FSET.BF with a uniform register operand (0xc0a), used by battle scene shaders that were rejected and left the screen white. - MATCH.ALL.U32, next to U64. - IDP.2A.LO.U16.U8. - USHF with UR16-UR27 (was UR0-UR15). - STG with desc[URn][Ra.64] addressing, as LDG, and the LDG descriptor form with any URn (was UR0 only). Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
Diffstat (limited to 'src')
-rw-r--r--src/shader_recompiler/frontend/ampere/lower_to_maxwell.h94
1 files changed, 66 insertions, 28 deletions
diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
index d7bad88fa5..7bc8388192 100644
--- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
+++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
@@ -56,7 +56,7 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
for (const auto& instruction : code) {
const u32 op = instruction.Bits(0, 12);
uses_uniform_bank |= op == 0x3c4 || op == 0x2bd || op == 0xc0c || op == 0x986 || op == 0x9a8 || op == 0xc12 || op == 0xc17 || op == 0xe24 || op == 0xc24 || op == 0xd00 || op == 0xd09 || op == 0x9c3 || op == 0xc45 || op == 0x2a4 || op == 0x891 || op == 0x890 || op == 0x290 || op == 0x28c || op == 0x88c || op == 0x287 || op == 0x887 || op == 0x958 || op == 0xab9 || op == 0xabb || op == 0x899 || op == 0xc20 || op == 0xe21 || op == 0xe23 || op == 0xc02 ||
- op == 0x886 || op == 0x3c2 || op == 0xc82 || op == 0x882 || op == 0x292 || op == 0x892 || op == 0x291 || op == 0x981 || op == 0xc13 || op == 0xc10 || op == 0xc23 || op == 0x526 || op == 0x2be || op == 0x8a4 || op == 0xd05 || (op == 0x984 && instruction.Bits(91, 1)) || op == 0x89c || op == 0x883 || op == 0x2a5 || op == 0xd06 || op == 0x299 || op == 0xc09 || (op == 0x81c && instruction.Bits(67, 1)) || op == 0xf60 || op == 0xc11;
+ op == 0x886 || op == 0x3c2 || op == 0xc82 || op == 0x882 || op == 0x292 || op == 0x892 || op == 0x291 || op == 0x981 || op == 0xc13 || op == 0xc10 || op == 0xc23 || op == 0x526 || op == 0x2be || op == 0x8a4 || op == 0xd05 || (op == 0x984 && instruction.Bits(91, 1)) || op == 0x89c || op == 0x883 || op == 0x2a5 || op == 0xd06 || op == 0x299 || op == 0xc09 || (op == 0x81c && instruction.Bits(67, 1)) || op == 0xf60 || op == 0xc11 || op == 0xc0a;
}
const auto address_of = [](u32 index) { return 0x50U + index * 8 + ((index + 1) / 3) * 8; };
const auto fail = [&](u32 index, std::string_view reason) {
@@ -518,6 +518,18 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
emit(0x5c10000000070000ULL | d_low | (u64{250} << 8) | (u64{255} << 20));
break;
}
+ case 0x226: { // IDP.2A.LO.U16.U8: a.lo16 * b.byte0 + a.hi16 * b.byte1 + c
+ if ((insn.words[2] & ~0xffU) != 0x1000 || (insn.words[1] & ~0xffU))
+ return fail(index, "native packed dot-product mode");
+ const u32 a = insn.Bits(24, 8), b = insn.Bits(32, 8), c = insn.Bits(64, 8);
+ for (u32 component = 0; component < 2; ++component) {
+ emit(0x3828000000000000ULL | predicate | (250 + component) | (u64{b} << 8) | (u64{component * 8} << 20));
+ emit(0x0400000000000000ULL | predicate | (250 + component) | (u64{250 + component} << 8) | (u64{0xff} << 20));
+ }
+ xmad(252, a, 250, c, false, false, 0, false);
+ xmad(static_cast<u32>(dest), a, 251, 252, true, false, 0, false);
+ break;
+ }
case 0x225: case 0x227: case 0x625: case 0x825: case 0x827: case 0xa27: {
// IMAD.HI Rd / IMAD.WIDE Rd:Rd+1 = a * b + c:c+1 (.U32 or signed, bit 73), with an optional
// carry-out (bits 81-83). b is a register, an immediate (0x8xx) or a cbuf (0xa27); 0x625 takes
@@ -804,27 +816,27 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
}
case 0x899: { // USHF.L.U32; accept a zero word outside the selected half.
const u32 dst = insn.Bits(16, 8), low = insn.Bits(24, 8), high = insn.Bits(64, 8);
- if (insn.words[2] == (0x08011400U | high) && low == 63 && high < 16 &&
- dst < 16 && native_predicate == 7 && insn.words[1] <= 32) {
- emit(0x3828000000000000ULL | predicate | (dst + 216) |
- (u64{high + 216} << 8) | (u64{insn.words[1]} << 20) | (u64{1} << 48));
+ if (insn.words[2] == (0x08011400U | high) && low == 63 && high < 28 &&
+ dst < 28 && native_predicate == 7 && insn.words[1] <= 32) {
+ emit(0x3828000000000000ULL | predicate | uniform_register(dst) |
+ (u64{uniform_register(high)} << 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));
+ const bool right_low = insn.words[2] == 0x0800163f && low < 28;
+ const bool right_high = insn.words[2] == (0x08011600U | high) && low == 63 && high < 28;
+ if ((right_low || right_high) && dst < 28 && native_predicate == 7 && insn.words[1] <= 32) {
+ emit(0x3828000000000000ULL | predicate | uniform_register(dst) |
+ (u64{uniform_register(right_high ? high : low)} << 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))
+ const bool high_result = insn.words[2] == (0x08010600U | high) && low == 63 && high < 28;
+ const bool low_result = insn.words[2] == 0x0800063f && low < 28;
+ if (dst >= 28 || native_predicate != 7 || insn.words[1] > 32 || (!high_result && !low_result))
return fail(index, "native uniform shift mode");
- emit(0x3848000000000000ULL | predicate | (dst + 216) |
- (u64{(high_result ? high : low) + 216} << 8) | (u64{insn.words[1]} << 20));
+ emit(0x3848000000000000ULL | predicate | uniform_register(dst) |
+ (u64{uniform_register(high_result ? high : low)} << 8) | (u64{insn.words[1]} << 20));
break;
}
case 0xb82: { // LDC.64 indexed byte offset
@@ -933,23 +945,25 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
emit(0x3828000000000000ULL | predicate | (dest + 216) | (u64{insn.words[1] + 216} << 8) |
(u64{1} << 39) | (u64{1} << 40));
break;
- case 0x3a1: { // MATCH.ALL.U64 with only the all-equal predicate retained.
+ case 0x3a1: { // MATCH.ALL.U32/U64 (bit 73) with only the all-equal predicate retained.
const u32 source = insn.Bits(24, 8), target = insn.Bits(81, 3);
- if (dest != 255 || source >= 254 || (source & 1) || target >= 6 ||
- native_predicate != 7 || insn.words[1] || (insn.words[2] & ~0xe0000U) != 0x200)
+ const bool wide = insn.Bits(73, 1);
+ if (dest != 255 || source >= 254 || (wide && (source & 1)) || target >= 6 ||
+ native_predicate != 7 || insn.words[1] || (insn.words[2] & ~0xe0200U))
return fail(index, "native warp match mode");
save_predicate();
emit(0x50d8000000070000ULL | 250 | (u64{7} << 39) | (u64{7} << 45) | (u64{1} << 48));
emit(0x5c10000000070000ULL | 251 | (u64{250} << 8) | (u64{255} << 20) | (u64{1} << 49));
emit(0x5c40000000070000ULL | 250 | (u64{250} << 8) | (u64{251} << 20) | (u64{7} << 48));
emit(0x5c30000000070000ULL | 250 | (u64{250} << 20));
- for (u32 word = 0; word < 2; ++word)
+ for (u32 word = 0; word < (wide ? 2U : 1U); ++word)
emit(0xef10000000070000ULL | (251 + word) | (u64{source + word} << 8) |
(u64{250} << 20) | (u64{1} << 29) | (u64{31} << 34) | (u64{7} << 48));
emit(0x5b60000000070000ULL | 7 | (u64{6} << 3) | (u64{source} << 8) |
(u64{251} << 20) | (u64{7} << 39) | (u64{2} << 49));
- emit(0x5b60000000070000ULL | 7 | (u64{6} << 3) | (u64{source + 1} << 8) |
- (u64{252} << 20) | (u64{6} << 39) | (u64{2} << 49));
+ if (wide)
+ emit(0x5b60000000070000ULL | 7 | (u64{6} << 3) | (u64{source + 1} << 8) |
+ (u64{252} << 20) | (u64{6} << 39) | (u64{2} << 49));
emit(0x50d8000000070000ULL | 255 | (u64{6} << 39) | (u64{target} << 45));
restore_predicate();
break;
@@ -1378,7 +1392,7 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
insn.Bits(38, 2) ? ~0U : uniform_register(insn.Bits(32, 6));
// desc[URn][Ra.64]: the uniform pair is a memory (cache policy) descriptor and
// Ra the whole address.
- const bool descriptor_address = (insn.words[2] & ~0xe00U) == 0x0c1e1100;
+ const bool descriptor_address = (insn.words[2] & ~(0x3fU | 0xe00U)) == 0x0c1e1100;
if ((!descriptor_address &&
(insn.words[2] & ~(0x04000000U | 0x3fU | 0xe00U | 0x2000U)) != 0x081e8100) ||
uniform_address == ~0U || (uniform_address & 1))
@@ -1419,10 +1433,16 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
bool extended = insn.Bits(72, 1) != 0;
if (opcode == 0x986) {
const u32 uniform_address = uniform_register(insn.Bits(64, 6));
- if ((insn.words[2] & ~(0x04000000U | 0x3fU | 0xe00U)) != 0x0810a100 ||
+ // desc[URn][Ra.64]: the uniform pair is a memory (cache policy) descriptor and
+ // Ra the whole address.
+ const bool descriptor_address = (insn.words[2] & ~(0x3fU | 0xe00U)) == 0x0c101100;
+ if ((!descriptor_address && (insn.words[2] & ~(0x04000000U | 0x3fU | 0xe00U)) != 0x0810a100) ||
uniform_address == ~0U || (uniform_address & 1) || !extended)
return fail(index, "native global store uniform addressing mode");
- if (address == 255) address = uniform_address;
+ if (descriptor_address) {
+ if ((address & 1) || address >= 246)
+ return fail(index, "native global store register address pair");
+ } else if (address == 255) address = uniform_address;
else {
if (insn.Bits(90, 1) && ((address & 1) || address >= 254))
return fail(index, "native global store register address pair");
@@ -1453,13 +1473,21 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
(u64{insn.Bits(32, 8)} << 20) | (u64{3} << 39) | (u64{1} << 44));
break;
}
+ case 0x312: { // I2F.F32.(U|S)64, register pair source; sign (74) and rounding (78-79) as Maxwell's
+ if ((insn.words[2] & ~0xc400U) != 0x00301000 || (insn.words[1] & ~0xffU) || insn.Bits(24, 8) ||
+ (insn.Bits(32, 8) & 1) || insn.Bits(32, 8) >= 254)
+ return fail(index, "native 64-bit integer-to-float mode");
+ emit(base(0x5cb8000000000000ULL) | (u64{2} << 8) | (u64{3} << 10) | (u64{insn.Bits(74, 1)} << 13) |
+ (u64{insn.Bits(32, 8)} << 20) | (u64{insn.Bits(78, 2)} << 39));
+ 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;
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) ||
+ if ((!half && !small && !floor && !nearest && (mode & ~0x10100U) != 0x0020f000) ||
(opcode != 0xb05 && (insn.words[1] & 0xffffff00U)) ||
(opcode == 0xd05 && uniform_register(insn.Bits(32, 8)) == ~0U)) {
return fail(index, "native float-to-integer conversion mode");
@@ -1923,12 +1951,22 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
break;
}
case 0x209: case 0x809: case 0xa09: case 0xc09: { // FMNMX, including a uniform source
- if (insn.Bits(74, 6) || insn.Bits(81, 6) || (opcode != 0xc09 && insn.Bits(91, 1)))
+ if (insn.Bits(74, 6) || insn.Bits(82, 5) || (opcode != 0xc09 && insn.Bits(91, 1)))
return fail(index, "native floating min/max mode");
const u32 a = operand(0), b = operand(1);
+ // .NAN (bit 81): an unordered pair gives the canonical NaN. R252 = ~0 when both are numbers.
+ const bool propagate_nan = insn.Bits(81, 1);
+ if (propagate_nan)
+ emit(0x5800000000000000ULL | predicate | 252 | (u64{a} << 8) | (u64{b} << 20) | (u64{7} << 39) | (u64{7} << 48));
emit(base(0x5c60000000000000ULL) | (u64{a} << 8) | (u64{b} << 20) |
(u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42) |
(u64{insn.Bits(80, 1)} << 44));
+ if (propagate_nan) {
+ emit(base(0x5c40000000000000ULL) | (dest << 8) | (u64{252} << 20) | (u64{7} << 48));
+ emit(0x0400000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{0xffffffffU} << 20) | (u64{2} << 53));
+ emit(0x0400000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{0x7fffffffU} << 20));
+ emit(base(0x5c40000000000000ULL) | (dest << 8) | (u64{252} << 20) | (u64{1} << 41) | (u64{7} << 48));
+ }
break;
}
case 0x308: case 0xb08: { // MUFU
@@ -2051,8 +2089,8 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
(u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42));
break;
}
- case 0x20a: case 0x80a: case 0xa0a: { // FSET.BF, combined with Pc (bits 87-90) by bits 74-75
- if (insn.Bits(64, 8) || insn.Bits(74, 2) == 3 || insn.Bits(81, 6) || insn.Bits(91, 1))
+ case 0x20a: case 0x80a: case 0xa0a: case 0xc0a: { // FSET.BF, combined with Pc (bits 87-90) by bits 74-75 (0xc0a: uniform b)
+ if (insn.Bits(64, 8) || insn.Bits(74, 2) == 3 || insn.Bits(81, 6) || (opcode != 0xc0a && insn.Bits(91, 1)))
return fail(index, "native floating comparison output mode");
const u32 a = operand(0), b = operand(1);
emit(base(0x5800000000000000ULL) | (u64{a} << 8) | (u64{b} << 20) |