aboutsummaryrefslogtreecommitdiff
path: root/src/shader_recompiler
diff options
context:
space:
mode:
authorDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-01 16:18:35 -0500
committerDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-01 21:54:48 -0500
commit2b8002474d25897ced2f7787ab68f39971c6b05a (patch)
treee891a58f1046e33b6cbb9dce379c2cb67faffe51 /src/shader_recompiler
parent18b0671e1b640fc3cbca565c39f5a69a287ede9b (diff)
shader: lower more integer forms
Forms Fire Emblem: Fortune's Weave's shaders use that the lowering rejected, mostly 64-bit address and index arithmetic: - ISETP.EX: EQ/NE as the high comparison ANDed/ORed with the low words' predicate, unsigned LT/LE/GT/GE as (hi < or > hi) || (hi == hi && low). - IMAD.HI signed (bit 73), and with a constant buffer multiplier (0xa27). - IMAD.X Rd, Ra, 1, Rc, Pn (an add with carry). - IADD3.X with a register first source or a uniform operand, and IADD3 with a uniform operand and a carry out. - LEA.HI.X (carry in) with a register base, and LEA.HI.X.SX32 with a constant base. - SHF with a constant buffer shift amount (0xa19). - IMNMX with a negative immediate. - F2I.FTZ.U64.TRUNC (0x311). Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting) Signed-off-by: duck912 <350528965+Duck912@users.noreply.github.com> Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
Diffstat (limited to 'src/shader_recompiler')
-rw-r--r--src/shader_recompiler/frontend/ampere/lower_to_maxwell.h147
1 files changed, 127 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 2d994ad366..946874c271 100644
--- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
+++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
@@ -504,15 +504,31 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
emit(0x5c10000000070000ULL | d_low | (u64{250} << 8) | (u64{255} << 20));
break;
}
- case 0x827: { // IMAD.HI.U32, captured zero addend
- if (insn.words[2] != 0x078e00ff) return fail(index, "native high multiply mode");
- const u32 a = insn.Bits(24, 8), b = insn.words[1];
+ case 0x827: case 0xa27: { // IMAD.HI (.U32 or signed, bit 73), immediate or cbuf, zero addend
+ const bool constant = opcode == 0xa27, signed_product = insn.Bits(73, 1);
+ if ((insn.words[2] & ~0x200U) != 0x078e00ff ||
+ (constant && (!valid_cbuf() || insn.Bits(62, 2))))
+ return fail(index, "native high multiply mode");
+ const u32 a = insn.Bits(24, 8), b = constant ? 254U : insn.words[1];
+ if (constant) emit(0x4c98000000000000ULL | predicate | 254 | (u64{15} << 39) | cbuf());
+ if (signed_product) {
+ // The signed high word is the unsigned one minus (a < 0 ? b : 0) + (b < 0 ? a : 0),
+ // which R253 holds (R252 is free until the multiplication).
+ if (!constant) mov_imm(254, b);
+ emit(0x3828000000000000ULL | predicate | 253 | (u64{a} << 8) | (u64{31} << 20) | (u64{1} << 48));
+ emit(0x5c40000000000000ULL | predicate | 253 | (u64{253} << 8) | (u64{254} << 20) | (u64{7} << 48));
+ emit(0x3828000000000000ULL | predicate | 252 | (u64{254} << 8) | (u64{31} << 20) | (u64{1} << 48));
+ emit(0x5c40000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{a} << 20) | (u64{7} << 48));
+ emit(0x5c10000000000000ULL | predicate | 253 | (u64{253} << 8) | (u64{252} << 20));
+ }
// Base-2^16 long multiplication. Each intermediate sum fits U32.
- xmad(250, a, b, 255, false, false, 0, true);
- xmad(251, a, b, 250, true, false, 2, true);
- xmad(250, a, b, 251, false, true, 1, true);
- xmad(252, a, b, 251, true, true, 2, true);
+ xmad(250, a, b, 255, false, false, 0, !constant);
+ xmad(251, a, b, 250, true, false, 2, !constant);
+ xmad(250, a, b, 251, false, true, 1, !constant);
+ xmad(252, a, b, 251, true, true, 2, !constant);
emit(0x3828000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{16} << 20));
+ if (signed_product)
+ emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{253} << 20) | (u64{1} << 48));
emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{252} << 20));
break;
}
@@ -555,6 +571,16 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
}
case 0x824: { // Captured low-word IMAD forms; bit 75 negates the addend.
const bool negate_c = insn.Bits(75, 1) != 0;
+ if (insn.words[1] == 1 && (insn.words[2] & ~(0x07800000U | 0xffU)) == 0x000e0600U) {
+ // IMAD.X Rd, Ra, 1, Rc, Pn: Ra + Rc plus the carry predicate in bits 87-90.
+ mov_imm(252, 1);
+ emit(0x5ca0000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{255} << 20) |
+ (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42));
+ emit(0x5c10000000000000ULL | predicate | 250 | (u64{insn.Bits(24, 8)} << 8) |
+ (u64{insn.Bits(64, 8)} << 20));
+ emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{252} << 20));
+ break;
+ }
if (insn.words[1] == 1 && (insn.words[2] & 0xfffff700U) == 0x078e0200) {
emit(base(0x5c10000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) |
(u64{insn.Bits(64, 8)} << 20) | (u64{negate_c} << 48));
@@ -974,21 +1000,27 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
emit(base(0x3828000000000000ULL) | (u64{insn.words[1]} << 8) |
(u64{1} << 39) | (u64{1} << 40));
break;
- case 0x219: case 0xc19: { // U32 register (or uniform, 0xc19) shifts with a zero word outside the selected result.
+ case 0x219: case 0xa19: case 0xc19: { // U32 register (or cbuf, 0xa19, or uniform, 0xc19) shifts with a zero word outside the selected result.
const u32 low = insn.Bits(24, 8), high = insn.Bits(64, 8);
- const u32 shift = opcode == 0xc19 ? uniform_register(insn.Bits(32, 8)) : insn.Bits(32, 8);
+ const bool constant = opcode == 0xa19;
+ // A constant shift amount is loaded into R251 once the form is known.
+ const u32 shift = opcode == 0xc19 ? uniform_register(insn.Bits(32, 8)) : constant ? 251U : insn.Bits(32, 8);
+ const bool shift_operand = constant ? valid_cbuf() && !insn.Bits(62, 2) : insn.words[1] <= 255;
+ const auto load_shift = [&] { if (constant) materialize(2, 0, 251, false, false); };
const bool right = insn.Bits(76, 1) != 0, high_result = insn.Bits(80, 1) != 0;
// SHF.R.U32 of the low word with a zero high word: a plain logical right shift.
if (right && !high_result && high == 255 && insn.Bits(73, 2) == 3 && shift != ~0U &&
- insn.words[1] <= 255 &&
+ shift_operand &&
(insn.words[2] & ~(0x000118ffU | (opcode == 0xc19 ? 0x08000000U : 0U))) == 0x00000600U) {
+ load_shift();
emit(base(0x5c28000000000000ULL) | (u64{low} << 8) | (u64{shift} << 20));
break;
}
- if (insn.words[1] > 255 || insn.Bits(73, 2) != 3 || shift == ~0U ||
+ if (!shift_operand || insn.Bits(73, 2) != 3 || shift == ~0U ||
(insn.words[2] & ~(0x000118ffU | (opcode == 0xc19 ? 0x08000000U : 0U))) != 0x00000600U ||
(right ? (!high_result || low != 255) : (high_result || high != 255)))
return fail(index, "native register shift mode");
+ load_shift();
emit(base(right ? 0x5cf8000000000000ULL : 0x5bf8000000000000ULL) |
(u64{right ? high : 255U} << 8) | (u64{shift} << 20) |
(u64{right ? 255U : low} << 39) | (u64{insn.Bits(75, 1)} << 50));
@@ -1334,6 +1366,14 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
return fail(index, "native fragment discard mode");
emit(0xe33000000000000fULL | predicate);
break;
+ case 0x311: { // F2I.FTZ.U64.TRUNC, register source, destination pair
+ if (insn.words[2] != 0x0021d800 || (insn.words[1] & ~0xffU) || insn.Bits(24, 8) ||
+ (dest & 1) || dest >= 246)
+ return fail(index, "native unsigned 64-bit float conversion mode");
+ emit(base(0x5cb0000000000000ULL) | (u64{3} << 8) | (u64{2} << 10) |
+ (u64{insn.Bits(32, 8)} << 20) | (u64{3} << 39) | (u64{1} << 44));
+ 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;
@@ -1955,7 +1995,45 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
case 0x20b: case 0x80b: case 0xa0b: // FSETP
case 0x20c: case 0x80c: case 0xa0c: case 0xc0c: { // ISETP
const bool floating = (opcode & 0x1ff) == 0xb;
- if (!floating && insn.Bits(72, 1)) return fail(index, "native extended integer comparison");
+ if (!floating && insn.Bits(72, 1)) {
+ // ISETP.EX: the high words, chained to the low words' predicate in bits 68-71.
+ const u32 comparison = insn.Bits(76, 3), low_comparison = insn.Bits(68, 4);
+ if (insn.Bits(74, 2) || insn.Bits(84, 3) != 7 || insn.Bits(87, 4) != 7)
+ return fail(index, "native extended integer comparison");
+ if (comparison == 1 || comparison == 3 || comparison == 4 || comparison == 6) {
+ // Unsigned LT/LE/GT/GE: (a < b, or > for GT/GE) || (a == b && low predicate),
+ // built from 0/~0 ISET masks, then one ISETP.NE into the output predicate.
+ const u32 a = insn.Bits(24, 8), b = insn.Bits(32, 8);
+ if (opcode != 0x20c || insn.Bits(73, 1) || insn.Bits(64, 4) || insn.Bits(80, 1) ||
+ (insn.words[1] & ~0xffU))
+ return fail(index, "native extended ordered comparison mode");
+ const auto set_mask = [&](u32 target, u32 operation, u32 lhs, u32 rhs) {
+ emit(0x5b50000000000000ULL | predicate | target | (u64{lhs} << 8) |
+ (u64{rhs} << 20) | (u64{7} << 39) | (u64{operation} << 49));
+ };
+ if ((low_comparison & 7) == 7) {
+ mov_imm(252, (low_comparison & 8) ? 0U : ~0U);
+ } else {
+ emit(0x38e8000000000000ULL | predicate | 252 | (u64{255} << 8) |
+ (u64{1} << (20 + (low_comparison & 7))));
+ set_mask(252, (low_comparison & 8) ? 2U : 5U, 252, 255);
+ }
+ set_mask(250, comparison == 1 || comparison == 3 ? 1U : 4U, a, b);
+ set_mask(251, 2, a, b);
+ emit(0x5c40000000000000ULL | predicate | 251 | (u64{251} << 8) | (u64{252} << 20) |
+ (u64{7} << 48));
+ emit(0x5c40000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{251} << 20) |
+ (u64{1} << 41) | (u64{7} << 48));
+ emit(0x5b60000000000000ULL | predicate | 7 | (u64{insn.Bits(81, 3)} << 3) |
+ (u64{250} << 8) | (u64{255} << 20) | (u64{7} << 39) | (u64{5} << 49));
+ break;
+ }
+ // EQ/NE: the high comparison ANDed (EQ) or ORed (NE) with that predicate.
+ if (comparison != 2 && comparison != 5)
+ return fail(index, "native extended integer comparison");
+ insn.words[2] = (insn.words[2] & ~(0x100U | 0xc00U | 0x07800000U)) |
+ (insn.Bits(68, 4) << 23) | (comparison == 5 ? 0x400U : 0U);
+ }
const u32 a = operand(0, floating), b = operand(1, floating);
const u32 combine = insn.Bits(74, 2);
if (combine > 2) return fail(index, "native predicate combination");
@@ -2003,15 +2081,19 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
}
case 0x217: case 0x817: case 0xa17: case 0xc17: { // IMNMX register, immediate, cbuf or uniform operand.
if ((insn.words[2] & ~(0x04000200U | (opcode == 0xc17 ? 0x08000000U : 0U))) != 0x03800000 ||
- (opcode != 0xa17 && insn.words[1] > (opcode == 0x817 ? 0x7ffffU : 255U)) ||
+ (opcode == 0x817 && insn.words[1] > 0x7ffffU && insn.words[1] < 0xfff80000U) ||
+ (opcode != 0xa17 && opcode != 0x817 && insn.words[1] > 255U) ||
(opcode == 0xa17 && (!valid_cbuf() || insn.Bits(62, 2))))
return fail(index, "native integer min/max mode");
+ // An immediate is a signed 20-bit value: 19 bits at 20, the sign in bit 56.
const u32 source = opcode == 0xc17 ? uniform_register(insn.words[1])
: opcode == 0xa17 ? operand(1, false)
+ : opcode == 0x817 ? insn.words[1] & 0x7ffffU
: insn.words[1];
if (source == ~0U) return fail(index, "native uniform min/max operand");
emit(base(opcode == 0x817 ? 0x3820000000000000ULL : 0x5c20000000000000ULL) |
(u64{insn.Bits(24, 8)} << 8) | (u64{source} << 20) |
+ (u64{opcode == 0x817 ? insn.words[1] >> 31 : 0U} << 56) |
(u64{insn.Bits(87, 4)} << 39) | (u64{insn.Bits(73, 1)} << 48));
break;
}
@@ -2041,6 +2123,20 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
break;
}
case 0xa11: { // LEA with constant base and carry predicate.
+ if ((insn.words[2] & ~(0x07800000U | 0x0000f800U)) == 0x000f06ffU && !insn.Bits(62, 2) &&
+ insn.Bits(75, 5) != 0) {
+ // LEA.HI.X.SX32, no high source: (sign-extended Ra << shift) >> 32, plus the
+ // constant high word and the carry predicate (bits 87-90).
+ const u32 b = operand(1, false);
+ emit(0x3828000000000000ULL | predicate | 250 | (u64{insn.Bits(24, 8)} << 8) |
+ (u64{32 - insn.Bits(75, 5)} << 20) | (u64{1} << 48));
+ mov_imm(252, 1);
+ emit(0x5ca0000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{255} << 20) |
+ (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42));
+ emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{b} << 20));
+ emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{252} << 20));
+ break;
+ }
if (insn.Bits(72, 1) || insn.Bits(74, 1) || insn.Bits(80, 1) ||
insn.Bits(81, 3) == 7 || insn.Bits(62, 2))
return fail(index, "native constant LEA carry mode");
@@ -2054,8 +2150,9 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
case 0x211: case 0x811: { // LEA low word, no carry result
if (insn.Bits(80, 1)) {
// LEA.HI Rd, Ra, Rb (or an immediate, 0x811), RZ, s: Rb + high word of (Ra << s);
- // bit 73 sign-extends Ra.
- if (insn.Bits(72, 1) || insn.Bits(74, 1) || insn.Bits(81, 3) != 7 ||
+ // bit 73 sign-extends Ra and .X (bit 74) adds the carry predicate in bits 87-90.
+ const bool extended = insn.Bits(74, 1) != 0;
+ if (insn.Bits(72, 1) || insn.Bits(81, 3) != 7 ||
insn.Bits(64, 8) != 255 || (opcode == 0x211 && insn.Bits(62, 2)))
return fail(index, "native high effective address mode");
u32 b = insn.Bits(32, 8);
@@ -2064,10 +2161,16 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
if (b == 251) mov_imm(251, insn.words[1]);
}
const u64 shift = (u64{32 - insn.Bits(75, 5)} << 20) | (u64{insn.Bits(73, 1)} << 48);
- if (b == 255) {
+ if (b == 255 && !extended) {
emit(base(0x3828000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | shift);
} else {
emit(0x3828000000000000ULL | predicate | 250 | (u64{insn.Bits(24, 8)} << 8) | shift);
+ if (extended) {
+ mov_imm(252, 1);
+ emit(0x5ca0000000000000ULL | predicate | 252 | (u64{252} << 8) | (u64{255} << 20) |
+ (u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42));
+ emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{252} << 20));
+ }
emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{b} << 20));
}
break;
@@ -2130,9 +2233,10 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
emit(base(0x5c10000000000000ULL) | (u64{250} << 8) | (u64{251} << 20));
break;
}
- if (opcode == 0xa10 && insn.Bits(81, 3) != 7) {
+ if ((opcode == 0xa10 || opcode == 0xc10) && insn.Bits(81, 3) != 7) { // cbuf or uniform b
if (insn.Bits(64, 8) != 255 || insn.Bits(73, 4) || insn.Bits(84, 7) != 0x7f ||
- insn.Bits(77, 4) != 15 || insn.Bits(63, 1) || insn.Bits(72, 1))
+ insn.Bits(77, 4) != 15 || insn.Bits(63, 1) || insn.Bits(72, 1) ||
+ (opcode == 0xc10 && (insn.words[1] & ~0x3fU)))
return fail(index, "native constant add carry mode");
const u32 b = operand(1, false);
emit(base(0x5c10000000000000ULL) | (u64{insn.Bits(24, 8)} << 8) | (u64{b} << 20));
@@ -2140,13 +2244,16 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
(dest << 8) | (u64{b} << 20) | (u64{7} << 39) | (u64{1} << 49));
break;
}
- if (opcode == 0xa10 && insn.Bits(74, 1)) {
- if (insn.Bits(24, 8) != 255 || (insn.words[2] & ~0x07800000U) != 0x007fe4ffU)
+ if ((opcode == 0xa10 || opcode == 0xc10) && insn.Bits(74, 1)) { // IADD3.X Ra + cbuf or UR + carry predicate
+ if ((insn.words[2] & ~(0x07800000U | (opcode == 0xc10 ? 0x08000000U : 0U))) != 0x007fe4ffU ||
+ (opcode == 0xc10 && (insn.words[1] & ~0x3fU)))
return fail(index, "native extended constant add mode");
const u32 b = operand(1, false);
mov_imm(250, 1);
emit(0x5ca0000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{255} << 20) |
(u64{insn.Bits(87, 3)} << 39) | (u64{insn.Bits(90, 1)} << 42));
+ if (insn.Bits(24, 8) != 255)
+ emit(0x5c10000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{insn.Bits(24, 8)} << 20));
emit(base(0x5c10000000000000ULL) | (u64{b} << 8) | (u64{250} << 20));
break;
}