170 lines
6.4 KiB
C#
170 lines
6.4 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Buffers.Binary;
|
|
using SharpEmu.HLE;
|
|
using SharpEmu.Libs.Tests.Gpu.Scheduling;
|
|
using Xunit;
|
|
|
|
namespace SharpEmu.Libs.Tests.VideoOut;
|
|
|
|
[Collection(SchedulingStateCollection.Name)]
|
|
public sealed class VideoOutLatencyTests
|
|
{
|
|
private const string OpenNid = "Up36PTk687E";
|
|
private const string CloseNid = "uquVH4-Du78";
|
|
private const string SubmitFlipNid = "U46NwOiJpys";
|
|
private const string WaitBeforeInputNid = "eb-gvTYQcoY";
|
|
private const string SetStartPointNid = "MCJ8SkzsQxY";
|
|
private const ulong MemoryBase = 0x1_0000_0000;
|
|
private const ulong ControlAddress = MemoryBase + 0x100;
|
|
private const ulong TimeoutAddress = MemoryBase + 0x180;
|
|
private static readonly ulong InvalidValue = unchecked((ulong)(int)0x80290001);
|
|
private static readonly ulong InvalidHandle = unchecked((ulong)(int)0x8029000B);
|
|
private static readonly ulong TimedOut = unchecked(
|
|
(ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
|
|
|
[Fact]
|
|
public void Gen5LatencyExportsTrackCompletedFlipArguments()
|
|
{
|
|
var manager = CreateManager();
|
|
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
|
|
var context = new CpuContext(memory, Generation.Gen5);
|
|
var handle = Open(manager, context);
|
|
|
|
try
|
|
{
|
|
context[CpuRegister.Rdi] = handle;
|
|
context[CpuRegister.Rsi] = 42;
|
|
Assert.True(manager.TryDispatch(SetStartPointNid, context, out _));
|
|
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
|
|
|
context[CpuRegister.Rdi] = handle;
|
|
context[CpuRegister.Rsi] = ulong.MaxValue;
|
|
context[CpuRegister.Rdx] = 1;
|
|
context[CpuRegister.Rcx] = 42;
|
|
Assert.True(manager.TryDispatch(SubmitFlipNid, context, out _));
|
|
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
|
|
|
WriteControl(memory, control: 1, target: 42, extraUsec: 0);
|
|
context[CpuRegister.Rdi] = handle;
|
|
context[CpuRegister.Rsi] = ControlAddress;
|
|
context[CpuRegister.Rdx] = 0;
|
|
Assert.True(manager.TryDispatch(WaitBeforeInputNid, context, out _));
|
|
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
|
}
|
|
finally
|
|
{
|
|
Close(manager, context, handle);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void WaitBeforeInputValidatesControlAndHonorsZeroTimeout()
|
|
{
|
|
var manager = CreateManager();
|
|
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
|
|
var context = new CpuContext(memory, Generation.Gen5);
|
|
var handle = Open(manager, context);
|
|
|
|
try
|
|
{
|
|
WriteControl(memory, control: 2, target: 0, extraUsec: 0);
|
|
Assert.Equal(InvalidValue, Wait(manager, context, handle, ControlAddress, 0));
|
|
|
|
WriteControl(memory, control: 0, target: 0, extraUsec: 100_001);
|
|
Assert.Equal(InvalidValue, Wait(manager, context, handle, ControlAddress, 0));
|
|
|
|
WriteControl(memory, control: 1, target: 99, extraUsec: 0);
|
|
Span<byte> timeout = stackalloc byte[sizeof(uint)];
|
|
timeout.Clear();
|
|
Assert.True(memory.TryWrite(TimeoutAddress, timeout));
|
|
Assert.Equal(TimedOut, Wait(manager, context, handle, ControlAddress, TimeoutAddress));
|
|
|
|
Assert.Equal(InvalidHandle, Wait(manager, context, ulong.MaxValue, ControlAddress, 0));
|
|
}
|
|
finally
|
|
{
|
|
Close(manager, context, handle);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClosingPortReleasesBlockedLatencyWait()
|
|
{
|
|
var manager = CreateManager();
|
|
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
|
|
var openContext = new CpuContext(memory, Generation.Gen5);
|
|
var waitContext = new CpuContext(memory, Generation.Gen5);
|
|
var closeContext = new CpuContext(memory, Generation.Gen5);
|
|
var handle = Open(manager, openContext);
|
|
|
|
WriteControl(memory, control: 1, target: 99, extraUsec: 0);
|
|
var waitTask = Task.Run(
|
|
() => Wait(manager, waitContext, handle, ControlAddress, 0));
|
|
|
|
await Task.Delay(50);
|
|
Assert.False(waitTask.IsCompleted);
|
|
Close(manager, closeContext, handle);
|
|
|
|
Assert.Equal(InvalidHandle, await waitTask.WaitAsync(TimeSpan.FromSeconds(2)));
|
|
}
|
|
|
|
private static ModuleManager CreateManager()
|
|
{
|
|
var manager = new ModuleManager();
|
|
manager.RegisterExports(
|
|
SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5));
|
|
Assert.True(manager.TryGetExport(WaitBeforeInputNid, out var wait));
|
|
Assert.Equal("sceVideoOutLatencyControlWaitBeforeInput", wait.Name);
|
|
Assert.True(manager.TryGetExport(SetStartPointNid, out var start));
|
|
Assert.Equal("sceVideoOutLatencyMeasureSetStartPoint", start.Name);
|
|
return manager;
|
|
}
|
|
|
|
private static ulong Open(ModuleManager manager, CpuContext context)
|
|
{
|
|
context[CpuRegister.Rdi] = 0;
|
|
context[CpuRegister.Rsi] = 0;
|
|
context[CpuRegister.Rdx] = 0;
|
|
context[CpuRegister.Rcx] = 0;
|
|
Assert.True(manager.TryDispatch(OpenNid, context, out _));
|
|
Assert.NotEqual(0UL, context[CpuRegister.Rax]);
|
|
return context[CpuRegister.Rax];
|
|
}
|
|
|
|
private static void Close(ModuleManager manager, CpuContext context, ulong handle)
|
|
{
|
|
context[CpuRegister.Rdi] = handle;
|
|
_ = manager.TryDispatch(CloseNid, context, out _);
|
|
}
|
|
|
|
private static ulong Wait(
|
|
ModuleManager manager,
|
|
CpuContext context,
|
|
ulong handle,
|
|
ulong controlAddress,
|
|
ulong timeoutAddress)
|
|
{
|
|
context[CpuRegister.Rdi] = handle;
|
|
context[CpuRegister.Rsi] = controlAddress;
|
|
context[CpuRegister.Rdx] = timeoutAddress;
|
|
Assert.True(manager.TryDispatch(WaitBeforeInputNid, context, out _));
|
|
return context[CpuRegister.Rax];
|
|
}
|
|
|
|
private static void WriteControl(
|
|
FakeCpuMemory memory,
|
|
uint control,
|
|
long target,
|
|
uint extraUsec)
|
|
{
|
|
Span<byte> bytes = stackalloc byte[0x20];
|
|
bytes.Clear();
|
|
BinaryPrimitives.WriteUInt32LittleEndian(bytes[0x00..0x04], control);
|
|
BinaryPrimitives.WriteInt64LittleEndian(bytes[0x08..0x10], target);
|
|
BinaryPrimitives.WriteUInt32LittleEndian(bytes[0x10..0x14], extraUsec);
|
|
Assert.True(memory.TryWrite(ControlAddress, bytes));
|
|
}
|
|
}
|