mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-21 18:36:13 +08:00
[AGC] Quake rendering progress: WAIT_REG_MEM, draw fixes, VideoOut, and HLE improvements (#68)
* [agc] WAIT_REG_MEM suspend/resume, draw packet fixes, new HLE exports, debug cleanup
Rebased onto upstream 79a7437 (par274/sharpemu, rewritten history).
- GpuWaitRegistry: DCBs suspended on unsatisfied WAIT_REG_MEM are re-polled
against guest memory on every submit; fixed 64-bit and standard packet parse
offsets, apply the mask, treat PM4 compare function 0 as "always".
- TryReadSubmittedDrawCount: accept the 5-dword ItDrawIndex2 form emitted by
DcbDrawIndex (count at +4); menu draws were silently discarded before.
- sceAgcDriverSubmitMultiDcbs: reversed ABI (rdi=address array, rsi=dword
sizes, rdx=count).
- VideoOut: vblank events, sceVideoOutGetFlipStatus, buffers registered via
sceVideoOutRegisterBuffers are valid flip targets.
- New HLE: libc stdio (fopen/fread/fseek/ftell/fclose/fgets), Dinkumware
_Getpctype ctype table, NpTrophy2 stubs, AMPR PAK sequential-read tracker,
MsgDialog lifecycle, NGS2 alt NIDs + dummy vtable for handle objects,
guarded memset intrinsic, abort()/strcasecmp null-arg recovery.
- Removed investigation-only code (INT3 breakpoints, qfont/mcpp dumps,
error-candidate printf traces, unconditional debug logs).
First rendered frame: Quake (PPSA01880) presents a 1920x1080 guest frame.
* Implemented a guarded native intrinsic (rep movsb) in DirectExecutionBackend to bypass HLE dispatch overhead, while preserving memory safety checks.
This commit is contained in:
@@ -38,6 +38,7 @@ public static class VideoOutExports
|
||||
private const ulong SceVideoOutPixelFormatA2R10G10B10 = 0x88060000;
|
||||
private const ulong SceVideoOutPixelFormatA2R10G10B10Srgb = 0x88000000;
|
||||
private const ulong SceVideoOutPixelFormatA2R10G10B10Bt2020Pq = 0x88740000;
|
||||
private const ulong SceVideoOutInternalEventVblank = 0x5;
|
||||
private const ulong SceVideoOutInternalEventFlip = 0x6;
|
||||
private const short OrbisKernelEventFilterVideoOut = -13;
|
||||
|
||||
@@ -53,9 +54,15 @@ public static class VideoOutExports
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIDEOOUT_FPS"),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
private static readonly bool _logVideoOutSync = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIDEOOUT_SYNC"),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
private static long _frameRateWindowStart = Stopwatch.GetTimestamp();
|
||||
private static long _submittedFrameCount;
|
||||
private static long _presentedFrameCount;
|
||||
private static long _vblankSignalCount;
|
||||
private static long _flipSubmitCount;
|
||||
|
||||
public static void ConfigureApplicationInfo(string? title, string? titleId, string? version)
|
||||
{
|
||||
@@ -99,6 +106,7 @@ public static class VideoOutExports
|
||||
public float Gamma { get; set; } = 1.0f;
|
||||
public VideoOutBufferGroup?[] Groups { get; } = new VideoOutBufferGroup?[MaxDisplayBufferGroups];
|
||||
public VideoOutBufferSlot[] BufferSlots { get; } = CreateBufferSlots();
|
||||
public List<FlipEventRegistration> VblankEvents { get; } = new();
|
||||
public List<FlipEventRegistration> FlipEvents { get; } = new();
|
||||
}
|
||||
|
||||
@@ -312,11 +320,48 @@ public static class VideoOutExports
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
lock (_stateGate)
|
||||
SignalVblank(port);
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Xru92wHJRmg",
|
||||
ExportName = "sceVideoOutAddVblankEvent",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutAddVblankEvent(CpuContext ctx)
|
||||
{
|
||||
var equeue = ctx[CpuRegister.Rdi];
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
if (!TryGetPort(handle, out var port))
|
||||
{
|
||||
port.VblankCount++;
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
if (!KernelEventQueueCompatExports.IsValidEqueue(equeue))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidEventQueue;
|
||||
}
|
||||
|
||||
var userData = ctx[CpuRegister.Rdx];
|
||||
lock (_stateGate)
|
||||
{
|
||||
var existingIndex = port.VblankEvents.FindIndex(registration => registration.Equeue == equeue);
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
port.VblankEvents[existingIndex] = new FlipEventRegistration(equeue, userData);
|
||||
}
|
||||
else
|
||||
{
|
||||
port.VblankEvents.Add(new FlipEventRegistration(equeue, userData));
|
||||
}
|
||||
}
|
||||
|
||||
// Some engines wait on this queue before issuing their first flip. Provide a first
|
||||
// edge now; later calls to WaitVblank advance the same notification sequence.
|
||||
SignalVblank(port);
|
||||
TraceVideoOut($"videoout.add_vblank_event eq=0x{equeue:X16} handle={handle} udata=0x{userData:X16}");
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -371,6 +416,53 @@ public static class VideoOutExports
|
||||
return SubmitFlip(ctx, handle, bufferIndex, flipMode, flipArg, submitGpuImage: true);
|
||||
}
|
||||
|
||||
// Struct layout matches the classic SceVideoOutFlipStatus (40 bytes):
|
||||
// count, processTime, tsc, flipArg, currentBuffer, flipPendingNum.
|
||||
[SysAbiExport(
|
||||
Nid = "SbU3dwp80lQ",
|
||||
ExportName = "sceVideoOutGetFlipStatus",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutGetFlipStatus(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var statusAddress = ctx[CpuRegister.Rsi];
|
||||
if (statusAddress == 0)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidAddress;
|
||||
}
|
||||
|
||||
VideoOutPortState? port;
|
||||
lock (_stateGate)
|
||||
{
|
||||
_ports.TryGetValue(handle, out port);
|
||||
}
|
||||
|
||||
if (port is null)
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
ulong count;
|
||||
long flipArg;
|
||||
uint currentBuffer;
|
||||
lock (_stateGate)
|
||||
{
|
||||
count = port.FlipCount;
|
||||
flipArg = 0;
|
||||
currentBuffer = unchecked((uint)port.CurrentBuffer);
|
||||
}
|
||||
|
||||
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x00, count);
|
||||
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x08, 0);
|
||||
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x10, 0);
|
||||
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x18, unchecked((ulong)flipArg));
|
||||
KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, statusAddress + 0x20, currentBuffer);
|
||||
|
||||
TraceVideoOut($"videoout.get_flip_status handle={handle} count={count} currentBuffer={currentBuffer}");
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "zgXifHT9ErY",
|
||||
ExportName = "sceVideoOutIsFlipPending",
|
||||
@@ -407,7 +499,8 @@ public static class VideoOutExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (filter != OrbisKernelEventFilterVideoOut || ident != SceVideoOutInternalEventFlip)
|
||||
if (filter != OrbisKernelEventFilterVideoOut ||
|
||||
ident is not (SceVideoOutInternalEventVblank or SceVideoOutInternalEventFlip))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidEvent;
|
||||
}
|
||||
@@ -436,7 +529,8 @@ public static class VideoOutExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (filter != OrbisKernelEventFilterVideoOut || ident != SceVideoOutInternalEventFlip)
|
||||
if (filter != OrbisKernelEventFilterVideoOut ||
|
||||
ident is not (SceVideoOutInternalEventVblank or SceVideoOutInternalEventFlip))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidEvent;
|
||||
}
|
||||
@@ -756,6 +850,38 @@ public static class VideoOutExports
|
||||
return groupIndex < 0 ? groupIndex : setIndex;
|
||||
}
|
||||
|
||||
private static void SignalVblank(VideoOutPortState port)
|
||||
{
|
||||
List<FlipEventRegistration> vblankEvents;
|
||||
ulong eventHint;
|
||||
lock (_stateGate)
|
||||
{
|
||||
port.VblankCount++;
|
||||
eventHint = SceVideoOutInternalEventVblank |
|
||||
((port.VblankCount & 0x0000_FFFF_FFFF_FFFFUL) << 16);
|
||||
vblankEvents = new List<FlipEventRegistration>(port.VblankEvents);
|
||||
}
|
||||
|
||||
var signalCount = Interlocked.Increment(ref _vblankSignalCount);
|
||||
|
||||
foreach (var vblankEvent in vblankEvents)
|
||||
{
|
||||
_ = KernelEventQueueCompatExports.TriggerDisplayEvent(
|
||||
vblankEvent.Equeue,
|
||||
SceVideoOutInternalEventVblank,
|
||||
OrbisKernelEventFilterVideoOut,
|
||||
eventHint,
|
||||
vblankEvent.UserData);
|
||||
}
|
||||
|
||||
if (_logVideoOutSync && (signalCount <= 8 || signalCount % 60 == 0))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][SYNC] vblank#{signalCount} handle={port.Handle} count={port.VblankCount} " +
|
||||
$"queues={vblankEvents.Count} hint=0x{eventHint:X16}");
|
||||
}
|
||||
}
|
||||
|
||||
private static int SubmitFlip(
|
||||
CpuContext ctx,
|
||||
int handle,
|
||||
@@ -790,11 +916,14 @@ public static class VideoOutExports
|
||||
flipEvents = new List<FlipEventRegistration>(port.FlipEvents);
|
||||
}
|
||||
|
||||
var guestImageSubmitted = false;
|
||||
ulong guestImageAddress = 0;
|
||||
if (submitGpuImage &&
|
||||
bufferIndex >= 0 &&
|
||||
TryGetDisplayBufferInfo(handle, bufferIndex, out var displayBuffer))
|
||||
{
|
||||
_ = VulkanVideoPresenter.TrySubmitGuestImage(
|
||||
guestImageAddress = displayBuffer.Address;
|
||||
guestImageSubmitted = VulkanVideoPresenter.TrySubmitGuestImage(
|
||||
displayBuffer.Address,
|
||||
displayBuffer.Width,
|
||||
displayBuffer.Height,
|
||||
@@ -819,6 +948,15 @@ public static class VideoOutExports
|
||||
flipEvent.UserData);
|
||||
}
|
||||
|
||||
var flipCount = Interlocked.Increment(ref _flipSubmitCount);
|
||||
if (_logVideoOutSync && (flipCount <= 8 || flipCount % 60 == 0))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][SYNC] flip#{flipCount} handle={handle} buffer={bufferIndex} " +
|
||||
$"addr=0x{guestImageAddress:X16} submitted={guestImageSubmitted} " +
|
||||
$"flipQueues={flipEvents.Count}");
|
||||
}
|
||||
|
||||
TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}");
|
||||
ReportFrameRate(presented: false);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -902,6 +1040,16 @@ public static class VideoOutExports
|
||||
TraceVideoOut(
|
||||
$"videoout.register_buffers handle={port.Handle} group={groupIndex} start={startIndex} count={addresses.Length} fmt=0x{attribute.PixelFormat:X} tile={attribute.TilingMode} {attribute.Width}x{attribute.Height} pitch={attribute.PitchInPixel}");
|
||||
VulkanVideoPresenter.EnsureStarted(attribute.Width, attribute.Height);
|
||||
|
||||
var guestFormat = MapPixelFormatToGuestTextureFormat(attribute.PixelFormat);
|
||||
if (guestFormat != 0)
|
||||
{
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
VulkanVideoPresenter.RegisterKnownDisplayBuffer(address, guestFormat);
|
||||
}
|
||||
}
|
||||
|
||||
return groupIndex;
|
||||
}
|
||||
}
|
||||
@@ -1123,6 +1271,22 @@ public static class VideoOutExports
|
||||
? 4u
|
||||
: 0u;
|
||||
|
||||
// Maps the PS5 VideoOut pixel format space to the AGC "guest texture format" tags
|
||||
// VulkanVideoPresenter._availableGuestImages keys on (see VulkanVideoPresenter.
|
||||
// GetGuestTextureFormat: format=10 => 56 for 8-bit RGBA variants, format=9 => 9 for 10-bit).
|
||||
private static uint MapPixelFormatToGuestTextureFormat(ulong pixelFormat) =>
|
||||
NormalizePixelFormat(pixelFormat) switch
|
||||
{
|
||||
SceVideoOutPixelFormatA8R8G8B8Srgb or
|
||||
SceVideoOutPixelFormatA8B8G8R8Srgb or
|
||||
SceVideoOutPixelFormatB8G8R8A8Unorm or
|
||||
SceVideoOutPixelFormatR8G8B8A8Unorm => 56u,
|
||||
SceVideoOutPixelFormatA2R10G10B10 or
|
||||
SceVideoOutPixelFormatA2R10G10B10Srgb or
|
||||
SceVideoOutPixelFormatA2R10G10B10Bt2020Pq => 9u,
|
||||
_ => 0u,
|
||||
};
|
||||
|
||||
private static ulong NormalizePixelFormat(ulong pixelFormat)
|
||||
{
|
||||
if (GetBytesPerPixel(pixelFormat) != 0)
|
||||
|
||||
Reference in New Issue
Block a user