AGC: deliver compute-queue completion events at the queue fence (#721)

sceAgcDriverSubmitAcb submissions never produced a completion interrupt.
NotifySubmittedDcbCompleted returned early for anything that was not the
graphics queue, so an ACB reaching its ordered-queue fence published
nothing. UE 4.27's dynamic-resolution GPU-timing heuristic parks the game
thread on exactly that interrupt, so the render side never advanced and
Silent Hill: The Short Message deadlocked after its first frame.

Give every queue a CompletionEventId: 0 for graphics (what it already
published) and the owner handle from sceAgcDriverSubmitAcb rdi for a
compute queue, which is the same value the guest passes to
sceAgcDriverAddEqEvent. Publish under that ident from the existing fence
point, which both PumpSubmittedQueue and ResumeSuspendedDcb already reach
only after the submission is fully parsed.

Delivery is synchronous on the ordered guest-action queue rather than on a
ThreadPool hop with a sleep. That action runs after the logical queue has
flushed and waited for its latest fence, which is the moment hardware
would raise end-of-pipe. Deferring past it can only make the interrupt
late and reorder it against registration changes.

Gating: the per-queue completion event is unconditional, because
completion interrupts do fire on real hardware and because delivery is
registration-gated -- TriggerRegisteredEvents only queues onto equeues
that registered this exact (ident, graphics filter) pair, and
sceAgcDriverAddEqEvent is the only producer of graphics registrations. A
title that never registers its ACB owner handle observes no change.
SHARPEMU_AGC_SUBMIT_COMPLETION_EVENT is left to gate only the broad
ident-ignoring fan-out (TriggerRegisteredEventsDistinct), which is a
compatibility guess rather than hardware behavior; it also stays scoped to
the graphics queue where it was measured, so enabling the flag does not
newly fan out across compute queues.
This commit is contained in:
kuba
2026-07-31 11:12:43 +02:00
committed by GitHub
parent 3f9bd2b92b
commit 531e35b6d5
3 changed files with 269 additions and 11 deletions
+31 -11
View File
@@ -604,6 +604,11 @@ public static partial class AgcExports
public uint DrawIndexOffset { get; set; }
public bool PredicateSkip { get; set; }
public string QueueName { get; set; } = "graphics";
// Ident this queue's end-of-pipe completion interrupt is published under.
// The graphics queue keeps 0; a compute queue takes the owner handle it
// was submitted with, which is the same value the guest registers through
// sceAgcDriverAddEqEvent.
public ulong CompletionEventId { get; set; }
public ulong ActiveSubmissionId { get; set; }
public Queue<PendingSubmission> PendingSubmissions { get; } = new();
public bool HasActiveSubmission { get; set; }
@@ -3333,6 +3338,7 @@ public static partial class AgcExports
}
queueState.QueueName = $"acb.compute[{ownerHandle}]";
queueState.CompletionEventId = ownerHandle;
EnqueueSubmittedDcb(
ctx,
gpuState,
@@ -3523,33 +3529,47 @@ public static partial class AgcExports
SubmittedDcbState state,
ulong submissionId)
{
if (!ReferenceEquals(state, gpuState.Graphics) ||
state.CompletionEventNotifiedSubmissionId == submissionId)
if (state.CompletionEventNotifiedSubmissionId == submissionId)
{
return;
}
state.CompletionEventNotifiedSubmissionId = submissionId;
// Hardware raises an end-of-pipe interrupt for every submission on every
// queue, so this is unconditional. It stays safe for titles that do not
// want it because delivery is registration-gated: TriggerRegisteredEvents
// only queues onto equeues that registered this exact ident through
// sceAgcDriverAddEqEvent. Graphics keeps ident 0; a compute queue uses the
// owner handle it was submitted under.
var completionEventId = state.CompletionEventId;
var isGraphics = ReferenceEquals(state, gpuState.Graphics);
var queueName = state.QueueName;
void TriggerCompletionEvents()
{
var triggered = KernelEventQueueCompatExports.TriggerRegisteredEvents(
ident: 0,
completionEventId,
KernelEventQueueCompatExports.KernelEventFilterGraphics,
data: 0);
if (_compatibilitySubmitCompletionEvent)
completionEventId);
// The broad fan-out wakes graphics registrations whose ident never
// matches anything the driver publishes. That is a compatibility
// guess rather than hardware behavior, so it stays opt-in and stays
// on the graphics queue where it was measured.
if (isGraphics && _compatibilitySubmitCompletionEvent)
{
triggered += KernelEventQueueCompatExports.TriggerRegisteredEventsDistinct(
KernelEventQueueCompatExports.KernelEventFilterGraphics);
}
TraceAgc(
$"agc.driver_submit_dcb completion submission={submissionId} " +
$"queues={triggered}");
$"agc.completion_event queue={queueName} submission={submissionId} " +
$"event=0x{completionEventId:X} queues={triggered}");
}
// A DCB is complete only after its translated Vulkan work and ordered
// guest-memory writes have finished. Put the notification on that same
// logical graphics queue instead of approximating completion with a
// timer, which can wake Unity while its upload data is still stale.
// A submission is complete only after its translated Vulkan work and
// ordered guest-memory writes have finished. Put the notification on that
// same logical queue instead of approximating completion with a timer or a
// ThreadPool hop, either of which can only make the interrupt late and
// reorder it against registration changes (and can wake Unity while its
// upload data is still stale).
if (GuestGpu.Current.SubmitOrderedGuestAction(
TriggerCompletionEvents,
$"agc submit completion {submissionId}") == 0)