[Gpu] Expose HDR output support and handle PQ buffer attribute changes
This commit is contained in:
parent
f199acd42f
commit
e7a4f0a0ce
|
|
@ -457,7 +457,7 @@ public sealed record DepthFormatRule(GuestDepthFormat DepthFormat, GuestPixelFor
|
|||
|
||||
public readonly record struct DisplayPixelFormat(Format HostFormat, GuestPixelFormat GuestFormat, uint BytesPerElement, bool Bgra16);
|
||||
|
||||
// The six display surface pixel formats the store accepts.
|
||||
// Display formats retain their encoded values; presentation applies the transfer function.
|
||||
public static class DisplayFormatRule
|
||||
{
|
||||
public static readonly (ulong PixelFormat, DisplayPixelFormat Info)[] Table =
|
||||
|
|
@ -466,6 +466,10 @@ public static class DisplayFormatRule
|
|||
(0x8000000000000000, new DisplayPixelFormat(Format.B8G8R8A8Srgb, GuestPixelFormat.Bits8_8_8_8Srgb, 4, false)),
|
||||
(0x8100000022000000, new DisplayPixelFormat(Format.A2B10G10R10UnormPack32, GuestPixelFormat.Bits10_10_10_2UNorm, 4, false)),
|
||||
(0x8100000000000000, new DisplayPixelFormat(Format.A2R10G10B10UnormPack32, GuestPixelFormat.Bits10_10_10_2UNorm, 4, false)),
|
||||
(0x8100000622000000, new DisplayPixelFormat(Format.A2B10G10R10UnormPack32, GuestPixelFormat.Bits10_10_10_2UNorm, 4, false)),
|
||||
(0x8100000600000000, new DisplayPixelFormat(Format.A2R10G10B10UnormPack32, GuestPixelFormat.Bits10_10_10_2UNorm, 4, false)),
|
||||
(0x8100070422000000, new DisplayPixelFormat(Format.A2B10G10R10UnormPack32, GuestPixelFormat.Bits10_10_10_2UNorm, 4, false)),
|
||||
(0x8100070400000000, new DisplayPixelFormat(Format.A2R10G10B10UnormPack32, GuestPixelFormat.Bits10_10_10_2UNorm, 4, false)),
|
||||
(0xc001000622000000, new DisplayPixelFormat(Format.R16G16B16A16Sfloat, GuestPixelFormat.Bits16_16_16_16Float, 8, false)),
|
||||
(0xc001000600000000, new DisplayPixelFormat(Format.R16G16B16A16Sfloat, GuestPixelFormat.Bits16_16_16_16Float, 8, true)),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -231,10 +231,10 @@ public static partial class ImageRequestBuilders
|
|||
// Builds the request for a video-out surface registered by the guest.
|
||||
public static ImageRequest DisplaySurface(in DisplaySurfaceWords surface)
|
||||
{
|
||||
const ulong StrictColorimetryOption = 1;
|
||||
const ulong SupportedOptions = (1UL << 3) | (1UL << 5); // Strict colorimetry and premultiplied alpha.
|
||||
var compression = ImageDescription.ClassifyDisplayCompression(surface.Compressed, surface.MetadataAddress, surface.DccControl, surface.DccClearColor);
|
||||
if (surface.Width == 0 || surface.Height == 0 || surface.Width > 16384 || surface.Height > 16384 ||
|
||||
(surface.Option != 0 && surface.Option != StrictColorimetryOption) || surface.TilingMode != 0 || surface.DataAddress == 0 ||
|
||||
(surface.Option & ~SupportedOptions) != 0 || surface.TilingMode != 0 || surface.DataAddress == 0 ||
|
||||
compression == DisplayCompression.Unsupported)
|
||||
{
|
||||
throw SubmissionScheduler.Fatal(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
namespace SharpEmu.Libs.VideoOut;
|
||||
|
||||
using SharpEmu.Libs.Gpu.Metal;
|
||||
using SharpEmu.Libs.Gpu;
|
||||
|
||||
public enum HostWindowMode
|
||||
{
|
||||
|
|
@ -66,6 +67,9 @@ public sealed record HostVideoOptions
|
|||
public PerformanceOverlayCorner OverlayCorner { get; init; } = PerformanceOverlayCorner.TopRight;
|
||||
public PerformanceOverlayMode OverlayMode { get; init; } = PerformanceOverlayMode.TitleBar;
|
||||
|
||||
internal bool CanUseHdr(bool displayHdrEnabled, bool surfaceSupportsHdr) =>
|
||||
surfaceSupportsHdr && (HdrMode == HostHdrMode.On || HdrMode == HostHdrMode.Auto && displayHdrEnabled);
|
||||
|
||||
public HostVideoOptions Normalize() => this with
|
||||
{
|
||||
Width = Math.Clamp(Width, 640, 16384),
|
||||
|
|
@ -81,14 +85,22 @@ public sealed record HostVideoOptions
|
|||
public static class HostVideoHost
|
||||
{
|
||||
private static HostVideoOptions _currentOptions = HostVideoOptions.Default;
|
||||
private static bool _videoConfigured;
|
||||
|
||||
public static HostVideoOptions CurrentOptions => Volatile.Read(ref _currentOptions);
|
||||
|
||||
internal static bool IsHdrOutputSupported =>
|
||||
Volatile.Read(ref _videoConfigured) && CurrentOptions.HdrMode != HostHdrMode.Off &&
|
||||
GuestGpu.Current.BackendName == "Vulkan" && VulkanVideoPresenter.QueryHdrOutputSupport();
|
||||
|
||||
public static bool TryConfigureVideo(HostVideoOptions options)
|
||||
{
|
||||
var normalized = options.Normalize();
|
||||
Volatile.Write(ref _currentOptions, normalized);
|
||||
return VulkanVideoPresenter.TryConfigureVideo(normalized) &
|
||||
MetalVideoPresenter.TryConfigureVideo(normalized);
|
||||
var configured = VulkanVideoPresenter.TryConfigureVideo(normalized) &
|
||||
MetalVideoPresenter.TryConfigureVideo(normalized);
|
||||
if (configured)
|
||||
Volatile.Write(ref _videoConfigured, true);
|
||||
return configured;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using SharpEmu.Logging;
|
|||
using SharpEmu.HLE.Host;
|
||||
using SharpEmu.Libs.Diagnostics;
|
||||
using SharpEmu.Libs.Gpu;
|
||||
using SharpEmu.Libs.Gpu.Images;
|
||||
using SharpEmu.Libs.Audio;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using System.Buffers;
|
||||
|
|
@ -297,7 +298,8 @@ public static partial class VideoOutExports
|
|||
uint TilingMode,
|
||||
uint Width,
|
||||
uint Height,
|
||||
uint PitchInPixel);
|
||||
uint PitchInPixel,
|
||||
ulong Option = 0);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Up36PTk687E",
|
||||
|
|
@ -514,20 +516,28 @@ public static partial class VideoOutExports
|
|||
}
|
||||
|
||||
Span<byte> status = stackalloc byte[VideoOutOutputStatusSize];
|
||||
status.Clear();
|
||||
var resolutionClass = port.OutputWidth >= 3840 || port.OutputHeight >= 2160 ? 2 : 1;
|
||||
BinaryPrimitives.WriteInt32LittleEndian(status[0x00..0x04], resolutionClass);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(status[0x04..0x08], 1);
|
||||
// The status uses a refresh-rate code, not the frequency used for pacing.
|
||||
var refreshRateCode = port.RefreshRate >= 119
|
||||
? SceVideoOutRefreshRate119_88Hz
|
||||
: SceVideoOutRefreshRate59_94Hz;
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(status[0x08..0x10], refreshRateCode);
|
||||
WriteOutputStatus(status, port.OutputWidth, port.OutputHeight, port.RefreshRate,
|
||||
HostVideoHost.IsHdrOutputSupported);
|
||||
return ctx.Memory.TryWrite(statusAddress, status)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
internal static void WriteOutputStatus(Span<byte> status, uint width, uint height, uint refreshRate,
|
||||
bool hdrOutputSupported)
|
||||
{
|
||||
status.Clear();
|
||||
var resolutionClass = width >= 3840 || height >= 2160 ? 2 : 1;
|
||||
BinaryPrimitives.WriteInt32LittleEndian(status[0x00..0x04], resolutionClass);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(status[0x04..0x08], hdrOutputSupported ? 2 : 1);
|
||||
// The status uses a refresh-rate code, not the frequency used for pacing.
|
||||
var refreshRateCode = refreshRate >= 119
|
||||
? SceVideoOutRefreshRate119_88Hz
|
||||
: SceVideoOutRefreshRate59_94Hz;
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(status[0x08..0x10], refreshRateCode);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(status[0x10..0x18], hdrOutputSupported ? 1UL : 0UL);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "DYhhWbJSeRg",
|
||||
ExportName = "sceVideoOutColorSettingsSetGamma_",
|
||||
|
|
@ -1199,7 +1209,8 @@ public static partial class VideoOutExports
|
|||
attribute.TilingMode,
|
||||
attribute.Width,
|
||||
attribute.Height,
|
||||
attribute.PitchInPixel);
|
||||
attribute.PitchInPixel,
|
||||
attribute.Option);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1348,6 +1359,50 @@ public static partial class VideoOutExports
|
|||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "HuViW4HnrOw",
|
||||
ExportName = "sceVideoOutSubmitChangeBufferAttribute2",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutSubmitChangeBufferAttribute2(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var setIndex = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var attributeAddress = ctx[CpuRegister.Rdx];
|
||||
var option = ctx[CpuRegister.Rcx];
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_ports.TryGetValue(handle, out var port)) return OrbisVideoOutErrorInvalidHandle;
|
||||
if (attributeAddress == 0) return OrbisVideoOutErrorInvalidOption;
|
||||
if ((uint)setIndex >= MaxDisplayBufferGroups) return OrbisVideoOutErrorInvalidIndex;
|
||||
if (option != 0) return OrbisVideoOutErrorInvalidOption;
|
||||
if (port.Groups[setIndex] is null) return OrbisVideoOutErrorInvalidIndex;
|
||||
if (!TryReadBufferAttribute(ctx, attributeAddress, true, out var attribute))
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
if (attribute.Width is 0 or > 16384 || attribute.Height is 0 or > 16384 ||
|
||||
attribute.TilingMode != 0 || (attribute.Option & ~((1UL << 3) | (1UL << 5))) != 0 ||
|
||||
!DisplayFormatRule.TryDecode(attribute.PixelFormat, out _))
|
||||
return OrbisVideoOutErrorInvalidValue;
|
||||
|
||||
// Keep buffer addresses and membership when the guest switches SDR/HDR formats.
|
||||
port.Groups[setIndex] = new VideoOutBufferGroup { Index = setIndex, Attribute = attribute };
|
||||
port.OutputWidth = attribute.Width;
|
||||
port.OutputHeight = attribute.Height;
|
||||
var guestFormat = MapPixelFormatToGuestTextureFormat(attribute.PixelFormat);
|
||||
foreach (var slot in port.BufferSlots)
|
||||
{
|
||||
if (slot.GroupIndex != setIndex) continue;
|
||||
GuestGpu.Current.RegisterKnownDisplayBuffer(slot.AddressLeft, guestFormat);
|
||||
if (slot.AddressRight != 0)
|
||||
GuestGpu.Current.RegisterKnownDisplayBuffer(slot.AddressRight, guestFormat);
|
||||
}
|
||||
|
||||
TraceVideoOut($"videoout.change_buffer_attribute2 handle={handle} set={setIndex} " +
|
||||
$"fmt=0x{attribute.PixelFormat:X16} tile={attribute.TilingMode} {attribute.Width}x{attribute.Height}");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "w3BY+tAEiQY",
|
||||
ExportName = "sceVideoOutRegisterBuffers",
|
||||
|
|
|
|||
|
|
@ -549,7 +549,7 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
try
|
||||
{
|
||||
var surface = new DisplaySurfaceWords(
|
||||
displayBuffer.Address, 0, displayBuffer.PixelFormat, displayBuffer.Width, displayBuffer.Height, displayBuffer.TilingMode, 0, 0, 0, false);
|
||||
displayBuffer.Address, 0, displayBuffer.PixelFormat, displayBuffer.Width, displayBuffer.Height, displayBuffer.TilingMode, displayBuffer.Option, 0, 0, false);
|
||||
var request = ImageRequestBuilders.DisplaySurface(surface);
|
||||
_ = BeginBatchedGuestCommands();
|
||||
var imageIdentifier = _imageCache.FindImage(ref request);
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
}
|
||||
|
||||
var hdrState = _window.HdrState;
|
||||
_ = ChooseSurfaceFormat(formats, requestHdr: true, out var surfaceSupportsHdr);
|
||||
var hdrOutputSupported = _videoOptions.CanUseHdr(hdrState.Enabled, surfaceSupportsHdr);
|
||||
var guestHdrRequested = VideoOutExports.IsHdrOutputRequested;
|
||||
var requestHdr = _videoOptions.HdrMode switch
|
||||
{
|
||||
|
|
@ -140,7 +142,8 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
_imageInitialized = new bool[swapchainImageCount];
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Vulkan output color: requested={_videoOptions.HdrMode} " +
|
||||
$"display_hdr={hdrState.Enabled} guest_hdr={guestHdrRequested} active={_hdrOutputActive} " +
|
||||
$"display_hdr={hdrState.Enabled} guest_hdr_supported={hdrOutputSupported} " +
|
||||
$"guest_hdr={guestHdrRequested} active={_hdrOutputActive} " +
|
||||
$"format={_swapchainFormat} colorspace={_swapchainColorSpace} " +
|
||||
$"sdr_white={_hdrSdrWhiteLevel:F3} headroom={_hdrHeadroom:F3}");
|
||||
if (requestHdr && !_hdrOutputActive)
|
||||
|
|
@ -148,6 +151,13 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] HDR output requested but the Vulkan surface exposes no scRGB format; using SDR.");
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
_hdrOutputSupported = hdrOutputSupported;
|
||||
_hdrCapabilitiesReady = true;
|
||||
System.Threading.Monitor.PulseAll(_gate);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateGuestDrawResources()
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
private static uint _windowHeight;
|
||||
private static bool _closed;
|
||||
private static bool _presenterCloseRequested;
|
||||
private static bool _hdrCapabilitiesReady;
|
||||
private static bool _hdrOutputSupported;
|
||||
|
||||
private static bool _splashHidden;
|
||||
|
||||
|
|
@ -121,10 +123,30 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
}
|
||||
|
||||
_videoOptions = options.Normalize();
|
||||
_hdrCapabilitiesReady = false;
|
||||
_hdrOutputSupported = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool QueryHdrOutputSupport()
|
||||
{
|
||||
// The guest can query HDR before registering its first display buffer.
|
||||
EnsureStarted((uint)_videoOptions.Width, (uint)_videoOptions.Height);
|
||||
lock (_gate)
|
||||
{
|
||||
while (!_hdrCapabilitiesReady && !_closed && _presenterStartupFailure is null &&
|
||||
!HostSessionControl.IsShutdownRequested && !Volatile.Read(ref _presenterCloseRequested))
|
||||
{
|
||||
System.Threading.Monitor.Wait(_gate);
|
||||
}
|
||||
|
||||
return _hdrCapabilitiesReady && !_closed && _presenterStartupFailure is null &&
|
||||
!HostSessionControl.IsShutdownRequested && !Volatile.Read(ref _presenterCloseRequested) &&
|
||||
_hdrOutputSupported;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetHostSessionStateLocked()
|
||||
{
|
||||
_latestPresentation = null;
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public sealed class ImageDescriptionTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayFormats_DecodeTheSixPixelFormats()
|
||||
public void DisplayFormats_DecodeSupportedPixelFormats()
|
||||
{
|
||||
Assert.True(DisplayFormatRule.TryDecode(0x8000000022000000, out var rgba));
|
||||
Assert.Equal((Format.R8G8B8A8Srgb, GuestPixelFormat.Bits8_8_8_8Srgb, 4u, false), (rgba.HostFormat, rgba.GuestFormat, rgba.BytesPerElement, rgba.Bgra16));
|
||||
|
|
|
|||
|
|
@ -361,6 +361,22 @@ public sealed class ImageRequestBuildersTests : IClassFixture<HeadlessVulkanFixt
|
|||
Assert.Equal(ImageUsageFlags.TransferSrcBit, request.View.Usage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x8100070422000000UL, Format.A2B10G10R10UnormPack32, 8UL)]
|
||||
[InlineData(0x8100070400000000UL, Format.A2R10G10B10UnormPack32, 40UL)]
|
||||
[InlineData(0x8100000622000000UL, Format.A2B10G10R10UnormPack32, 32UL)]
|
||||
[InlineData(0x8100000600000000UL, Format.A2R10G10B10UnormPack32, 0UL)]
|
||||
public void DisplaySurface_Packed10BitFormatsPreserveEncodedValues(ulong pixelFormat, Format expectedFormat, ulong option)
|
||||
{
|
||||
var surface = new DisplaySurfaceWords(Base, 0, pixelFormat, 3840, 2160, 0, option, 0, 0, false);
|
||||
var request = ImageRequestBuilders.DisplaySurface(surface);
|
||||
Assert.Equal(expectedFormat, request.Description.PixelFormat);
|
||||
Assert.Equal(GuestPixelFormat.Bits10_10_10_2UNorm, request.Description.GuestFormat);
|
||||
Assert.Equal(4u, request.Description.BytesPerBlock);
|
||||
Assert.Equal(expectedFormat, request.View.Format);
|
||||
Assert.True(DisplayFormatRule.Supports(request.Description));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplaySurface_RejectsUnsupportedAttributes()
|
||||
{
|
||||
|
|
@ -368,7 +384,8 @@ public sealed class ImageRequestBuildersTests : IClassFixture<HeadlessVulkanFixt
|
|||
Assert.Throws<SchedulerFatalException>(() => ImageRequestBuilders.DisplaySurface(new DisplaySurfaceWords(Base, 0, 0x8000000022000000, 1920, 1080, 1, 0, 0, 0, false)));
|
||||
Assert.Throws<SchedulerFatalException>(() => ImageRequestBuilders.DisplaySurface(new DisplaySurfaceWords(Base, 0, 0x1234, 1920, 1080, 0, 0, 0, 0, false)));
|
||||
Assert.Throws<SchedulerFatalException>(() => ImageRequestBuilders.DisplaySurface(new DisplaySurfaceWords(Base, 0, 0x8000000022000000, 0, 1080, 0, 0, 0, 0, false)));
|
||||
Assert.Equal(3, fatal.Messages.Count);
|
||||
Assert.Throws<SchedulerFatalException>(() => ImageRequestBuilders.DisplaySurface(new DisplaySurfaceWords(Base, 0, 0x8100070422000000, 1920, 1080, 0, 1, 0, 0, false)));
|
||||
Assert.Equal(4, fatal.Messages.Count);
|
||||
}
|
||||
|
||||
private readonly record struct GuestSpanCheck(ulong Address, ulong Size);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,62 @@ public sealed class VideoOutFlipRequestTests : IDisposable
|
|||
_ = VideoOutExports.VideoOutClose(_context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangeBufferAttribute2UpdatesTheRegisteredGroupAndValidatesArguments()
|
||||
{
|
||||
var manager = new ModuleManager();
|
||||
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(Generation.Gen5));
|
||||
Assert.True(manager.TryGetExport("HuViW4HnrOw", out var export));
|
||||
Assert.Equal("sceVideoOutSubmitChangeBufferAttribute2", export.Name);
|
||||
|
||||
var ports = (System.Collections.IDictionary)typeof(VideoOutExports)
|
||||
.GetField("_ports", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!.GetValue(null)!;
|
||||
var port = ports[_handle]!;
|
||||
// Seed registration without starting a native presenter in this state test.
|
||||
var groups = (Array)port.GetType().GetProperty("Groups")!.GetValue(port)!;
|
||||
groups.SetValue(Activator.CreateInstance(groups.GetType().GetElementType()!, nonPublic: true), 0);
|
||||
var slots = (Array)port.GetType().GetProperty("BufferSlots")!.GetValue(port)!;
|
||||
var slot = slots.GetValue(0)!;
|
||||
slot.GetType().GetProperty("GroupIndex")!.SetValue(slot, 0);
|
||||
slot.GetType().GetProperty("AddressLeft")!.SetValue(slot, MemoryBase);
|
||||
|
||||
const ulong pqFormat = 0x8100070422000000;
|
||||
var attribute = new byte[0x50];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute.AsSpan(0x0C), 3840);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute.AsSpan(0x10), 2160);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(attribute.AsSpan(0x18), 8);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(attribute.AsSpan(0x20), pqFormat);
|
||||
Assert.True(_memory.TryWrite(StatusAddress, attribute));
|
||||
|
||||
ulong Change(int setIndex = 0, ulong address = StatusAddress, ulong option = 0, int? handle = null)
|
||||
{
|
||||
_context[CpuRegister.Rdi] = unchecked((ulong)(handle ?? _handle));
|
||||
_context[CpuRegister.Rsi] = unchecked((ulong)setIndex);
|
||||
_context[CpuRegister.Rdx] = address;
|
||||
_context[CpuRegister.Rcx] = option;
|
||||
Assert.True(manager.TryDispatch("HuViW4HnrOw", _context, out _));
|
||||
return _context[CpuRegister.Rax];
|
||||
}
|
||||
|
||||
Assert.Equal(0UL, Change());
|
||||
Assert.True(VideoOutExports.TryGetDisplayBufferInfo(_handle, 0, out var info));
|
||||
Assert.Equal(new VideoOutExports.DisplayBufferInfo(MemoryBase, pqFormat, 0, 3840, 2160, 3840, 8), info);
|
||||
Assert.Equal(3840u, port.GetType().GetProperty("OutputWidth")!.GetValue(port));
|
||||
Assert.Equal(2160u, port.GetType().GetProperty("OutputHeight")!.GetValue(port));
|
||||
Assert.Equal(unchecked((ulong)(int)0x8029000B), Change(handle: -1));
|
||||
Assert.Equal(unchecked((ulong)(int)0x8029001A), Change(address: 0));
|
||||
Assert.Equal(unchecked((ulong)(int)0x8029001A), Change(option: 1));
|
||||
Assert.Equal(unchecked((ulong)(int)0x8029000A), Change(setIndex: -1));
|
||||
Assert.Equal(unchecked((ulong)(int)0x8029000A), Change(setIndex: 4));
|
||||
Assert.Equal(unchecked((ulong)(int)0x8029000A), Change(setIndex: 1));
|
||||
Assert.Equal(unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT), Change(address: MemoryBase + 0x1000));
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(attribute.AsSpan(0x0C), 0);
|
||||
Assert.True(_memory.TryWrite(StatusAddress, attribute));
|
||||
Assert.Equal(unchecked((ulong)(int)0x80290001), Change());
|
||||
Assert.True(VideoOutExports.TryGetDisplayBufferInfo(_handle, 0, out var afterFailure));
|
||||
Assert.Equal(info, afterFailure);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CleanupPreservesAnotherPortsPendingFlip()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System.Buffers.Binary;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.VideoOut;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.VideoOut;
|
||||
|
|
@ -23,6 +24,33 @@ public sealed class VideoOutOutputSupportTests
|
|||
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)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue