mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 10:56:20 +08:00
videoout: implement output support query (#269)
Co-authored-by: Chris Cheng <chris@appxtream.com>
This commit is contained in:
@@ -23,6 +23,7 @@ public static class VideoOutExports
|
||||
private const int OrbisVideoOutErrorInvalidHandle = unchecked((int)0x8029000B);
|
||||
private const int OrbisVideoOutErrorInvalidEventQueue = unchecked((int)0x8029000C);
|
||||
private const int OrbisVideoOutErrorInvalidEvent = unchecked((int)0x8029000D);
|
||||
private const int OrbisVideoOutErrorUnsupportedOutputMode = unchecked((int)0x80290016);
|
||||
private const int OrbisVideoOutErrorInvalidOption = unchecked((int)0x8029001A);
|
||||
private const int SceVideoOutBusTypeMain = 0;
|
||||
private const int SceVideoOutBufferAttributeOptionNone = 0;
|
||||
@@ -34,8 +35,11 @@ public static class VideoOutExports
|
||||
private const int VideoOutBufferAttributeSize = 0x28;
|
||||
private const int VideoOutBufferAttribute2Size = 0x50;
|
||||
private const int VideoOutBuffersEntrySize = 0x20;
|
||||
private const int VideoOutOutputOptionsSize = 0x40;
|
||||
private const int VideoOutOutputStatusSize = 0x30;
|
||||
private const int VideoOutVblankStatusSize = 0x28;
|
||||
private const ulong SceVideoOutOutputModeDefault = 1;
|
||||
private const ulong SceVideoOutOutputMode119_88Hz = 0xF;
|
||||
private const ulong SceVideoOutPixelFormatA8R8G8B8Srgb = 0x80000000;
|
||||
private const ulong SceVideoOutPixelFormatA8B8G8R8Srgb = 0x80002200;
|
||||
private const ulong SceVideoOutPixelFormatA2R10G10B10 = 0x88060000;
|
||||
@@ -276,17 +280,46 @@ public static class VideoOutExports
|
||||
[SysAbiExport(
|
||||
Nid = "Nv8c-Kb+DUM",
|
||||
ExportName = "sceVideoOutIsOutputSupported",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutIsOutputSupported(CpuContext ctx)
|
||||
{
|
||||
var busType = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
_ = ctx[CpuRegister.Rsi]; // pixelFormat
|
||||
_ = ctx[CpuRegister.Rdx]; // aspectRatio
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var mode = ctx[CpuRegister.Rsi];
|
||||
var optionsAddress = ctx[CpuRegister.Rdx];
|
||||
var reservedPointer = ctx[CpuRegister.Rcx];
|
||||
var reserved = ctx[CpuRegister.R8];
|
||||
|
||||
// The emulator supports any output configuration on the main bus.
|
||||
// Return 1 (supported) for SceVideoOutBusTypeMain, 0 otherwise.
|
||||
return busType == SceVideoOutBusTypeMain ? 1 : 0;
|
||||
if (!TryGetPort(handle, out var port))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
if (reservedPointer != 0 || reserved != 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
}
|
||||
|
||||
if (optionsAddress != 0)
|
||||
{
|
||||
Span<byte> options = stackalloc byte[VideoOutOutputOptionsSize];
|
||||
if (!ctx.Memory.TryRead(optionsAddress, options))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (options.ContainsAnyExcept((byte)0))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidOption;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode != SceVideoOutOutputModeDefault && mode != SceVideoOutOutputMode119_88Hz)
|
||||
{
|
||||
return OrbisVideoOutErrorUnsupportedOutputMode;
|
||||
}
|
||||
|
||||
return mode == SceVideoOutOutputModeDefault || port.RefreshRate >= 119 ? 1 : 0;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
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 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);
|
||||
|
||||
[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));
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user