sharpemu/tests/SharpEmu.Libs.Tests/VideoOut/VideoOutOutputSupportTests.cs

209 lines
9.8 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.VideoOut;
using Xunit;
namespace SharpEmu.Libs.Tests.VideoOut;
public sealed class VideoOutOutputSupportTests
{
private const string OpenNid = "Up36PTk687E";
private const string CloseNid = "uquVH4-Du78";
private const string OutputSupportNid = "Nv8c-Kb+DUM";
private const string OutputStatusNid = "utPrVdxio-8";
private const string ConfigureOutputNid = "w0hLuNarQxY";
private const ulong MemoryBase = 0x1_0000_0000;
private const ulong OptionsAddress = MemoryBase + 0x100;
private static readonly ulong InvalidValue = unchecked((ulong)(int)0x80290001);
private static readonly ulong InvalidHandle = unchecked((ulong)(int)0x8029000B);
private static readonly ulong UnsupportedOutputMode = unchecked((ulong)(int)0x80290016);
private static readonly ulong InvalidOption = unchecked((ulong)(int)0x8029001A);
private static readonly ulong MemoryFault =
unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
[Theory]
[InlineData(HostHdrMode.Auto, false, true, false)]
[InlineData(HostHdrMode.Auto, true, false, false)]
[InlineData(HostHdrMode.Auto, true, true, true)]
[InlineData(HostHdrMode.On, false, true, true)]
[InlineData(HostHdrMode.On, true, true, true)]
[InlineData(HostHdrMode.On, true, false, false)]
[InlineData(HostHdrMode.Off, true, true, false)]
public void OutputStatusReportsHdrOnlyWhenAllowedAndSupported(
HostHdrMode mode, bool displayHdrEnabled, bool surfaceSupportsHdr, bool expectedHdr)
{
var supported = new HostVideoOptions { HdrMode = mode }.CanUseHdr(displayHdrEnabled, surfaceSupportsHdr);
Assert.Equal(expectedHdr, supported);
var bytes = Enumerable.Repeat((byte)0xA5, 0x32).ToArray();
VideoOutExports.WriteOutputStatus(bytes.AsSpan(1, 0x30), 3840, 2160, 120, supported);
Assert.Equal(0xA5, bytes[0]);
Assert.Equal(0xA5, bytes[^1]);
var status = bytes.AsSpan(1, 0x30);
Assert.Equal(2U, BinaryPrimitives.ReadUInt32LittleEndian(status));
Assert.Equal(expectedHdr ? 2U : 1U, BinaryPrimitives.ReadUInt32LittleEndian(status[4..]));
Assert.Equal(13UL, BinaryPrimitives.ReadUInt64LittleEndian(status[8..]));
Assert.Equal(expectedHdr ? 1UL : 0UL, BinaryPrimitives.ReadUInt64LittleEndian(status[0x10..]));
Assert.All(status[0x18..].ToArray(), value => Assert.Equal(0, value));
}
[Theory]
[InlineData(Generation.Gen4)]
[InlineData(Generation.Gen5)]
public void OutputStatusUsesRefreshRateCodeAndPreservesBufferBoundaries(Generation generation)
{
var manager = new ModuleManager();
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(generation));
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
var context = new CpuContext(memory, generation);
Assert.True(manager.TryDispatch(OpenNid, context, out _));
var handle = context[CpuRegister.Rax];
Assert.InRange(handle, 1UL, (ulong)int.MaxValue);
try
{
var buffer = Enumerable.Repeat((byte)0xA5, 0x32).ToArray();
Assert.True(memory.TryWrite(OptionsAddress, buffer));
context[CpuRegister.Rdi] = handle;
context[CpuRegister.Rsi] = OptionsAddress + 1;
Assert.True(manager.TryDispatch(OutputStatusNid, context, out _));
Assert.Equal(0UL, context[CpuRegister.Rax]);
Assert.True(memory.TryRead(OptionsAddress, buffer));
var expected = new byte[0x32];
expected[0] = 0xA5;
expected[^1] = 0xA5;
BinaryPrimitives.WriteUInt32LittleEndian(expected.AsSpan(1), 1);
BinaryPrimitives.WriteUInt32LittleEndian(expected.AsSpan(5), 1);
BinaryPrimitives.WriteUInt64LittleEndian(expected.AsSpan(9), 3);
Assert.Equal(expected, buffer);
context[CpuRegister.Rsi] = 0;
Assert.True(manager.TryDispatch(OutputStatusNid, context, out _));
Assert.Equal(unchecked((ulong)(int)0x80290002), context[CpuRegister.Rax]);
context[CpuRegister.Rsi] = MemoryBase + 0xFF0;
Assert.True(manager.TryDispatch(OutputStatusNid, context, out _));
Assert.Equal(MemoryFault, context[CpuRegister.Rax]);
context[CpuRegister.Rsi] = OptionsAddress + 1;
context[CpuRegister.Rdi] = ulong.MaxValue;
Assert.True(manager.TryDispatch(OutputStatusNid, context, out _));
Assert.Equal(InvalidHandle, context[CpuRegister.Rax]);
}
finally
{
context[CpuRegister.Rdi] = handle;
Assert.True(manager.TryDispatch(CloseNid, context, out _));
}
}
[Fact]
public void Gen5QueryReportsCapabilitiesAndValidatesArguments()
{
var gen4Manager = new ModuleManager();
gen4Manager.RegisterExports(
SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen4));
Assert.False(gen4Manager.TryGetExport(OutputSupportNid, out _));
var manager = new ModuleManager();
manager.RegisterExports(
SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5));
Assert.True(manager.TryGetExport(OutputSupportNid, out var export));
Assert.Equal("sceVideoOutIsOutputSupported", export.Name);
Assert.Equal("libSceVideoOut", export.LibraryName);
Assert.Equal(Generation.Gen5, export.Target);
var memory = new FakeCpuMemory(MemoryBase, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
context[CpuRegister.Rdi] = 0;
context[CpuRegister.Rsi] = 0;
context[CpuRegister.Rdx] = 0;
context[CpuRegister.Rcx] = 0;
Assert.True(manager.TryDispatch(OpenNid, context, out _));
var handle = context[CpuRegister.Rax];
Assert.NotEqual(0UL, handle);
try
{
Assert.Equal(1UL, DispatchOutputSupport(manager, context, handle, 1));
Assert.Equal(0UL, DispatchOutputSupport(manager, context, handle, 15));
Assert.Equal(
InvalidHandle,
DispatchOutputSupport(manager, context, ulong.MaxValue, 1));
Assert.Equal(
InvalidValue,
DispatchOutputSupport(manager, context, handle, 1, reservedPointer: 1));
Assert.Equal(
InvalidValue,
DispatchOutputSupport(manager, context, handle, 1, reserved: 1));
Assert.Equal(
1UL,
DispatchOutputSupport(manager, context, handle, 1, OptionsAddress));
Assert.Equal(
MemoryFault,
DispatchOutputSupport(manager, context, handle, 1, MemoryBase + 0x1000));
Assert.True(memory.TryWrite(OptionsAddress, new byte[] { 1 }));
Assert.Equal(
InvalidOption,
DispatchOutputSupport(manager, context, handle, 1, OptionsAddress));
Assert.Equal(
UnsupportedOutputMode,
DispatchOutputSupport(manager, context, handle, 2));
// Configuration must use the same validation as the capability query.
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
Assert.Equal(UnsupportedOutputMode, context[CpuRegister.Rax]);
DispatchOutputSupport(manager, context, handle, 1, OptionsAddress);
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
Assert.Equal(InvalidOption, context[CpuRegister.Rax]);
Assert.True(memory.TryWrite(OptionsAddress, new byte[0x40]));
DispatchOutputSupport(manager, context, handle, 1, OptionsAddress);
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
Assert.Equal(0UL, context[CpuRegister.Rax]);
DispatchOutputSupport(manager, context, handle, 15);
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
Assert.Equal(unchecked((ulong)(int)0x80290019), context[CpuRegister.Rax]);
DispatchOutputSupport(manager, context, handle, 1, reserved: 1);
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
Assert.Equal(InvalidValue, context[CpuRegister.Rax]);
DispatchOutputSupport(manager, context, handle, 1, reservedPointer: 1);
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
Assert.Equal(InvalidValue, context[CpuRegister.Rax]);
DispatchOutputSupport(manager, context, handle, 1, MemoryBase + 0x1000);
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
Assert.Equal(MemoryFault, context[CpuRegister.Rax]);
DispatchOutputSupport(manager, context, ulong.MaxValue, 1);
Assert.True(manager.TryDispatch(ConfigureOutputNid, context, out _));
Assert.Equal(InvalidHandle, context[CpuRegister.Rax]);
}
finally
{
context[CpuRegister.Rdi] = handle;
_ = manager.TryDispatch(CloseNid, context, out _);
}
}
private static ulong DispatchOutputSupport(
ModuleManager manager,
CpuContext context,
ulong handle,
ulong mode,
ulong optionsAddress = 0,
ulong reservedPointer = 0,
ulong reserved = 0)
{
context[CpuRegister.Rdi] = handle;
context[CpuRegister.Rsi] = mode;
context[CpuRegister.Rdx] = optionsAddress;
context[CpuRegister.Rcx] = reservedPointer;
context[CpuRegister.R8] = reserved;
context[CpuRegister.R9] = 0x1FC;
Assert.True(manager.TryDispatch(OutputSupportNid, context, out _));
return context[CpuRegister.Rax];
}
}