aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/gpu_split.h
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 11:55:07 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 20:49:48 -0400
commit15e953cafd5cb348833cb373d7ddd996b81307f7 (patch)
tree3fd08c6943dd5b620fe1c899c283c1c78a6a5545 /src/video_core/gpu_split.h
parent17c10c3690eab1ab461318f7e3e8850d490fb753 (diff)
video_core: GPU thread split on by default for Switch 2 titles; cheaper front paths
Clean A/B in title 1 (3 rounds each): 1.46x the draws a second of one thread, frames 17% shorter, no crashes, front register copies equal to the back's in 57,089 checks. EDEN_NX2_SPLIT=0 runs everything on one thread, =1 keeps macros on the back. The front's per-write paths no longer check guarded statics (GpuSplit::Get, Nx2Profile::Enabled, the skip-same switches) and the ring's method writes are inline. Notes row 145.
Diffstat (limited to 'src/video_core/gpu_split.h')
-rw-r--r--src/video_core/gpu_split.h51
1 files changed, 43 insertions, 8 deletions
diff --git a/src/video_core/gpu_split.h b/src/video_core/gpu_split.h
index 61644b9b80..d6f91e4141 100644
--- a/src/video_core/gpu_split.h
+++ b/src/video_core/gpu_split.h
@@ -28,12 +28,14 @@ namespace VideoCommon {
// second thread (the back), fed through a single-producer single-consumer ring. Work the front
// would do on back-owned state is deferred into the same ring; the front waits for the back to
// catch up (Drain) only where it needs a result from that state, e.g. a semaphore it acquires.
-// Opt-in for Switch 2 titles: EDEN_NX2_SPLIT=1. EDEN_NX2_SPLIT=2 also runs the 3D and compute
-// engines' macros on the front, against copies of the registers they read (Maxwell3D and
-// KeplerCompute Front*), and sends the writes they make.
+// On for Switch 2 titles with the 3D and compute engines' macros on the front, against copies of
+// the registers they read (Maxwell3D and KeplerCompute Front*), which sends the writes they make.
+// EDEN_NX2_SPLIT=1 keeps the macros on the back; EDEN_NX2_SPLIT=0 runs everything on one thread.
class GpuSplit {
public:
- static GpuSplit& Get();
+ static GpuSplit& Get() {
+ return instance;
+ }
/// Whether the front sends work to the back (front thread only).
[[nodiscard]] bool Active() const {
@@ -52,11 +54,28 @@ public:
/// An engine method write, executed as DmaPusher would (method sink, ConsumeSink, call).
/// `segment`: the engine's current_dma_segment for the call, when the engine reads it.
void Method(Tegra::Engines::EngineInterface* engine, u32 method, u32 argument, bool is_last,
- const GPUVAddr* segment);
+ const GPUVAddr* segment) {
+ SelectEngine(engine);
+ const size_t words = segment != nullptr ? 2 : 1;
+ u64* const entry = Reserve(words);
+ entry[0] = static_cast<u64>(Kind::Method) | (is_last ? IsLast : 0) |
+ (segment != nullptr ? HasSegment : 0) | (u64{method & 0xffff} << 8) |
+ (u64{argument} << 32);
+ if (segment != nullptr) {
+ entry[1] = *segment;
+ }
+ Commit(words);
+ }
void MultiMethod(Tegra::Engines::EngineInterface* engine, u32 method, const u32* words,
u32 amount, u32 methods_pending, const GPUVAddr* segment);
/// A write made by a macro: CallMethod without the method sink, as the engines' macros do.
- void MacroWrite(Tegra::Engines::EngineInterface* engine, u32 method, u32 argument);
+ void MacroWrite(Tegra::Engines::EngineInterface* engine, u32 method, u32 argument) {
+ SelectEngine(engine);
+ u64* const entry = Reserve(1);
+ entry[0] = static_cast<u64>(Kind::Method) | IsLast | Direct | (u64{method & 0xffff} << 8) |
+ (u64{argument} << 32);
+ Commit(1);
+ }
/// Runs `func` on the back after everything sent before it. Must be trivially copyable.
template <typename Func>
@@ -103,7 +122,16 @@ private:
(*reinterpret_cast<const Func*>(payload))();
}
- u64* Reserve(size_t words);
+ /// Room for an entry of `words` at the write position (never across the ring's end).
+ u64* Reserve(size_t words) {
+ const size_t index = write_position & RingMask;
+ if (index + words <= RingWords && write_position + words - consumed_cache <= RingWords)
+ [[likely]] {
+ return &ring[index];
+ }
+ return ReserveSlow(words);
+ }
+ u64* ReserveSlow(size_t words);
void Commit(size_t words) {
write_position += words;
if (write_position - published_local >= publish_every) {
@@ -111,7 +139,12 @@ private:
}
}
void WaitForSpace(size_t words);
- void SelectEngine(Tegra::Engines::EngineInterface* engine);
+ void SelectEngine(Tegra::Engines::EngineInterface* engine) {
+ if (engine != front_engine) [[unlikely]] {
+ SwitchEngine(engine);
+ }
+ }
+ void SwitchEngine(Tegra::Engines::EngineInterface* engine);
void BackLoop(std::stop_token stop_token);
size_t Execute(const u64* entry);
@@ -132,6 +165,8 @@ private:
// Back side.
Tegra::Engines::EngineInterface* back_engine{};
+ static GpuSplit instance;
+
alignas(64) std::atomic<u64> published{};
alignas(64) std::atomic<u64> consumed{};
alignas(64) std::atomic<bool> back_waiting{};