sharpemu/tests/SharpEmu.Libs.Tests/Gpu/GpuCommands/CommandStreamWorkerTests.cs

132 lines
5.1 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.Libs.Gpu.GpuCommands;
using Xunit;
namespace SharpEmu.Libs.Tests.Gpu.GpuCommands;
public sealed class CommandStreamWorkerTests
{
private const ulong Label = StreamRunner.LabelAddress;
private const ulong Graphics = StreamRunner.CommandAddress;
private static uint[] CreateInstanceCountPacket(uint count) => StreamRunner.Packet(PacketOpcode.NumInstances, count);
private static uint[] WaitEqual(ulong address, uint reference) =>
StreamRunner.Packet(PacketOpcode.WaitRegisterMemory, 0x13u, StreamRunner.Low(address), StreamRunner.High(address), reference, 0xFFFF_FFFFu, 0);
private static (RecordingCommandStreamHost Host, CommandStreamQueue Queue, CommandStreamWorker Worker) NewWorker(bool cancelBlockedAtStop)
{
var host = new RecordingCommandStreamHost();
var queue = new CommandStreamQueue(host);
var worker = new CommandStreamWorker(queue, host, () => host.PendingCommands.Count != 0, cancelBlockedAtStop, "test command stream");
worker.Start();
return (host, queue, worker);
}
private static void Enqueue(RecordingCommandStreamHost host, CommandStreamQueue queue, ulong submissionId, params uint[][] packets)
{
var words = StreamRunner.Concat(packets);
host.WriteWords(Graphics, words);
queue.EnqueueGraphics(Graphics, (uint)words.Length, submissionId, null);
}
[Fact]
public void Worker_ProcessesSubmissionsAndStopsWhenEmpty()
{
var (host, queue, worker) = NewWorker(cancelBlockedAtStop: false);
Enqueue(host, queue, 1, CreateInstanceCountPacket(4));
Assert.Equal(IdleOutcome.Completed, queue.WaitForIdle());
Assert.Equal(IdleOutcome.Completed, worker.Stop());
Assert.Equal(4u, queue.GetInterpreter(0).InstanceCount);
Assert.Contains("synchronize", host.Calls);
}
[Fact]
public void Worker_RetriesABlockedHeadAfterTheTimedWait()
{
var (host, queue, worker) = NewWorker(cancelBlockedAtStop: true);
host.WriteDword(Label, 0);
try
{
Enqueue(host, queue, 1, WaitEqual(Label, 1), CreateInstanceCountPacket(6));
// A loaded runner can start the worker after a fixed sleep has elapsed.
// Publish the signal only once the worker has actually blocked on it.
Assert.True(SpinWait.SpinUntil(() => queue.BlockedQueueCount == 1, TimeSpan.FromSeconds(5)));
host.WriteDword(Label, 1);
Assert.Equal(IdleOutcome.Completed, queue.WaitForIdle());
Assert.Equal(6u, queue.GetInterpreter(0).InstanceCount);
Assert.True(queue.BlockedRetries >= 1);
}
finally
{
worker.Stop();
}
}
[Fact]
public async Task Stop_CancelsAPermanentlyBlockedHeadAndReportsIt()
{
var (host, queue, worker) = NewWorker(cancelBlockedAtStop: true);
host.WriteDword(Label, 0);
Enqueue(host, queue, 1, WaitEqual(Label, 1), CreateInstanceCountPacket(6));
var waiter = Task.Factory.StartNew(
queue.WaitForIdle,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
Assert.Equal(IdleOutcome.Cancelled, worker.Stop());
Assert.Equal(IdleOutcome.Cancelled, await waiter.WaitAsync(TimeSpan.FromSeconds(2)));
Assert.Equal(1u, queue.GetInterpreter(0).InstanceCount);
}
[Fact]
public async Task FailureOutsideASlice_FailsTheQueueAndReleasesWaiters()
{
var (host, queue, worker) = NewWorker(cancelBlockedAtStop: true);
host.WriteDword(Label, 0);
Enqueue(host, queue, 1, WaitEqual(Label, 1), CreateInstanceCountPacket(6));
var waiter = Task.Run(queue.WaitForIdle);
host.PendingCommands.Enqueue(() => throw new InvalidOperationException("relay command failed"));
queue.Wake();
Assert.Equal(IdleOutcome.Failed, await waiter.WaitAsync(TimeSpan.FromSeconds(2)));
Assert.False(queue.HasPending);
Assert.Contains("relay command failed", Assert.Throws<InvalidOperationException>(() => worker.Stop()).Message);
}
[Fact]
public void Stop_DrainsCompletableWorkBeforeExiting()
{
var (host, queue, worker) = NewWorker(cancelBlockedAtStop: true);
host.WriteDword(Label, 1);
Enqueue(host, queue, 1, WaitEqual(Label, 1), CreateInstanceCountPacket(8));
Assert.Equal(IdleOutcome.Completed, worker.Stop());
Assert.Equal(8u, queue.GetInterpreter(0).InstanceCount);
}
[Fact]
public void Worker_RunsPostedCommandsBeforeSubmissions()
{
var (host, queue, worker) = NewWorker(cancelBlockedAtStop: false);
var order = new List<string>();
host.PendingCommands.Enqueue(() => order.Add("command"));
queue.Wake();
Thread.Sleep(50);
Enqueue(host, queue, 1, CreateInstanceCountPacket(2));
Assert.Equal(IdleOutcome.Completed, queue.WaitForIdle());
order.Add("submission");
worker.Stop();
Assert.Equal(new[] { "command", "submission" }, order);
}
}