Merge #941 (pr/gpu-fewer-drains)
This commit is contained in:
commit
e702daeb59
|
|
@ -29,6 +29,8 @@ public sealed class CommandStreamQueue
|
|||
public const int ComputeQueueCount = 56;
|
||||
public const int QueueCount = 1 + ComputeQueueCount;
|
||||
public const int AllBlockedRetryMilliseconds = 100;
|
||||
// The hardware lets one suspend point be in flight; a second one waits for the first.
|
||||
public const int MaxBoundariesInFlight = 1;
|
||||
|
||||
private readonly ICommandStreamHost _host;
|
||||
private readonly object _gate = new();
|
||||
|
|
@ -43,6 +45,7 @@ public sealed class CommandStreamQueue
|
|||
private ulong _submitId;
|
||||
private ulong _lastCompletedGpuTick;
|
||||
private int _doneCount;
|
||||
private int _pendingBoundaries;
|
||||
private IdleOutcome _outcome = IdleOutcome.Completed;
|
||||
private Thread? _processingThread;
|
||||
|
||||
|
|
@ -191,28 +194,57 @@ public sealed class CommandStreamQueue
|
|||
|
||||
_queues[submission.QueueId].AddLast(submission);
|
||||
_submissionCount++;
|
||||
if (submission.Kind != CommandSubmissionKind.FlipPreparation)
|
||||
if (submission.Kind is not (CommandSubmissionKind.FlipPreparation or CommandSubmissionKind.FrameBoundary))
|
||||
SubmissionFlowProfile.Record(SubmissionFlowProfile.EventKind.Enqueued, submission.QueueId,
|
||||
submission.SubmissionId, submission.Address, submission.DwordCount, _submissionCount);
|
||||
Monitor.PulseAll(_gate);
|
||||
}
|
||||
|
||||
// Marks the frame boundary; from another thread it first waits for the queue to drain.
|
||||
// Marks the frame boundary. A suspend point inserts commands that drain the graphics
|
||||
// pipe; it does not wait for them, and the hardware allows one to be in flight, so the
|
||||
// boundary is queued behind the submissions it ends and only a second one waits.
|
||||
public IdleOutcome Done()
|
||||
{
|
||||
var outcome = IdleOutcome.Completed;
|
||||
if (_processingThread != Thread.CurrentThread)
|
||||
if (_processingThread == Thread.CurrentThread)
|
||||
{
|
||||
outcome = WaitForIdle();
|
||||
lock (_gate)
|
||||
{
|
||||
_graphicsDone = true;
|
||||
_doneCount++;
|
||||
}
|
||||
|
||||
return IdleOutcome.Completed;
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
_graphicsDone = true;
|
||||
_doneCount++;
|
||||
}
|
||||
if (_outcome != IdleOutcome.Completed)
|
||||
{
|
||||
return _outcome;
|
||||
}
|
||||
|
||||
return outcome;
|
||||
_graphicsDone = true;
|
||||
if (!_accepting)
|
||||
{
|
||||
// Shutdown: no slice will run a queued boundary, so drain as before.
|
||||
while (_outcome == IdleOutcome.Completed && (_processing || _submissionCount != 0))
|
||||
{
|
||||
Monitor.Wait(_gate);
|
||||
}
|
||||
|
||||
_doneCount++;
|
||||
return _outcome;
|
||||
}
|
||||
|
||||
_pendingBoundaries++;
|
||||
EnqueueLocked(new CommandSubmission(CommandSubmissionKind.FrameBoundary, 0, 0, 0, 0, null));
|
||||
while (_outcome == IdleOutcome.Completed && _pendingBoundaries > MaxBoundariesInFlight)
|
||||
{
|
||||
Monitor.Wait(_gate);
|
||||
}
|
||||
|
||||
return _outcome;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns once nothing is pending; a cancelled or failed queue reports that instead.
|
||||
|
|
@ -348,7 +380,7 @@ public sealed class CommandStreamQueue
|
|||
_nextQueue = (selected + 1) % QueueCount;
|
||||
_processing = true;
|
||||
_processingThread = Thread.CurrentThread;
|
||||
if (!submission.Started && submission.Kind != CommandSubmissionKind.FlipPreparation)
|
||||
if (!submission.Started && submission.Kind is not (CommandSubmissionKind.FlipPreparation or CommandSubmissionKind.FrameBoundary))
|
||||
SubmissionFlowProfile.Record(SubmissionFlowProfile.EventKind.ProcessingStarted, submission.QueueId,
|
||||
submission.SubmissionId, submission.Address, submission.DwordCount, _submissionCount);
|
||||
}
|
||||
|
|
@ -388,6 +420,12 @@ public sealed class CommandStreamQueue
|
|||
}
|
||||
else
|
||||
{
|
||||
if (submission.Kind == CommandSubmissionKind.FrameBoundary)
|
||||
{
|
||||
_pendingBoundaries--;
|
||||
_doneCount++;
|
||||
}
|
||||
|
||||
foreach (var queue in _queues)
|
||||
{
|
||||
if (queue.First is { } head)
|
||||
|
|
@ -415,6 +453,14 @@ public sealed class CommandStreamQueue
|
|||
return true;
|
||||
}
|
||||
|
||||
// A suspend point drains the graphics pipe: submit what the frame recorded.
|
||||
if (submission.Kind == CommandSubmissionKind.FrameBoundary)
|
||||
{
|
||||
SlicesRun++;
|
||||
_host.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
var firstSlice = !submission.Started;
|
||||
var processor = GetInterpreter(submission.QueueId);
|
||||
if (firstSlice && submission.ResetInterpreter)
|
||||
|
|
@ -526,6 +572,14 @@ public sealed class CommandStreamQueue
|
|||
if (queue.First is { } head && head.Value.Blocked)
|
||||
{
|
||||
_submissionCount -= queue.Count;
|
||||
foreach (var dropped in queue)
|
||||
{
|
||||
if (dropped.Kind == CommandSubmissionKind.FrameBoundary)
|
||||
{
|
||||
_pendingBoundaries--;
|
||||
}
|
||||
}
|
||||
|
||||
queue.Clear();
|
||||
_outcome = IdleOutcome.Cancelled;
|
||||
}
|
||||
|
|
@ -630,5 +684,6 @@ public sealed class CommandStreamQueue
|
|||
}
|
||||
|
||||
_submissionCount = 0;
|
||||
_pendingBoundaries = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ public enum CommandSubmissionKind
|
|||
Compute,
|
||||
// A video-out export flip queued behind the graphics submissions before it.
|
||||
FlipPreparation,
|
||||
// A suspend point queued behind the graphics submissions of the frame it ends.
|
||||
FrameBoundary,
|
||||
}
|
||||
|
||||
public enum SubmissionProgress
|
||||
|
|
|
|||
|
|
@ -40,16 +40,15 @@ public sealed partial class GpuCommandInterpreter
|
|||
}
|
||||
}
|
||||
|
||||
// The wait bit is PredicationZPassWaitOp: 0 (kWaitForQueryResults) stalls the command
|
||||
// processor until the results arrive, 1 (kDoNotPredicateIfQueryResultsNotReady) never
|
||||
// stalls and leaves the tagged packets unpredicated while the results are outstanding.
|
||||
internal void SetPredication(uint condition, uint operation, uint waitOperation, ulong address)
|
||||
{
|
||||
if (waitOperation != 0)
|
||||
{
|
||||
_host.FlushAndWait();
|
||||
}
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case 0:
|
||||
// Clearing predication reads no memory, so no GPU result can be outstanding.
|
||||
PredicateSkip = false;
|
||||
break;
|
||||
case 3:
|
||||
|
|
@ -59,6 +58,15 @@ public sealed partial class GpuCommandInterpreter
|
|||
throw _host.Fatal("The predication address is zero.");
|
||||
}
|
||||
|
||||
if (waitOperation != 0)
|
||||
{
|
||||
// The results are outstanding while work is queued behind this packet, and
|
||||
// the game asked for the packets to run unpredicated in that case.
|
||||
PredicateSkip = false;
|
||||
break;
|
||||
}
|
||||
|
||||
_host.FlushAndWait();
|
||||
var value = ReadQword(address);
|
||||
PredicateSkip = condition switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -318,19 +318,28 @@ public sealed class CommandStreamQueueTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Done_FromAnotherThreadWaitsForTheQueueAndCountsFrames()
|
||||
public async Task Done_QueuesTheBoundaryAndOnlyWaitsForASecondOne()
|
||||
{
|
||||
var (host, queue) = NewQueue();
|
||||
Enqueue(host, queue, Graphics, 1, CreateInstanceCountPacket(1));
|
||||
var done = Task.Run(queue.Done);
|
||||
|
||||
await Task.WhenAny(done, Task.Delay(100));
|
||||
Assert.False(done.IsCompleted);
|
||||
// The first suspend point returns without draining the accepted submissions.
|
||||
Assert.Equal(IdleOutcome.Completed, await Task.Run(queue.Done).WaitAsync(TimeSpan.FromSeconds(2)));
|
||||
Assert.Equal(0, queue.FrameNumber);
|
||||
Assert.True(queue.HasPending);
|
||||
|
||||
// The second one waits until the first boundary has been processed.
|
||||
var second = Task.Run(queue.Done);
|
||||
await Task.WhenAny(second, Task.Delay(100));
|
||||
Assert.False(second.IsCompleted);
|
||||
Assert.Equal(SliceResult.Completed, queue.ProcessOne());
|
||||
Assert.Equal(IdleOutcome.Completed, await done.WaitAsync(TimeSpan.FromSeconds(2)));
|
||||
Assert.Equal(SliceResult.Completed, queue.ProcessOne());
|
||||
Assert.Equal(IdleOutcome.Completed, await second.WaitAsync(TimeSpan.FromSeconds(2)));
|
||||
Assert.Equal(1, queue.FrameNumber);
|
||||
|
||||
// The next graphics submission starts from a reset processor.
|
||||
Assert.Equal(SliceResult.Completed, queue.ProcessOne());
|
||||
Assert.Equal(2, queue.FrameNumber);
|
||||
Enqueue(host, queue, Graphics, 2, CreateInstanceCountPacket(2));
|
||||
Assert.Equal(SliceResult.Completed, queue.ProcessOne());
|
||||
Assert.Equal(2UL, queue.GetInterpreter(0).SubmitId);
|
||||
|
|
|
|||
|
|
@ -169,14 +169,24 @@ public sealed class GpuCommandInterpreterWaitTests
|
|||
var skipped = CreateInstanceCountPacket(2);
|
||||
skipped[0] |= 1u;
|
||||
|
||||
var newLayout = StreamRunner.Packet(PacketOpcode.SetPredication, (3u << 16) | (1u << 8) | (1u << 12), StreamRunner.Low(Label), StreamRunner.High(Label));
|
||||
runner.Run(newLayout, skipped);
|
||||
// kWaitForQueryResults (0) drains the GPU and predicates on the label.
|
||||
var waitingLayout = StreamRunner.Packet(PacketOpcode.SetPredication, (3u << 16) | (1u << 8), StreamRunner.Low(Label), StreamRunner.High(Label));
|
||||
runner.Run(waitingLayout, skipped);
|
||||
Assert.Equal(new[] { "flush_and_wait" }, runner.Host.Calls);
|
||||
Assert.True(runner.Interpreter.PredicateSkip);
|
||||
Assert.Equal(1u, runner.Interpreter.InstanceCount);
|
||||
|
||||
// kDoNotPredicateIfQueryResultsNotReady (1) never drains and never skips.
|
||||
runner.Host.Calls.Clear();
|
||||
var nonWaitingLayout = StreamRunner.Packet(PacketOpcode.SetPredication, (3u << 16) | (1u << 8) | (1u << 12), StreamRunner.Low(Label), StreamRunner.High(Label));
|
||||
runner.Run(nonWaitingLayout, skipped);
|
||||
Assert.Empty(runner.Host.Calls);
|
||||
Assert.False(runner.Interpreter.PredicateSkip);
|
||||
Assert.Equal(2u, runner.Interpreter.InstanceCount);
|
||||
|
||||
var oldLayout = StreamRunner.Packet(PacketOpcode.SetPredication, StreamRunner.Low(Label), StreamRunner.High(Label) | (3u << 16));
|
||||
runner.Run(oldLayout, skipped);
|
||||
Assert.Equal(new[] { "flush_and_wait" }, runner.Host.Calls);
|
||||
Assert.False(runner.Interpreter.PredicateSkip);
|
||||
Assert.Equal(2u, runner.Interpreter.InstanceCount);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue