From 2e2fc464c20be03995be87c453711ed8f2ebee02 Mon Sep 17 00:00:00 2001 From: kuba Date: Tue, 6 Oct 2026 00:20:30 +0200 Subject: [PATCH] [GPU] Re-issue a stale pending readback instead of reading synchronously on the worker A guest read of GPU-written memory records a copy and waits for its tick off the queue. When another GPU write lands first, the completion fell back to a synchronous read on the command worker, which stalled it until the GPU drained (up to half of SH's readbacks). The guest thread now re-issues the pending read and waits again; only the fourth attempt falls back to the synchronous read. (cherry picked from commit 17bf5bb958ebfd4dd980dbf241cbe9e77fffd938) --- .../Gpu/Buffers/GuestBufferCache.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs b/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs index c84c5f1..aa2c6fb 100644 --- a/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs +++ b/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs @@ -898,9 +898,17 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable if (!_relay.IsGpuQueueThread && GuestReadsAwaitOffQueue && (AsyncReadback is not null || MainQueuePendingReads)) { - PendingDownload? pending = null; - if (_relay.TryRunOnGpuQueue(() => pending = BeginReadMemoryOnGpu(guestAddress, size, isWrite, source, allowPending: true))) + // A GPU write that lands after the copy was recorded makes the read stale. Retrying + // synchronously stalls the command worker until the GPU drains, so re-issue the read and + // wait here again; only the last attempt falls back to the synchronous read. + for (var attempt = 1; ; attempt++) { + PendingDownload? pending = null; + if (!_relay.TryRunOnGpuQueue(() => pending = BeginReadMemoryOnGpu(guestAddress, size, isWrite, source, allowPending: true))) + { + return AwaitShutdown(); + } + if (pending is null) { return true; @@ -915,13 +923,18 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable _scheduler.WaitForSubmittedTick(pending.MainQueueTick); } - if (_relay.TryRunOnGpuQueue(() => CompleteReadMemoryOnGpu(pending, retrySynchronously: true))) + var finalAttempt = attempt >= PendingReadAttempts; + var applied = false; + if (!_relay.TryRunOnGpuQueue(() => applied = CompleteReadMemoryOnGpu(pending, retrySynchronously: finalAttempt))) + { + return AwaitShutdown(); + } + + if (applied || finalAttempt) { return true; } } - - return AwaitShutdown(); } var onQueue = _relay.IsGpuQueueThread; @@ -1307,6 +1320,9 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable private static readonly bool MainQueuePendingReads = Environment.GetEnvironmentVariable("SHARPEMU_MAIN_QUEUE_PENDING_READS") != "0"; + // Pending reads a guest thread re-issues before it lets the worker read synchronously. + private const int PendingReadAttempts = 4; + private bool CompleteReadMemoryOnGpu(PendingDownload pending, bool retrySynchronously) { using var readbackScope = GuestMemoryProfile.Measure(GuestMemoryProfile.Operation.BufferReadback);