aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/shader_recompiler/frontend/ampere/lower_to_maxwell.h151
1 files changed, 108 insertions, 43 deletions
diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
index 031f7fac75..2d994ad366 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 == 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;
}
const auto address_of = [](u32 index) { return 0x50U + index * 8 + ((index + 1) / 3) * 8; };
const auto fail = [&](u32 index, std::string_view reason) {
@@ -99,7 +99,8 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
// Scratch values and emulated uniform state occupy reserved high GPRs.
// Reject native accesses that could alias that state.
for (u32 bit : {16U, 24U, 32U, 64U}) {
- if (bit == 32 && insn.Bits(9, 3) != 1 && opcode != 0xb60 && opcode != 0xb66) continue;
+ if (bit == 32 && insn.Bits(9, 3) != 1 && opcode != 0xb60 && opcode != 0xb66 && opcode != 0x361 &&
+ opcode != 0xf60) continue;
const u32 reg = insn.Bits(bit, 8);
if (reg >= (uses_uniform_bank ? 216U : 246U) && reg < 255) return fail(index, "native program uses lowering scratch registers");
}
@@ -163,6 +164,13 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
};
std::string_view operand_error;
const auto emit = [&](u64 word) { result.instructions.push_back(word); };
+ // Bound Maxwell texture instructions share one descriptor constant buffer per program.
+ const auto bind_texture_buffer = [&](u32 binding) {
+ if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding)
+ return false;
+ result.required_texture_buffer = binding;
+ return true;
+ };
// P(guard slot) while a uniform guard is swapped in; P6 for lowerings that use it as
// scratch (R2UR, MATCH, HFMA2.RELU), which save and restore it with the defaults below.
const u32 guard_mask = guard_slot < 7 ? 1U << guard_slot : 0U;
@@ -759,16 +767,18 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
(u64{insn.Bits(64, 8)} << 20) | (u64{maxwell} << 28));
break;
}
- case 0xb6c: { // TXD 2D or ARRAY_2D, four channels; the two destination pairs anywhere.
+ case 0xb6c: { // TXD 2D or ARRAY_2D, any channels; the two destination pairs anywhere.
const u32 binding = insn.Bits(54, 5), offset = insn.Bits(40, 14);
const u32 coordinates = insn.Bits(24, 8), derivatives = insn.Bits(32, 8);
// Ampere dimensions: 1 = 2D, 5 = ARRAY_2D; Maxwell TXD's are 2 and 3. The coordinates
// (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
- if ((insn.words[2] & ~0xffU) != 0x001e0f00 || insn.Bits(59, 2) ||
+ const u32 mask = insn.Bits(72, 4), channels = static_cast<u32>(std::popcount(mask));
+ if ((insn.words[2] & ~(0xfffU | (1U << 26))) != 0x001e0000 || mask == 0 || insn.Bits(59, 2) ||
(dimension != 1 && dimension != 5) || binding >= 18 || offset > 0x1fff ||
- (dest & 1) || dest >= 248 || (high & 1) || (high >= 248 && high != 255) ||
+ (channels > 1 && (dest & 1)) || dest >= 248 ||
+ (channels > 2 ? (high & 1) || high >= 248 : high != 255) ||
coordinates >= 248 || derivatives >= 247)
return fail(index, "native gradient texture mode");
if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding)
@@ -776,16 +786,15 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
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;
+ 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) {
+ emit(0x5c98000000000000ULL | predicate | (i < 2 ? dest + i : high + i - 2) |
+ (u64{15} << 39) | (u64{248 + i} << 20));
}
}
break;
@@ -944,14 +953,20 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
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 ||
+ const bool strong = insn.words[2] == 0x0010af00; // .STRONG.GPU: fenced on both sides
+ if ((insn.words[2] != 0x00100f00 && !strong) || 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");
- result.required_texture_buffer = binding;
+ // A second descriptor buffer: SUST.B with the handle, loaded from that buffer, in R250.
+ const bool bindless = !bind_texture_buffer(binding);
+ if (bindless) {
+ emit(0x4c98000000070000ULL | 250 | (u64{15} << 39) | (u64{offset} << 20) | (u64{binding} << 34));
+ }
+ if (strong) emit(0xef98000000000000ULL | predicate | (u64{1} << 8)); // MEMBAR.GL
emit(0xeb20000000000000ULL | predicate | data | (u64{coordinate} << 8) |
- (u64{15} << 20) | (u64{dimension} << 33) | (u64{offset} << 36) | (u64{1} << 51));
+ (u64{15} << 20) | (u64{dimension} << 33) |
+ (bindless ? u64{250} << 39 : (u64{offset} << 36) | (u64{1} << 51)));
+ if (strong) emit(0xef98000000000000ULL | predicate | (u64{1} << 8));
break;
}
case 0x301: // BREV register.
@@ -1363,17 +1378,28 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
}
break;
}
- case 0xb6f: { // TXQ texture dimensions, two packed scalar outputs.
- if (insn.Bits(59, 5) || insn.Bits(64, 8) != 255 || insn.Bits(72, 4) == 0 || insn.Bits(72, 4) > 3 ||
- insn.Bits(76, 14) || insn.Bits(91, 1) || insn.Bits(54, 5) != 2 ||
- insn.Bits(40, 14) > 0x1fff || (insn.Bits(72, 4) == 3 && (dest & 1)) || dest >= 254)
+ case 0xb6f: { // TXQ texture dimensions (mask 1-3) or mip level count (mask 8).
+ const u32 mask = insn.Bits(72, 4);
+ if (insn.Bits(59, 5) || insn.Bits(64, 8) != 255 || (mask == 0 || (mask > 3 && mask != 8)) ||
+ insn.Bits(76, 14) || insn.Bits(91, 1) || insn.Bits(54, 5) >= 18 ||
+ insn.Bits(40, 14) > 0x1fff || (mask == 3 && (dest & 1)) || dest >= 254)
return fail(index, "native texture dimension query mode");
- result.required_texture_buffer = 2;
+ if (!bind_texture_buffer(insn.Bits(54, 5)))
+ return fail(index, "native texture descriptor buffer conflict");
emit(0xdf48000000000000ULL | predicate | dest | (u64{insn.Bits(24, 8)} << 8) |
- (u64{1} << 22) | (u64{insn.Bits(72, 4)} << 31) | (u64{insn.Bits(40, 14)} << 36) |
+ (u64{1} << 22) | (u64{mask} << 31) | (u64{insn.Bits(40, 14)} << 36) |
(u64{insn.Bits(90, 1)} << 49));
break;
}
+ case 0x370: { // TXQ.B dimensions or mip count: handle in Ra, mip level in Ra+1.
+ const u32 mask = insn.Bits(72, 4), source = insn.Bits(24, 8);
+ if (insn.words[1] != 0x08000000 || insn.Bits(64, 8) != 255 || (mask == 0 || (mask > 3 && mask != 8)) ||
+ (insn.words[2] & ~0x04000f00U) != 255 || source > 245 || (mask == 3 && (dest & 1)) || dest >= 254)
+ return fail(index, "native bindless texture dimension query mode");
+ emit(0xdf50000000000000ULL | predicate | dest | (u64{source} << 8) |
+ (u64{1} << 22) | (u64{mask} << 31) | (u64{insn.Bits(90, 1)} << 49));
+ break;
+ }
case 0xb66: { // TLD.SCR.LZ, direct texel fetch
const u32 mask = insn.Bits(72, 4);
const u32 dimension = insn.Bits(61, 3);
@@ -1402,9 +1428,25 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
return fail(index, "native texel fetch mode or descriptor binding");
}
const u32 binding = insn.Bits(54, 5);
- if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding)
- return fail(index, "native texture descriptor buffer conflict");
- result.required_texture_buffer = binding;
+ if (!bind_texture_buffer(binding)) {
+ // A second descriptor buffer: TLD.B (LZ) with the coordinates in R250.. (array
+ // index or Z after Ra) and the handle, loaded from that buffer, in R253.
+ const auto copy = [&](u32 target, u32 source) {
+ emit(0x5c98000000000000ULL | predicate | target | (u64{15} << 39) | (u64{source} << 20));
+ };
+ copy(250, source_a);
+ if (array || volume) copy(251, source_a + 1);
+ if (dimension != 0) copy(array || volume ? 252 : 251, source_b);
+ emit(0x4c98000000070000ULL | 253 | (u64{15} << 39) | (u64{insn.Bits(40, 14)} << 20) |
+ (u64{binding} << 34));
+ emit(0xdd00000000000000ULL | predicate | 250 | (u64{250} << 8) | (u64{253} << 20) |
+ (u64{array ? 3U : volume ? 4U : dimension == 0 ? 0U : 2U} << 28) | (u64{mask} << 31) |
+ (u64{insn.Bits(90, 1)} << 49) | (u64{7} << 51));
+ const u32 components = static_cast<u32>(std::popcount(mask));
+ for (u32 i = 0; i < components; ++i)
+ copy(i < 2 || !split ? static_cast<u32>(dest) + i : second_dest + (i - 2), 250 + i);
+ break;
+ }
if (array) {
emit(0x5c98000000000000ULL | predicate | 250 | (u64{15} << 39) | (u64{source_a + 1} << 20));
emit(0x5c98000000000000ULL | predicate | 251 | (u64{15} << 39) | (u64{source_b} << 20));
@@ -1475,7 +1517,10 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
}
break;
}
+ case 0x361: // TEX with the descriptor handle in a register (after the coordinates)
+ case 0xf60: // TEX with the descriptor handle in a uniform register
case 0xb60: { // TEX, scalar 2D implicit-LOD sample
+ const bool register_handle = opcode == 0x361, uniform_handle = opcode == 0xf60;
const u32 mask = insn.Bits(72, 4);
const u32 second_dest = insn.Bits(64, 8);
constexpr std::array<u32, 8> rg_masks{1, 2, 4, 8, 3, 9, 10, 12};
@@ -1485,12 +1530,13 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
const bool depth_compare = insn.Bits(78, 1);
const u32 dimension = insn.Bits(61, 3), lod = insn.Bits(87, 3);
const u32 source_a = insn.Bits(24, 8), source_b = insn.Bits(32, 8);
- if (dimension == 5 && depth_compare && lod == 0 && !split && mask == 1) {
+ if (dimension == 5 && depth_compare && lod == 0 && !split && mask == 1 && !register_handle &&
+ !uniform_handle) {
if (!insn.Bits(60, 1) || insn.Bits(76, 2) || insn.Bits(81, 3) != 7 ||
insn.Bits(59, 1) || insn.Bits(40, 14) > 0x1fff || insn.Bits(54, 5) != 2 ||
(source_a & 1) || (source_b & 1) || source_a >= 248 || source_b >= 248 || dest >= 248)
return fail(index, "native array depth comparison sample mode");
- result.required_texture_buffer = 2;
+ if (!bind_texture_buffer(2)) return fail(index, "native texture descriptor buffer conflict");
for (u32 i = 0; i < 3; ++i) {
const u32 source = i < 2 ? source_a + i : source_b;
emit(0x5c98000000000000ULL | predicate | (250 + i) |
@@ -1510,14 +1556,24 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
constexpr std::array<u32, 8> coordinate_count{1, 2, 3, 3, 2, 3, 0, 4};
constexpr std::array<u32, 8> maxwell_type{0, 2, 4, 6, 1, 3, 0, 7};
const bool offsets = insn.Bits(76, 1) != 0;
- if (dimension == 6 || insn.Bits(77, 1) || insn.Bits(81, 3) != 7 || insn.Bits(59, 1) ||
- insn.Bits(40, 14) > 0x1fff || insn.Bits(54, 5) >= 18 || (lod > 3 && lod != 6 && lod != 7) ||
- (depth_compare && mask != 1) || mask == 0)
+ // Handle forms: 0x361 has no descriptor fields (bit 59 set), 0xf60 names a UR
+ // in bits 40-45 (bit 91 set) and is only known with packed operands.
+ const u32 handle_register = uniform_handle ? uniform_register(insn.Bits(40, 6)) : ~0U;
+ if (register_handle ? !insn.Bits(59, 1) || insn.Bits(40, 19)
+ : uniform_handle ? !insn.Bits(60, 1) || insn.Bits(59, 1) || insn.Bits(46, 13) ||
+ !insn.Bits(91, 1) || handle_register == ~0U || handle_register == 255
+ : insn.Bits(59, 1) || insn.Bits(40, 14) > 0x1fff || insn.Bits(54, 5) >= 18)
+ return false;
+ if (dimension == 6 || insn.Bits(77, 1) || insn.Bits(81, 3) != 7 ||
+ (lod > 3 && lod != 6 && lod != 7) || (depth_compare && mask != 1) || mask == 0)
return false;
const u32 coordinates = coordinate_count[dimension];
const bool has_lod = lod == 2 || lod == 3 || lod == 6 || lod == 7;
- const u32 count = coordinates + (has_lod ? 1 : 0) + (offsets ? 1 : 0) + (depth_compare ? 1 : 0);
- const u32 in_a = count <= 4 ? (count + 1) / 2 : 4;
+ const u32 count = coordinates + (register_handle ? 1 : 0) + (has_lod ? 1 : 0) +
+ (offsets ? 1 : 0) + (depth_compare ? 1 : 0);
+ // Unpacked 0x361 (bit 60 clear): Ra holds the coordinates, Rb the handle and the rest.
+ const u32 in_a = register_handle && !insn.Bits(60, 1) ? coordinates
+ : count <= 4 ? (count + 1) / 2 : 4;
const auto value = [&](u32 i) -> u32 {
if (i < in_a) return source_a == 255 ? 255U : source_a + i;
return source_b == 255 ? 255U : source_b + (i - in_a);
@@ -1525,25 +1581,34 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
if ((source_a != 255 && source_a + in_a > 246) ||
(count > in_a && source_b != 255 && source_b + (count - in_a) > 246))
return false;
- const u32 binding = insn.Bits(54, 5);
- if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding)
- return false;
- result.required_texture_buffer = binding;
+ // Handle forms and a second descriptor buffer are read bindless: TEX.B takes the
+ // handle (the register's, or loaded from that buffer) as its first meta register.
+ const u32 binding = insn.Bits(54, 5), offset = insn.Bits(40, 14);
+ const bool bindless = register_handle || uniform_handle || !bind_texture_buffer(binding);
const auto copy = [&](u32 target, u32 source) {
emit(0x5c98000000000000ULL | predicate | target | (u64{15} << 39) | (u64{source} << 20));
};
- // Coordinates to R250.., then LOD/bias, offsets and reference to R246...
+ // Coordinates to R250.., then the handle, LOD/bias, offsets and reference to R246...
for (u32 i = 0; i < coordinates; ++i) copy(250 + i, value(i));
u32 meta = 246;
u32 next = coordinates;
+ // The handle is copied unpredicated, so it is the same value on every path.
+ if (register_handle || uniform_handle) {
+ emit(0x5c98000000070000ULL | meta++ | (u64{15} << 39) |
+ (u64{register_handle ? value(next++) : handle_register} << 20));
+ } else if (bindless) {
+ emit(0x4c98000000070000ULL | meta++ | (u64{15} << 39) | (u64{offset} << 20) |
+ (u64{binding} << 34));
+ }
if (has_lod) copy(meta++, value(next++));
if (offsets) copy(meta++, value(next++));
if (depth_compare) copy(meta++, value(next++));
- emit(0xc000000000000000ULL | predicate | 250 | (u64{250} << 8) |
+ emit((bindless ? 0xde80000000000000ULL : 0xc000000000000000ULL) | predicate | 250 | (u64{250} << 8) |
(u64{meta == 246 ? 255U : 246U} << 20) | (u64{maxwell_type[dimension]} << 28) |
- (u64{mask} << 31) | (u64{insn.Bits(40, 14)} << 36) |
- (u64{insn.Bits(90, 1)} << 49) | (u64{depth_compare} << 50) | (u64{7} << 51) |
- (u64{offsets} << 54) | (u64{lod} << 55));
+ (u64{mask} << 31) | (u64{insn.Bits(90, 1)} << 49) | (u64{depth_compare} << 50) |
+ (u64{7} << 51) |
+ (bindless ? (u64{offsets} << 36) | (u64{lod} << 37)
+ : (u64{offset} << 36) | (u64{offsets} << 54) | (u64{lod} << 55)));
const u32 components = static_cast<u32>(std::popcount(mask));
if (insn.Bits(79, 1)) {
// FP16 results: (c0, c1) packed into the first destination, (c2, c3) into
@@ -1587,11 +1652,11 @@ inline LoweredProgram LowerToMaxwell(std::span<const Instruction> code) {
dest == 255 || (mask & (mask - 1) && ((dest & 1) || dest >= 254)) ||
(split && mask == 15 && ((second_dest & 1) || second_dest >= 254)) ||
(depth_compare && (dimension != 1 || split || mask != 1 || (source_a & 1) || source_a >= 249)) ||
- (dimension == 5 && ((source_a & 1) || source_a >= 249))) {
+ (dimension == 5 && ((source_a & 1) || source_a >= 249)) || register_handle || uniform_handle ||
+ !bind_texture_buffer(2)) {
if (!general_tex()) return fail(index, "native texture mode or descriptor binding");
break;
}
- result.required_texture_buffer = 2;
emit((0xd800000000000000ULL & ~(u64{insn.Bits(79, 1)} << 59)) | predicate | dest | (u64{insn.Bits(24, 8)} << 8) |
(u64{insn.Bits(32, 8)} << 20) | (u64{insn.Bits(64, 8)} << 28) |
(u64{insn.Bits(40, 14)} << 36) | (u64{insn.Bits(90, 1)} << 49) |