aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/macro_turing.h
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 22:26:16 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 22:26:16 -0400
commit51207c73c889dbdc4a586a5da23314f6d33e4eb9 (patch)
tree4ae96cb41d662499cc20f21fd239bad75a0eb64d /src/video_core/macro_turing.h
parentbf1de8f40130af5d3b490ee40dcc2fb20d460a14 (diff)
Switch 2: Turing macros, native compute, Ampere shader lowering, native tiling
From changes.patch: the TU104/GA10x MME interpreter (shared macro context for the process's channels, also on the compute class), native compute with QMD 3.0 dependency scheduling, the Ampere-to-Maxwell shader lowering, native vertex stream sizes, and the console's GOB swizzle for textures, DMA copies and the host unswizzle shaders. Switch 2 channels turn all of it on at init. The lowering is widened for title 4 (all 32 of its programs lower): general TEX (any dimension/LOD/mask, operand split 1+1/2+1/2+2/4+n, any but consistent descriptor buffer), cube/array, SULD channel masks, 128-bit STG, indexed IPA, VOTE, SHFL with any immediate or predicate output, R2P/P2R byte select, uniform ULOP3/ULEA/UBREV/UFLO.SH/UISETP signed/UMOV URZ, IMNMX cbuf, IADD3 and FFMA uniform operands, FMUL /4 /8 x8, and BSSY/BSYNC/BREAK/WARPSYNC as no-ops. The capture stops at unmapped memory. EDEN_NX2_SHADER_DUMP dumps every native program; tools in C:\dev\nx2work test the lowering offline.
Diffstat (limited to 'src/video_core/macro_turing.h')
-rw-r--r--src/video_core/macro_turing.h213
1 files changed, 213 insertions, 0 deletions
diff --git a/src/video_core/macro_turing.h b/src/video_core/macro_turing.h
new file mode 100644
index 0000000000..fe82ce4a4f
--- /dev/null
+++ b/src/video_core/macro_turing.h
@@ -0,0 +1,213 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Instruction semantics adapted from Mesa mme_tu104_sim.c and mme_tu104.c:
+// Copyright © 2022 Collabora Ltd. SPDX-License-Identifier: MIT
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <cstdint>
+#include <span>
+#include <string_view>
+
+namespace Tegra {
+
+// TU104/GA10x MME has two ALUs and two outputs per 96-bit instruction.
+class TuringMacroInterpreter {
+public:
+ using Word = std::uint32_t;
+ struct Result {
+ bool success;
+ Word pc;
+ std::size_t consumed;
+ std::string_view error;
+ };
+
+ template <typename Read, typename Emit>
+ Result Execute(std::span<const Word> code, Word start,
+ std::span<const Word> parameters, Read read, Emit emit) {
+ std::array<Word, 24> regs{};
+ Word initialized{}, carry{}, method{}, increment{};
+ Word pc = start, loop_count{}, loop_start{}, loop_end{};
+ std::size_t consumed{};
+ bool end_next{}, stopped{};
+ std::string_view error;
+ const auto fail = [&](std::string_view message) { if (error.empty()) error = message; };
+ for (Word steps = 0; steps < 1000000; ++steps) {
+ if (static_cast<std::size_t>(pc) * 3 + 2 >= code.size()) {
+ fail("instruction outside uploaded RAM");
+ break;
+ }
+ const std::array<Word, 3> bits{code[pc * 3 + 2], code[pc * 3 + 1], code[pc * 3]};
+ const auto field = [&](Word low, Word count) {
+ std::uint64_t value = bits[low / 32];
+ if (low / 32 < 2) value |= std::uint64_t{bits[low / 32 + 1]} << 32;
+ return Word((value >> (low % 32)) & ((std::uint64_t{1} << count) - 1));
+ };
+ const std::array<Word, 2> imm{field(30, 16), field(66, 16)};
+ const std::array<Word, 2> op{field(10, 5), field(46, 5)};
+ const std::array<Word, 2> dst{field(15, 5), field(51, 5)};
+ const std::array<Word, 2> src_a{field(20, 5), field(56, 5)};
+ const std::array<Word, 2> src_b{field(25, 5), field(61, 5)};
+ const std::array<Word, 2> out_method{field(82, 3), field(89, 3)};
+ const std::array<Word, 2> out_emit{field(85, 4), field(92, 4)};
+ const Word pred_reg = field(5, 5), pred_mode = field(1, 4);
+ std::array<Word, 2> load{}, alu{};
+ for (Word i = 0; i < 2; ++i) {
+ const Word reg = 28 + i, out = 3 + i;
+ const bool needed = pred_reg == reg || src_a[0] == reg || src_a[1] == reg ||
+ src_b[0] == reg || src_b[1] == reg || out_method[0] == out ||
+ out_method[1] == out || out_emit[0] == out || out_emit[1] == out;
+ if (needed) {
+ if (consumed >= parameters.size()) { fail("parameter FIFO underflow"); break; }
+ load[i] = parameters[consumed++];
+ }
+ }
+ // Both ALUs read the register file before either destination is
+ // committed. Carry chaining remains explicit between the lanes.
+ const auto input_regs = regs;
+ const Word input_initialized = initialized;
+ const auto get = [&](Word reg, Word lane) -> Word {
+ if (reg < 24) {
+ if (!(input_initialized & (1U << reg))) fail("uninitialized register read");
+ return input_regs[reg];
+ }
+ switch (reg) {
+ case 24: return 0;
+ case 25: return Word(std::int32_t(std::int16_t(imm[lane])));
+ case 26: return Word(std::int32_t(std::int16_t(imm[1 - lane])));
+ case 27: return (imm[0] << 16) | imm[1];
+ case 28: return load[0];
+ case 29: return load[1];
+ default: fail("invalid source register"); return 0;
+ }
+ };
+ constexpr std::array<std::string_view, 16> predicates{
+ "UUUU", "TTTT", "FFFF", "TTUU", "FFUU", "TFUU", "TUUU", "FUUU",
+ "UUTT", "UUTF", "UUTU", "UUFT", "UUFF", "UUFU", "UUUT", "UUUF"};
+ Word mask = 15;
+ if (pred_mode) {
+ const char skipped = get(pred_reg, 0) ? 'T' : 'F';
+ mask = 0;
+ for (Word i = 0; i < 4; ++i)
+ if (predicates[pred_mode][i] != skipped) mask |= 1U << i;
+ }
+ Word next_pc = pc + 1;
+ bool branch_cancels_end{};
+ for (Word lane = 0; lane < 2 && error.empty(); ++lane) {
+ if (!(mask & (1U << lane))) continue;
+ const Word x = get(src_a[lane], lane), y = get(src_b[lane], lane);
+ Word value{};
+ const auto condition = [&](Word comparison) {
+ switch (comparison) {
+ case 17: case 25: return std::int32_t(x) < std::int32_t(y);
+ case 18: case 26: return x < y;
+ case 19: case 27: return std::int32_t(x) <= std::int32_t(y);
+ case 20: case 28: return x <= y;
+ default: return x == y;
+ }
+ };
+ switch (op[lane]) {
+ case 0: value = x + y; carry = value < x; break;
+ case 1: value = x + y + carry; break;
+ case 2: value = x - y; carry = value > x; break;
+ case 3: value = x - y - carry; break;
+ case 4: case 6: {
+ const std::uint64_t a = op[lane] == 4 ? std::uint64_t(std::int64_t(std::int32_t(x))) : x;
+ const std::uint64_t b = op[lane] == 4 ? std::uint64_t(std::int64_t(std::int32_t(y))) : y;
+ const auto product = a * b;
+ value = Word(product); carry = Word(product >> 32); break;
+ }
+ case 5: value = carry; break;
+ case 7:
+ // EXTENDED: x = 0x1000 is the load barrier (Mesa mme_tu104_load_barrier),
+ // nothing to do in a sequential interpreter.
+ if (x != 0x1000) fail("unsupported extended operation");
+ break;
+ case 8: value = Word(std::countl_zero(x)); break;
+ case 9: value = x << (y & 31); break;
+ case 10: value = x >> (y & 31); break;
+ case 11: value = Word(std::int32_t(x) >> (y & 31)); break;
+ case 12: value = x & y; break;
+ case 13: value = ~(x & y); break;
+ case 14: value = x | y; break;
+ case 15: value = x ^ y; break;
+ case 16: {
+ const Word dest = (imm[lane] >> 10) & 63, count = (imm[lane] >> 5) & 31;
+ const Word source = imm[lane] & 31;
+ if (dest >= 32) { fail("invalid merge destination"); break; }
+ const Word bits_mask = (1U << count) - 1;
+ value = (x & ~(bits_mask << dest)) | (((y >> source) & bits_mask) << dest);
+ break;
+ }
+ case 17: case 18: case 19: case 20: case 21:
+ value = condition(op[lane]) ? ~0U : 0; break;
+ case 22: value = read((x + y) & 0xffff); break;
+ case 23:
+ if (loop_count || imm[lane] < 2) { fail("invalid loop"); break; }
+ // The native SDK emits zero-count loops around FIFO reads.
+ // Unlike Mesa's test simulator, these must skip the body.
+ if (x == 0) { next_pc = pc + imm[lane]; break; }
+ loop_count = x - 1; loop_start = pc; loop_end = pc + imm[lane] - 1;
+ break;
+ case 24: next_pc = pc + (imm[lane] & 0x7fff); break;
+ case 25: case 26: case 27: case 28: case 29:
+ if (condition(op[lane]) == bool(imm[lane] & 0x8000)) {
+ const auto offset = std::int32_t(imm[lane] << 19) >> 19;
+ if (offset == -4096) stopped = true;
+ else {
+ next_pc = Word(std::int32_t(pc) + offset);
+ // Native back edges use bit 14 to suppress the
+ // fallthrough termination when the branch is taken.
+ branch_cancels_end = (imm[lane] & 0x4000) != 0;
+ }
+ }
+ break;
+ case 30:
+ if (x >= dram.size()) { fail("data RAM read out of bounds"); break; }
+ value = dram[x]; break;
+ case 31:
+ if (x >= dram.size()) { fail("data RAM write out of bounds"); break; }
+ dram[x] = y; break;
+ }
+ alu[lane] = value;
+ if (dst[lane] < 24) { regs[dst[lane]] = value; initialized |= 1U << dst[lane]; }
+ else if (dst[lane] != 24) fail("invalid destination register");
+ }
+ const auto output = [&](Word selector) -> Word {
+ switch (selector) {
+ case 1: return alu[0]; case 2: return alu[1];
+ case 3: return load[0]; case 4: return load[1];
+ case 5: return imm[0]; case 6: return imm[1];
+ case 8: return imm[0] >> 12; case 9: return imm[1] >> 12;
+ case 10: return (imm[0] << 16) | imm[1];
+ default: fail("invalid output selector"); return 0;
+ }
+ };
+ for (Word lane = 0; lane < 2 && error.empty(); ++lane) {
+ if (!(mask & (4U << lane))) continue;
+ if (out_method[lane]) {
+ const Word address = output(out_method[lane]);
+ method = address & 0xfff; increment = (address >> 12) & 15;
+ }
+ if (out_emit[lane]) {
+ const Word value = output(out_emit[lane]);
+ if (error.empty() && !emit(method, value)) fail("unsupported emitted method");
+ method += increment;
+ }
+ }
+ if (!error.empty()) break;
+ if (end_next || stopped) return {true, pc, consumed, {}};
+ end_next = field(0, 1) != 0 && !branch_cancels_end;
+ if (loop_count && pc == loop_end) { --loop_count; next_pc = loop_start + 1; }
+ pc = next_pc;
+ }
+ if (error.empty()) error = "instruction limit exceeded";
+ return {false, pc, consumed, error};
+ }
+
+ std::array<Word, 0xc00> dram{};
+};
+
+} // namespace Tegra