diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 13:22:59 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 13:22:59 -0400 |
| commit | d0c0cb8906ba99bbe013aaa220887b7b8ab31a7a (patch) | |
| tree | 6e6998122a908ae5739dbcb239e26570a8f82beb /src | |
| parent | d6bbbf9f23a7e5b6a6ab11e2696085e0482b6fc4 (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.cpp | 1 | ||||
| -rw-r--r-- | src/video_core/engines/kepler_compute.cpp | 30 | ||||
| -rw-r--r-- | src/video_core/engines/kepler_compute.h | 5 | ||||
| -rw-r--r-- | src/video_core/engines/maxwell_3d.h | 3 | ||||
| -rw-r--r-- | src/video_core/macro.cpp | 10 | ||||
| -rw-r--r-- | src/video_core/macro_turing.h | 15 | ||||
| -rw-r--r-- | src/video_core/native_qmd.h | 18 |
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(¯os->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 |
