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
@@ -8,10 +8,20 @@ using Xunit;
namespace SharpEmu.Libs.Tests.Agc;
// The kernel event-queue registry is process-wide static state, and these tests assert over
// every graphics registration in it, so they cannot run beside another suite that registers
// graphics events.
[CollectionDefinition(GraphicsEventQueueStateCollection.Name, DisableParallelization = true)]
public sealed class GraphicsEventQueueStateCollection
{
public const string Name = "GraphicsEventQueueState";
}
// IT_EVENT_WRITE carries a 6-bit hardware EVENT_TYPE, but sceAgcDriverAddEqEvent registers the
// listener with a guest-defined eventId. Those two values are not the same numbering scheme, so
// exact ident matching never wakes anything (issue #173). TriggerRegisteredEventsByFilter wakes
// every graphics registration instead.
[Collection(GraphicsEventQueueStateCollection.Name)]
public sealed class AgcEventQueueTests
{
private const ulong BaseAddress = 0x1_0000_0000;
@@ -70,6 +80,14 @@ public sealed class AgcEventQueueTests
Assert.Equal(1u, ReadUInt32(memory, eventsAddress + 0x0C));
Assert.Equal(eventType, ReadUInt64(memory, eventsAddress + 0x10));
Assert.Equal(userData, ReadUInt64(memory, eventsAddress + 0x18));
// Registrations live in process-wide static state, and a sibling test asserts that
// no graphics registration exists at all. Drop this one instead of relying on
// execution order.
ctx[CpuRegister.Rdi] = handle;
Assert.Equal(
(int)OrbisGen2Result.ORBIS_GEN2_OK,
KernelEventQueueCompatExports.KernelDeleteEqueue(ctx));
}
[Fact]