aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 13:22:59 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 13:22:59 -0400
commitd0c0cb8906ba99bbe013aaa220887b7b8ab31a7a (patch)
tree6e6998122a908ae5739dbcb239e26570a8f82beb /src
parentd6bbbf9f23a7e5b6a6ab11e2696085e0482b6fc4 (diff)
Switch 2: per-channel MME data RAM; one QMD table for all channels
Macro code stays shared by the channels of a process, but each channel now has its own MME data RAM. Title 1's channels keep their own QMD ring base in data word 0x27; with one shared RAM every channel used the last channel's ring, QMDs collided, releases came early or never and the GPU waited on semaphores (4 s timeouts per frame). The QMD scheduler state is one table for all channels: a QMD's dependency counter is state of the QMD, scheduled and decremented from different channels. Diagnostics: EDEN_NX2_QMD_TRACE (scheduler loads, counts, launches) and EDEN_NX2_MME_DUMP (compute macro code, calls, data word 0x27 changes).
Diffstat (limited to 'src')
-rw-r--r--src/video_core/control/channel_state.cpp1
-rw-r--r--src/video_core/engines/kepler_compute.cpp30
-rw-r--r--src/video_core/engines/kepler_compute.h5
-rw-r--r--src/video_core/engines/maxwell_3d.h3
-rw-r--r--src/video_core/macro.cpp10
-rw-r--r--src/video_core/macro_turing.h15
-rw-r--r--src/video_core/native_qmd.h18
7 files changed, 73 insertions, 9 deletions
diff --git a/src/video_core/control/channel_state.cpp b/src/video_core/control/channel_state.cpp
index 57d7dc8e57..8e0b6c1b9e 100644
--- a/src/video_core/control/channel_state.cpp
+++ b/src/video_core/control/channel_state.cpp
@@ -82,6 +82,7 @@ void ChannelState::Init(Core::System& system, u64 program_id_) {
payload->maxwell_3d.SetNativeTuringMacroMode(true);
payload->kepler_compute.SetNativeMacroMode(&macros->engine, macros->positions,
macros.get());
+ payload->kepler_compute.SetNativeDataRam(&payload->maxwell_3d.GetNativeDataRam());
}
initialized = true;
}
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 4954fe613a..8a3855468f 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -29,6 +29,13 @@ KeplerCompute::KeplerCompute(MemoryManager& memory_manager_)
: memory_manager{memory_manager_}
, upload_state{memory_manager, regs.upload}
{
+ // EDEN_NX2_QMD_TRACE: log the QMD scheduler's loads, dependency counts and launches.
+ if (std::getenv("EDEN_NX2_QMD_TRACE")) {
+ native_qmd.trace = [this](std::string_view event, u64 address, s32 value) {
+ LOG_INFO(HW_GPU, "QMD trace {} {:#x} {} (engine {} as {})", event, address, value,
+ fmt::ptr(this), memory_manager.GetID());
+ };
+ }
execution_mask.reset();
execution_mask[KEPLER_COMPUTE_REG_INDEX(exec_upload)] = true;
execution_mask[KEPLER_COMPUTE_REG_INDEX(data_upload)] = true;
@@ -109,6 +116,7 @@ bool KeplerCompute::ProcessNativeMethod(Core::System& system, u32 method, u32 ar
native_parameters.push_back(argument);
if (!is_last_call) return true;
const u32 entry = ((*native_executing_macro - 0xe00) / 2) % native_macro_positions.size();
+ native_executing_macro_logged = native_executing_macro;
native_executing_macro.reset();
static const u32 watch = [] {
const char* value = std::getenv("EDEN_NX2_SEM_WATCH");
@@ -120,6 +128,22 @@ bool KeplerCompute::ProcessNativeMethod(Core::System& system, u32 method, u32 ar
LOG_INFO(HW_GPU, "Semaphore watch: compute macro start={:#x} parameters={:08x}",
native_macro_positions[entry], fmt::join(native_parameters, ","));
}
+ // EDEN_NX2_MME_DUMP: write the compute macro code once per engine, and log each macro
+ // call with its entry and parameters (diagnostics).
+ if (static const bool mme_dump = std::getenv("EDEN_NX2_MME_DUMP") != nullptr; mme_dump) {
+ if (!native_mme_dumped && native_macro_positions[entry] == 0xda) {
+ native_mme_dumped = true;
+ std::ofstream dump(fmt::format("native-compute-mme-{}.bin", fmt::ptr(this)),
+ std::ios::binary);
+ dump.write(reinterpret_cast<const char*>(native_macro->native_code.data()),
+ sizeof(native_macro->native_code));
+ }
+ LOG_INFO(HW_GPU, "MME call engine {} method {:#x} start={:#x} parameters={:08x}",
+ fmt::ptr(this), *native_executing_macro_logged, native_macro_positions[entry],
+ fmt::join(native_parameters, ","));
+ }
+ if (native_data_ram) native_macro->native_interpreter.active_dram = native_data_ram;
+ const u32 dram0_before = (*native_macro->native_interpreter.active_dram)[0x27];
const auto result = native_macro->native_interpreter.Execute(native_macro->native_code,
native_macro_positions[entry], native_parameters,
[&](u32 address) { return address < native_registers.size() ? native_registers[address] : 0U; },
@@ -132,6 +156,12 @@ bool KeplerCompute::ProcessNativeMethod(Core::System& system, u32 method, u32 ar
CallMethod(system, address, value, true);
return true;
});
+ if (static const bool mme_dump = std::getenv("EDEN_NX2_MME_DUMP") != nullptr;
+ mme_dump && (*native_macro->native_interpreter.active_dram)[0x27] != dram0_before) {
+ LOG_INFO(HW_GPU, "MME data RAM[0x27] {:#x} -> {:#x} by engine {} macro start={:#x}",
+ dram0_before, (*native_macro->native_interpreter.active_dram)[0x27], fmt::ptr(this),
+ native_macro_positions[entry]);
+ }
if (!result.success) {
native_macro->native_execution_failed = true;
if (static const bool diagnostics = std::getenv("EDEN_NATIVE_DRAW_DIAGNOSTICS") != nullptr;
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index dd69211f7b..7cecab378b 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -57,6 +57,8 @@ public:
/// Binds a rasterizer to this engine.
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
+ /// The channel's MME data RAM, shared with its 3D engine.
+ void SetNativeDataRam(std::array<u32, 0xc00>* data_ram) { native_data_ram = data_ram; }
void SetNativeMacroMode(MacroEngine* shared_macro, std::span<u32> positions, NativeMacroContext* context = nullptr) {
if (native_context != context) native_compute_uploaded = false;
native_context = context;
@@ -242,6 +244,9 @@ private:
bool UpscaleInsteadOfNativeProgram();
bool ProcessNativeMethod(Core::System& system, u32 method, u32 argument, bool is_last_call);
NativeQmdScheduler native_qmd;
+ std::array<u32, 0xc00>* native_data_ram{};
+ bool native_mme_dumped{};
+ std::optional<u32> native_executing_macro_logged;
bool native_mode{};
NativeMacroContext* native_context{};
bool native_compute_uploaded{};
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 2a10795a4e..82e716b822 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -3150,6 +3150,8 @@ public:
native_macro_context = std::move(context);
}
MacroEngine& GetNativeMacroEngine() { return native_macro_context ? native_macro_context->engine : macro_engine; }
+ /// This channel's MME data RAM (shared with its compute engine).
+ std::array<u32, 0xc00>& GetNativeDataRam() { return native_data_ram; }
std::span<u32> GetNativeMacroPositions() { return native_macro_context ? std::span<u32>{native_macro_context->positions} : std::span<u32>{macro_positions}; }
@@ -3302,6 +3304,7 @@ private:
/// Interpreter for the macro codes uploaded to the GPU.
std::shared_ptr<NativeMacroContext> native_macro_context;
+ std::array<u32, 0xc00> native_data_ram{};
MacroEngine macro_engine;
Upload::State upload_state;
diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp
index e6279deab4..59396cb58b 100644
--- a/src/video_core/macro.cpp
+++ b/src/video_core/macro.cpp
@@ -1354,6 +1354,7 @@ void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u
LOG_DEBUG(HW_GPU, "Native MME context={:#x}", context);
LOG_DEBUG(HW_GPU, "Native MME start={:#x}, parameters={}, data={:08x}", method, parameters.size(), fmt::join(parameters, ","));
std::vector<u32> fifo(parameters.begin(), parameters.end());
+ native_interpreter.active_dram = &maxwell3d.GetNativeDataRam();
const auto result = native_interpreter.Execute(native_code, method, fifo,
[&](u32 address) { return address < Maxwell3D::Regs::NUM_REGS ? maxwell3d.GetRegisterValue(address) : 0U; },
[&](u32 address, u32 value) {
@@ -1385,8 +1386,13 @@ void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u
const u32 word = memory.Read<u32>(source + u64{i} * 4);
if (address == DmaReadFifoed) {
fifo.push_back(word);
- } else if (ram + i < native_interpreter.dram.size()) {
- native_interpreter.dram[ram + i] = word;
+ } else if (ram + i < native_interpreter.active_dram->size()) {
+ if (static const bool mme_dump = std::getenv("EDEN_NX2_MME_DUMP") != nullptr;
+ mme_dump && ram + i == 0x27 && (*native_interpreter.active_dram)[0x27] != word) {
+ LOG_INFO(HW_GPU, "MME data RAM[0x27] {:#x} -> {:#x} by 3D engine {} (DMA from {:#x})",
+ (*native_interpreter.active_dram)[0x27], word, fmt::ptr(&maxwell3d), source);
+ }
+ (*native_interpreter.active_dram)[ram + i] = word;
}
}
}
diff --git a/src/video_core/macro_turing.h b/src/video_core/macro_turing.h
index a0a40000c2..063e22c297 100644
--- a/src/video_core/macro_turing.h
+++ b/src/video_core/macro_turing.h
@@ -167,11 +167,11 @@ public:
}
break;
case 30:
- if (x >= dram.size()) { fail("data RAM read out of bounds"); break; }
- value = dram[x]; break;
+ if (x >= active_dram->size()) { fail("data RAM read out of bounds"); break; }
+ value = (*active_dram)[x]; break;
case 31:
- if (x >= dram.size()) { fail("data RAM write out of bounds"); break; }
- dram[x] = y; break;
+ if (x >= active_dram->size()) { fail("data RAM write out of bounds"); break; }
+ (*active_dram)[x] = y; break;
}
alu[lane] = value;
if (dst[lane] < 24) { regs[dst[lane]] = value; initialized |= 1U << dst[lane]; }
@@ -209,7 +209,12 @@ public:
return {false, pc, consumed, error};
}
- std::array<Word, 0xc00> dram{};
+ using DataRam = std::array<Word, 0xc00>;
+ DataRam dram{};
+ // The data RAM DREAD/DWRITE use: a channel's own copy when the caller sets one. Macro code is
+ // shared by the channels of a process, but each channel keeps its own data RAM (title 1's
+ // channels store their own QMD ring base in word 0x27).
+ DataRam* active_dram{&dram};
};
} // namespace Tegra
diff --git a/src/video_core/native_qmd.h b/src/video_core/native_qmd.h
index 41961c1aea..15c59cb7c6 100644
--- a/src/video_core/native_qmd.h
+++ b/src/video_core/native_qmd.h
@@ -3,6 +3,7 @@
#pragma once
#include <array>
#include <cstdint>
+#include <functional>
#include <string_view>
#include <type_traits>
#include <unordered_map>
@@ -16,6 +17,8 @@ public:
using Address = std::uint64_t;
using Descriptor = std::array<Word, 64>;
struct RejectShaderDispatch { bool operator()(const Descriptor&) const { return false; } };
+ // Optional diagnostics: called with "load"/"count"/"defer"/"run" events.
+ std::function<void(std::string_view, Address, std::int32_t)> trace;
template <typename Read, typename Release>
std::string_view Signal(Address address, Word argument, Read read, Release release) {
return Signal(address, argument, read, release, RejectShaderDispatch{});
@@ -41,6 +44,8 @@ public:
return "native shader dispatch required";
}
state.dependencies = std::int32_t((qmd[4] >> 14) & 0xffff) + state.deferred;
+ if (trace) trace("base", request.address, std::int32_t((qmd[4] >> 14) & 0xffff));
+ if (trace) trace("load", request.address, state.dependencies);
state.deferred = 0;
state.loaded = true;
state.completed = false;
@@ -48,8 +53,13 @@ public:
state.scheduled = true;
} else if (request.action == 7 || request.action == 12) {
const auto delta = request.action == 7 ? -std::int32_t(request.count) : std::int32_t(request.count);
- if (!state.loaded || state.completed) { state.deferred += delta; continue; }
+ if (!state.loaded || state.completed) {
+ state.deferred += delta;
+ if (trace) trace("defer", request.address, state.deferred);
+ continue;
+ }
state.dependencies += delta;
+ if (trace) trace("count", request.address, state.dependencies);
} else if (request.action == 0 || request.action == 1) {
continue;
} else {
@@ -78,6 +88,7 @@ public:
// are what other queues and the CPU wait on (the console never fails a launch).
const bool dispatched = !(qmd[12] && (qmd[13] & 0xffff) && (qmd[14] & 0xffff)) || dispatch(qmd);
if (!dispatched) error = "native shader dispatch required";
+ if (trace) trace("run", request.address, dispatched ? 1 : 0);
state.completed = true;
for (const Word base : ReleaseSlots) {
const Word flags = qmd[base + 1];
@@ -114,6 +125,9 @@ private:
bool scheduled{};
bool completed{};
};
- std::unordered_map<Address, State> states;
+ // One table for every channel: a QMD's dependency counter is state of the QMD itself, and
+ // title 1 schedules a QMD on one channel while another decrements it (a decrement that
+ // arrived first was kept by the other channel's scheduler and the QMD never launched).
+ static inline std::unordered_map<Address, State> states;
};
} // namespace Tegra