aboutsummaryrefslogtreecommitdiff
path: root/src/video_core
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 07:49:09 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 20:49:47 -0400
commit15eef40dd12c2b751d5b1cac57dd8f3602e2d133 (patch)
tree44b191586781c8c11e0e0279983ef9b45abe6c34 /src/video_core
parent4d7558e25bb0a59d90153bc8dfdca4b4e1fc2f48 (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/video_core')
-rw-r--r--src/video_core/engines/maxwell_3d.cpp29
-rw-r--r--src/video_core/engines/maxwell_3d.h10
-rw-r--r--src/video_core/nx2_profile.h7
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;