diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 07:49:09 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 20:49:47 -0400 |
| commit | 15eef40dd12c2b751d5b1cac57dd8f3602e2d133 (patch) | |
| tree | 44b191586781c8c11e0e0279983ef9b45abe6c34 /src | |
| parent | 4d7558e25bb0a59d90153bc8dfdca4b4e1fc2f48 (diff) | |
maxwell_3d: drop state register writes that keep the register's value
Title 1 makes ~6 million 3D register writes a second and ~60% of them write the value
the register already holds (counted with EDEN_NX2_GPU_PROFILE), most from macro memo
replays. For registers without side effects (not in execution_mask: draws, clears,
constant buffer data and binds, semaphores, ...) such a write changes nothing, but it
set dirty flags that made the next draw re-check that state. CallMethod and the sunk
writes now skip them after shadow RAM processing. Two profiled runs each: macro calls
169 ns instead of 185, draw setup 5.80 us instead of 5.95. EDEN_NX2_SKIP_SAME=0 turns it
off.
Diffstat (limited to 'src')
| -rw-r--r-- | src/video_core/engines/maxwell_3d.cpp | 29 | ||||
| -rw-r--r-- | src/video_core/engines/maxwell_3d.h | 10 | ||||
| -rw-r--r-- | src/video_core/nx2_profile.h | 7 |
3 files changed, 42 insertions, 4 deletions
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 20a973bb6b..f958b025bc 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -20,6 +20,7 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" +#include "video_core/nx2_profile.h" #include "video_core/rasterizer_interface.h" #include "video_core/textures/texture.h" @@ -307,17 +308,25 @@ u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { void Maxwell3D::ConsumeSinkImpl(Core::System& system) { const auto control = shadow_state.shadow_ram_control; + // Sunk methods are state writes only (not in execution_mask): skip unchanged values, as + // CallMethod does. + const bool skip_same = SkipSameValueWrites(); + const auto process = [&](u32 method, u32 value) { + if (!skip_same || regs.reg_array[method] != value) { + ProcessDirtyRegisters(method, value); + } + }; if (control == Regs::ShadowRamControl::Track || control == Regs::ShadowRamControl::TrackWithFilter) { for (auto [method, value] : method_sink) { shadow_state.reg_array[method] = value; - ProcessDirtyRegisters(method, value); + process(method, value); } } else if (control == Regs::ShadowRamControl::Replay) { for (auto [method, value] : method_sink) - ProcessDirtyRegisters(method, shadow_state.reg_array[method]); + process(method, shadow_state.reg_array[method]); } else { for (auto [method, value] : method_sink) - ProcessDirtyRegisters(method, value); + process(method, value); } method_sink.clear(); } @@ -445,7 +454,21 @@ void Maxwell3D::CallMethod(Core::System& system, u32 method, u32 method_argument } ASSERT(method < Regs::NUM_REGS && "Invalid Maxwell3D register, increase the size of the Regs structure"); + if (VideoCommon::Nx2Profile::Enabled()) [[unlikely]] { + VideoCommon::Nx2Profile::Add(VideoCommon::Nx2Profile::Counter::MethodWrites, 1); + if (regs.reg_array[method] == method_argument) { + VideoCommon::Nx2Profile::Add(VideoCommon::Nx2Profile::Counter::SameValueWrites, 1); + } + } const u32 argument = ProcessShadowRam(method, method_argument); + // A state register written with the value it holds changes nothing: no dirty flags (they + // would make the next draw re-check that state), no method processing. Title 1 makes ~6 + // million 3D register writes a second, ~60% of them same-value, most from macro replays. + // Methods with side effects (execution_mask: draws, clears, constant buffer data and + // binds, semaphores, ...) always run. EDEN_NX2_SKIP_SAME=0 turns it off. + if (SkipSameValueWrites() && !execution_mask[method] && regs.reg_array[method] == argument) { + return; + } ProcessDirtyRegisters(method, argument); ProcessMethodCall(method, argument, method_argument, is_last_call); } diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 190d5de630..8a806cc070 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -9,6 +9,7 @@ #include <array> #include <bitset> #include <cmath> +#include <cstdlib> #include <limits> #include <optional> #include <span> @@ -3250,6 +3251,15 @@ private: */ void CallMacroMethod(Core::System& system, u32 method, std::span<const u32> parameters); + /// Whether state register writes that keep the register's value are dropped (CallMethod). + static bool SkipSameValueWrites() { + static const bool skip = [] { + const char* value = std::getenv("EDEN_NX2_SKIP_SAME"); + return value == nullptr || value[0] != '0'; + }(); + return skip; + } + /// Handles writes to the macro uploading register. void ProcessMacroUpload(u32 data); diff --git a/src/video_core/nx2_profile.h b/src/video_core/nx2_profile.h index e4609cf77d..ccedb7b77d 100644 --- a/src/video_core/nx2_profile.h +++ b/src/video_core/nx2_profile.h @@ -75,7 +75,7 @@ inline u64 Now() { struct ThreadData { std::array<u64, static_cast<size_t>(Zone::Count)> exclusive_ns{}; std::array<u64, static_cast<size_t>(Zone::Count)> count{}; - std::array<u64, 4> counters{}; ///< See Counter + std::array<u64, 6> counters{}; ///< See Counter u64 child_ns{}; ///< Time of the zones nested in the current one u64 window_start{}; }; @@ -121,6 +121,8 @@ enum class Counter : u32 { DownloadCopies, ///< Buffer ranges copied back DmaReadBytes, ///< Guest memory the native MME read with DMA DownloadBytesOnDmaPages, ///< Of DownloadBytes: on pages the MME has DMA-read + MethodWrites, ///< 3D register writes (Maxwell3D::CallMethod below the macro range) + SameValueWrites, ///< Of MethodWrites: the register already held the value }; /// Device pages (4 KiB) the native MME has DMA-read since start (GPU thread only; filled only @@ -174,6 +176,9 @@ inline void Report() { static_cast<double>(data.counters[1]) * 1e9 / window, static_cast<double>(data.counters[3]) * 1e9 / 1024.0 / window, static_cast<double>(data.counters[2]) * 1e9 / 1024.0 / window); + line += fmt::format(" | 3D register writes {:.0f} ({:.0f} same value)", + static_cast<double>(data.counters[4]) * 1e9 / window, + static_cast<double>(data.counters[5]) * 1e9 / window); LOG_INFO(HW_GPU, "GPU thread per second:{}", line); data = {}; data.window_start = now; |
