[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)
This commit is contained in:
kuba 2026-10-06 00:20:30 +02:00
parent b167a565d0
commit 2e2fc464c2
1 changed files with 21 additions and 5 deletions

View File

@ -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);