aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/nx2.h
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 05:41:45 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 05:41:45 -0400
commitcff4add442d78e088d13005c4c6fdd436b47eecd (patch)
treec4bcb200d58b5c1f70ebcd9449597a987ad2e6e8 /src/video_core/nx2.h
parent5c4e642c286279f3e44d476550736ed6bff400ab (diff)
nx2: semaphore releases latch the acquires they satisfy
Switch 2 channels that wait for an exact semaphore value poll on the console and see every release. Eden resumes a waiting channel only between command lists and writes releases when fences signal, often several back to back, so an acquire-equal could miss its value and wait forever. Title 4's 3D queue ticks a counter that two other queues wait on value by value; under the loading after the post-customization save one of them missed a tick and the game froze (channel 4 waiting for 0x5293 with the counter at 0x5310). Pending acquires now register with Nx2SemaphoreWaiters before fences run, and every query-cache payload write and QMD add/max release latches the waiters it meets at the moment it is written.
Diffstat (limited to 'src/video_core/nx2.h')
-rw-r--r--src/video_core/nx2.h83
1 files changed, 83 insertions, 0 deletions
diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h
index 7bbbf14785..5b6d89abdd 100644
--- a/src/video_core/nx2.h
+++ b/src/video_core/nx2.h
@@ -3,8 +3,11 @@
#pragma once
+#include <algorithm>
#include <array>
#include <atomic>
+#include <mutex>
+#include <vector>
#include "common/common_types.h"
@@ -57,4 +60,84 @@ inline u64 Nx2SemaphoreMirror(u64 address) {
return 0;
}
+/// Channels suspended on a semaphore acquire. A console channel polls and sees every release;
+/// Eden writes releases in bursts when fences signal and resumes waiters only between command
+/// lists, so an acquire-equal could miss its value. Writes latch the waiters they satisfy.
+class Nx2SemaphoreWaiters {
+public:
+ // Puller GpuSemaphoreOperation values.
+ static constexpr u32 AcquireEqual = 0x1;
+ static constexpr u32 AcquireGequal = 0x4;
+ static constexpr u32 AcquireMask = 0x8;
+
+ static Nx2SemaphoreWaiters& Get() {
+ static Nx2SemaphoreWaiters waiters;
+ return waiters;
+ }
+
+ void Add(const void* owner, u64 device_address, u32 value, u32 operation) {
+ std::scoped_lock lk{mutex};
+ std::erase_if(waiters, [owner](const Waiter& w) { return w.owner == owner; });
+ waiters.push_back({owner, device_address, value, operation, false});
+ count.store(waiters.size(), std::memory_order_release);
+ }
+
+ void Remove(const void* owner) {
+ if (count.load(std::memory_order_acquire) == 0) {
+ return;
+ }
+ std::scoped_lock lk{mutex};
+ std::erase_if(waiters, [owner](const Waiter& w) { return w.owner == owner; });
+ count.store(waiters.size(), std::memory_order_release);
+ }
+
+ [[nodiscard]] bool Latched(const void* owner) {
+ if (count.load(std::memory_order_acquire) == 0) {
+ return false;
+ }
+ std::scoped_lock lk{mutex};
+ return std::ranges::any_of(waiters,
+ [owner](const Waiter& w) { return w.owner == owner && w.latched; });
+ }
+
+ /// A GPU write of `value` to the u32 at `device_address`.
+ void OnWrite(u64 device_address, u32 value) {
+ if (count.load(std::memory_order_acquire) == 0) {
+ return;
+ }
+ std::scoped_lock lk{mutex};
+ for (auto& w : waiters) {
+ if (w.address == device_address && !w.latched && Matches(w, value)) {
+ w.latched = true;
+ }
+ }
+ }
+
+private:
+ struct Waiter {
+ const void* owner;
+ u64 address;
+ u32 value;
+ u32 operation;
+ bool latched;
+ };
+
+ static bool Matches(const Waiter& w, u32 current) {
+ switch (w.operation) {
+ case AcquireEqual:
+ return current == w.value;
+ case AcquireGequal:
+ return current >= w.value;
+ case AcquireMask:
+ return (current & w.value) != 0;
+ default:
+ return false;
+ }
+ }
+
+ std::mutex mutex;
+ std::vector<Waiter> waiters;
+ std::atomic<size_t> count{};
+};
+
} // namespace Tegra