aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/engines/puller.cpp
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 03:39:52 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 03:39:52 -0400
commite17fd9cef116a088f080ca502951e500864a58b6 (patch)
treee7ff65f614622515d6a2e4263521909eea89a5d8 /src/video_core/engines/puller.cpp
parentea6c1193fbfa95e586bb6d129dc684163a12cb3e (diff)
parent80c79333fe6fd28a31b65b997f1cbf2987711c91 (diff)
Merge Switch 2 work from local Eden (nx2 at b0dd3fe)
Diffstat (limited to 'src/video_core/engines/puller.cpp')
-rw-r--r--src/video_core/engines/puller.cpp216
1 files changed, 211 insertions, 5 deletions
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp
index 0b05d31718..b607f20161 100644
--- a/src/video_core/engines/puller.cpp
+++ b/src/video_core/engines/puller.cpp
@@ -4,6 +4,9 @@
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include <atomic>
+#include <cstdlib>
+
#include "common/assert.h"
#include "common/logging.h"
#include "common/settings.h"
@@ -18,6 +21,7 @@
#include "video_core/engines/puller.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
+#include "video_core/nx2.h"
#include "video_core/rasterizer_interface.h"
namespace Tegra::Engines {
@@ -27,14 +31,102 @@ namespace {
constexpr u32 Gf100BindClassMask = 0xffff;
constexpr u32 Gf100BindValidMask = 0x1f0000 | Gf100BindClassMask;
+// Reads a semaphore word, flushing a GPU-written copy out of the buffer cache first.
+u32 ReadSemaphoreWord(DmaPusher& dma_pusher, GPUVAddr address) {
+ if (const auto device = dma_pusher.memory_manager.GpuToCpuAddress(address);
+ device && dma_pusher.rasterizer->MustFlushRegion(*device, sizeof(u32),
+ VideoCommon::CacheType::BufferCache)) {
+ dma_pusher.rasterizer->FlushRegion(*device, sizeof(u32),
+ VideoCommon::CacheType::BufferCache);
+ }
+ return dma_pusher.memory_manager.Read<u32>(address);
+}
+
} // Anonymous namespace
+bool Puller::PendingAcquire::Matches(u32 current) const {
+ switch (static_cast<GpuSemaphoreOperation>(operation)) {
+ case GpuSemaphoreOperation::AcquireEqual:
+ return current == value;
+ case GpuSemaphoreOperation::AcquireGequal:
+ return current >= value;
+ case GpuSemaphoreOperation::AcquireMask:
+ return (current & value) != 0;
+ default:
+ return true;
+ }
+}
+
+bool Puller::CanResumeAcquire(DmaPusher& dma_pusher) {
+ if (!pending_acquire) {
+ return true;
+ }
+ const u32 current = ReadSemaphoreWord(dma_pusher, pending_acquire->address);
+ if (Nx2SemaphoreWaiters::Get().Latched(this)) {
+ if (static std::atomic<u32> rescued{};
+ !pending_acquire->Matches(current) && rescued.fetch_add(1) < 64) {
+ LOG_INFO(HW_GPU, "Channel {} met semaphore {:#x} value {:#x} at its release (now {:#x})",
+ dma_pusher.channel_state.bind_id, pending_acquire->address,
+ pending_acquire->value, current);
+ }
+ return true;
+ }
+ if (pending_acquire->Matches(current)) {
+ return true;
+ }
+ if (!pending_acquire->reported &&
+ std::chrono::steady_clock::now() - pending_acquire->since > std::chrono::seconds{2}) {
+ pending_acquire->reported = true;
+ const auto device = dma_pusher.memory_manager.GpuToCpuAddress(pending_acquire->address);
+ LOG_WARNING(HW_GPU, "Channel {} waits on semaphore {:#x} (op {}, {:#x}, now {:#x}) for 2 s; "
+ "device {:#x}, buffer cache holds GPU data: {}, words {:08x} {:08x} {:08x} {:08x}",
+ dma_pusher.channel_state.bind_id, pending_acquire->address,
+ pending_acquire->operation, pending_acquire->value, current, device.value_or(0),
+ device && dma_pusher.rasterizer->MustFlushRegion(*device, 16, VideoCommon::CacheType::All),
+ dma_pusher.memory_manager.Read<u32>(pending_acquire->address & ~GPUVAddr{0xf}),
+ dma_pusher.memory_manager.Read<u32>((pending_acquire->address & ~GPUVAddr{0xf}) + 4),
+ dma_pusher.memory_manager.Read<u32>((pending_acquire->address & ~GPUVAddr{0xf}) + 8),
+ dma_pusher.memory_manager.Read<u32>((pending_acquire->address & ~GPUVAddr{0xf}) + 12));
+ }
+ return false;
+}
+
+bool Puller::ResumeAcquire(DmaPusher& dma_pusher) {
+ if (!CanResumeAcquire(dma_pusher)) {
+ return false;
+ }
+ pending_acquire.reset();
+ Nx2SemaphoreWaiters::Get().Remove(this);
+ return true;
+}
+
void Puller::ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_call) {
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {:#x}", method_call.subchannel, method_call.argument);
u32 engine = method_call.argument;
if ((engine & ~Gf100BindClassMask) != 0 && (engine & ~Gf100BindValidMask) == 0) {
engine &= Gf100BindClassMask;
}
+ // Switch 2 binds Ampere classes; run them on the Maxwell engines (same method layout for
+ // most state). Class 0 unbinds the subchannel.
+ switch (engine & 0xFFFF) {
+ case 0xC797: // AMPERE_B
+ engine = static_cast<u32>(EngineID::MAXWELL_B);
+ break;
+ case 0xC7C0: // AMPERE_COMPUTE_B
+ engine = static_cast<u32>(EngineID::KEPLER_COMPUTE_B);
+ break;
+ case 0xC7B5: // AMPERE_DMA_COPY_B
+ engine = static_cast<u32>(EngineID::MAXWELL_DMA_COPY_A);
+ break;
+ case 0x0:
+ if (g_nx2_ampere) {
+ LOG_DEBUG(HW_GPU, "Switch 2 unbinds subchannel {}", method_call.subchannel);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
const auto engine_id = static_cast<EngineID>(engine);
bound_engines[method_call.subchannel] = engine_id;
@@ -82,10 +174,64 @@ void Puller::ProcessFenceActionMethod(DmaPusher& dma_pusher) {
void Puller::ProcessSemaphoreTriggerMethod(DmaPusher& dma_pusher) {
const auto semaphoreOperationMask = 0xF;
const auto op = GpuSemaphoreOperation(regs.semaphore_trigger & semaphoreOperationMask);
+ // EDEN_NX2_SEM_WATCH=<gpu address>: log every release/acquire on the 16 bytes there.
+ static const GPUVAddr watch = [] {
+ const char* value = std::getenv("EDEN_NX2_SEM_WATCH");
+ return value ? GPUVAddr{std::strtoull(value, nullptr, 16)} : GPUVAddr{};
+ }();
+ if (watch != 0 && (regs.semaphore_address.SemaphoreAddress() & ~GPUVAddr{0xf}) == watch) {
+ LOG_INFO(HW_GPU, "Semaphore watch: channel {} as {} device {:#x} op {} {:#x} value {:#x} (now {:#x})",
+ dma_pusher.channel_state.bind_id, dma_pusher.memory_manager.GetID(),
+ dma_pusher.memory_manager.GpuToCpuAddress(regs.semaphore_address.SemaphoreAddress()).value_or(0),
+ static_cast<u32>(op),
+ regs.semaphore_address.SemaphoreAddress(), regs.semaphore_sequence,
+ dma_pusher.memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress()));
+ }
if (op == GpuSemaphoreOperation::WriteLong) {
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
const u32 payload = regs.semaphore_sequence;
- dma_pusher.rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
+ // RELEASE_SIZE (bit 24) set means a 4-byte release without the timestamp; writing 16
+ // bytes there clobbers the words next to it (Switch 2 packs semaphores tightly).
+ const auto properties = g_nx2_ampere && (regs.semaphore_trigger & (1U << 24)) != 0
+ ? VideoCommon::QueryPropertiesFlags::IsAFence
+ : VideoCommon::QueryPropertiesFlags::HasTimeout;
+ dma_pusher.rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, properties, payload, 0);
+ if (const GPUVAddr mirror = g_nx2_ampere ? Nx2SemaphoreMirror(sequence_address) : 0;
+ mirror != 0) {
+ LOG_DEBUG(HW_GPU, "Switch 2 semaphore {:#x}={:#x} mirrored to {:#x}", sequence_address, payload, mirror);
+ dma_pusher.rasterizer->Query(mirror, VideoCommon::QueryType::Payload, properties, payload, 0);
+ }
+ } else if (g_nx2_ampere) {
+ // Switch 2 waits on semaphores the CPU (or another channel) releases, e.g. once it has
+ // written the commands this channel runs next: suspend the channel until the value is
+ // met instead of reading ahead (approach from changes.patch).
+ PendingAcquire acquire{
+ .address = regs.semaphore_address.SemaphoreAddress(),
+ .value = regs.semaphore_sequence,
+ .operation = static_cast<u32>(op),
+ .since = std::chrono::steady_clock::now(),
+ .reported = false,
+ };
+ if (op != GpuSemaphoreOperation::AcquireEqual && op != GpuSemaphoreOperation::AcquireGequal &&
+ op != GpuSemaphoreOperation::AcquireMask) {
+ LOG_ERROR(HW_GPU, "Invalid semaphore operation {}", static_cast<u32>(op));
+ return;
+ }
+ if (acquire.Matches(ReadSemaphoreWord(dma_pusher, acquire.address))) {
+ return;
+ }
+ // Registered before the fences run, so a release among them that meets the value counts
+ // even if a later one overwrites it.
+ auto& waiters = Nx2SemaphoreWaiters::Get();
+ if (const auto device = dma_pusher.memory_manager.GpuToCpuAddress(acquire.address)) {
+ waiters.Add(this, *device, acquire.value, acquire.operation);
+ }
+ dma_pusher.rasterizer->ReleaseFences();
+ if (!waiters.Latched(this) && !acquire.Matches(ReadSemaphoreWord(dma_pusher, acquire.address))) {
+ pending_acquire = acquire;
+ } else {
+ waiters.Remove(this);
+ }
} else {
do {
const u32 word = dma_pusher.memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
@@ -137,8 +283,43 @@ void Puller::ProcessSemaphoreAcquire(DmaPusher& dma_pusher) {
}
}
+// Ampere SEM_EXECUTE (method 0x1B): operation in bits 2:0 on the semaphore at
+// SEM_ADDR_HI:SEM_ADDR_LO (methods 0x18/0x17) with payload SEM_PAYLOAD_LO (0x19).
+void Puller::ProcessSemaphoreExecute(DmaPusher& dma_pusher, u32 argument) {
+ const GPUVAddr address = (GPUVAddr{regs.reg_array[0x18] & 0x1FFFFFF} << 32) |
+ (regs.reg_array[0x17] & ~3U);
+ const u32 payload = regs.reg_array[0x19];
+ const u32 operation = argument & 7;
+ if (operation == 1) { // RELEASE
+ dma_pusher.rasterizer->Query(address, VideoCommon::QueryType::Payload,
+ VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0);
+ return;
+ }
+ // Acquires: check once after flushing pending fences; never block the GPU thread on them.
+ dma_pusher.rasterizer->ReleaseFences();
+ const u32 word = dma_pusher.memory_manager.Read<u32>(address);
+ const bool ready = operation == 0 ? word == payload
+ : operation == 2 ? word >= payload
+ : operation == 3 ? static_cast<s32>(word - payload) >= 0
+ : operation == 4 ? (word & payload) != 0
+ : operation == 5 ? (word & payload) == 0
+ : true;
+ if (!ready) {
+ static std::atomic<u32> reported{};
+ if (reported.fetch_add(1) < 8) {
+ LOG_WARNING(HW_GPU, "SEM_EXECUTE op {} at {:#x}: {:#x} vs {:#x} not met, continuing",
+ operation, address, word, payload);
+ }
+ }
+}
+
/// Calls a GPU puller method.
void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call) {
+ static const bool nx2_trace = std::getenv("EDEN_NX2_PULLER_TRACE") != nullptr;
+ if (nx2_trace) {
+ LOG_INFO(HW_GPU, "nx2 puller sub={} method={:#x} arg={:#x}", method_call.subchannel,
+ method_call.method, method_call.argument);
+ }
regs.reg_array[method_call.method] = method_call.argument;
const auto method = static_cast<BufferMethods>(method_call.method);
@@ -172,7 +353,7 @@ void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_ca
break;
}
case BufferMethods::MemOpA: {
- LOG_ERROR(HW_GPU, "Memory Operation A");
+ LOG_TRACE(HW_GPU, "Memory Operation A");
break;
}
case BufferMethods::MemOpB: {
@@ -182,15 +363,22 @@ void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_ca
}
case BufferMethods::MemOpC:
case BufferMethods::MemOpD: {
- LOG_ERROR(HW_GPU, "Memory Operation C,D");
+ LOG_TRACE(HW_GPU, "Memory Operation C,D");
break;
}
case BufferMethods::SemaphoreAcquire: {
- ProcessSemaphoreAcquire(dma_pusher);
+ // On Ampere host classes 0x17..0x1A are SEM_ADDR_LO/HI and SEM_PAYLOAD_LO/HI.
+ if (!g_nx2_ampere) {
+ ProcessSemaphoreAcquire(dma_pusher);
+ }
break;
}
case BufferMethods::SemaphoreRelease: {
- ProcessSemaphoreRelease(dma_pusher);
+ if (g_nx2_ampere) {
+ ProcessSemaphoreExecute(dma_pusher, method_call.argument);
+ } else {
+ ProcessSemaphoreRelease(dma_pusher);
+ }
break;
}
case BufferMethods::Yield: {
@@ -200,13 +388,27 @@ void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_ca
}
default:
LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method);
+ if (static bool dumped = false; g_nx2_ampere && !dumped) {
+ dumped = true;
+ dma_pusher.DumpCurrentEntry("unknown host method");
+ }
break;
}
}
/// Calls a GPU engine method.
+static bool Nx2MethodTrace() {
+ static const bool trace = std::getenv("EDEN_NX2_METHOD_TRACE") != nullptr;
+ return trace;
+}
+
void Puller::CallEngineMethod(DmaPusher& dma_pusher, const MethodCall& method_call) {
const EngineID engine = bound_engines[method_call.subchannel];
+ if (Nx2MethodTrace()) {
+ LOG_INFO(HW_GPU, "nx2 method sub={} engine={:04X} method={:#x} arg={:#x}",
+ method_call.subchannel, static_cast<u32>(engine), method_call.method,
+ method_call.argument);
+ }
switch (engine) {
case EngineID::FERMI_TWOD_A:
dma_pusher.channel_state.payload->fermi_2d.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall());
@@ -235,6 +437,10 @@ void Puller::CallEngineMethod(DmaPusher& dma_pusher, const MethodCall& method_ca
/// Calls a GPU engine multivalue method.
void Puller::CallEngineMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending) {
const EngineID engine = bound_engines[subchannel];
+ if (Nx2MethodTrace()) {
+ LOG_INFO(HW_GPU, "nx2 multimethod sub={} engine={:04X} method={:#x} amount={} first={:#x}",
+ subchannel, static_cast<u32>(engine), method, amount, base_start[0]);
+ }
switch (engine) {
case EngineID::FERMI_TWOD_A:
dma_pusher.channel_state.payload->fermi_2d.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending);