sharpemu/src/SharpEmu.Libs/Agc/AgcExports.Events.cs

275 lines
9.9 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.Gpu.GpuCommands.Packets;
using SharpEmu.Libs.Gpu;
using SharpEmu.Libs.Kernel;
using SharpEmu.Libs.VideoOut;
namespace SharpEmu.Libs.Agc;
public static partial class AgcExports
{
// This partial handles AGC event signaling, including flips and queued interrupts.
private static readonly bool _traceAgcEqAccessors = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_TRACE_AGC_EQ_ACCESSORS"),
"1",
StringComparison.Ordinal);
private static long _agcEqAccessorTraceCount;
[SysAbiExport(
Nid = "cFazmnXpJOE",
ExportName = "sceAgcAcbEventWrite",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AcbEventWrite(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var eventType = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var eventAddress = ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0 || eventType >= 0x40)
{
return ReturnPointer(ctx, 0);
}
var hasAddress = (eventType & ~1u) == 0x38;
var packetDwords = hasAddress ? 4u : 2u;
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, packetDwords, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(packetDwords, ItEventWrite, 0)) ||
!TryWriteUInt32(ctx, commandAddress + 4, hasAddress ? eventType | 0x100u : eventType & 0x3Fu))
{
return ReturnPointer(ctx, 0);
}
if (hasAddress &&
(!TryWriteUInt32(ctx, commandAddress + 8, (uint)eventAddress & ~7u) ||
!TryWriteUInt32(ctx, commandAddress + 12, (uint)(eventAddress >> 32))))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "aJf+j5yntiU",
ExportName = "sceAgcDcbEventWrite",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbEventWrite(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var eventType = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var eventAddress = ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0 || eventType > 0x3F || eventAddress != 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryEmitCommandDwords(ctx, commandBufferAddress, out var commandAddress, Pm4(2, ItEventWrite, 0), eventType))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_event_write buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} type={eventType}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "MWiElSNE8j8",
ExportName = "sceAgcDcbWaitUntilSafeForRendering",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbWaitUntilSafeForRendering(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var videoOutHandle = (uint)ctx[CpuRegister.Rsi];
var displayBufferIndex = (uint)ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryEmitCommandDwords(ctx, commandBufferAddress, out var commandAddress, Pm4(7, ItNop, RWaitFlipDone), videoOutHandle, displayBufferIndex, 0, 0, 0, 0))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_wait_safe buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} handle={videoOutHandle} index={displayBufferIndex}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "YUeqkyT7mEQ",
ExportName = "sceAgcDcbSetFlip",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetFlip(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var videoOutHandle = (uint)ctx[CpuRegister.Rsi];
var displayBufferIndex = (int)ctx[CpuRegister.Rdx];
var flipMode = (uint)ctx[CpuRegister.Rcx];
var flipArg = unchecked((ulong)ctx[CpuRegister.R8]);
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryEmitCommandDwords(ctx, commandBufferAddress, out var commandAddress, Pm4(6, ItNop, RFlip), videoOutHandle, unchecked((uint)displayBufferIndex), flipMode, (uint)(flipArg & 0xFFFF_FFFFUL), (uint)(flipArg >> 32)))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_set_flip buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} handle={videoOutHandle} index={displayBufferIndex} mode={flipMode} arg=0x{flipArg:X16}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "w2rJhmD+dsE",
ExportName = "sceAgcDriverAddEqEvent",
Target = Generation.Gen5,
LibraryName = "libSceAgcDriver")]
public static int DriverAddEqEvent(CpuContext ctx)
{
var equeue = ctx[CpuRegister.Rdi];
var eventId = ctx[CpuRegister.Rsi];
var userData = ctx[CpuRegister.Rdx];
if (!KernelEventQueueCompatExports.RegisterEvent(
equeue,
eventId,
KernelEventQueueCompatExports.KernelEventFilterGraphics,
userData))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
TraceAgc($"agc.driver_add_eq_event eq=0x{equeue:X16} id=0x{eventId:X16} udata=0x{userData:X16}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "DL2RXaXOy88",
ExportName = "sceAgcDriverDeleteEqEvent",
Target = Generation.Gen5,
LibraryName = "libSceAgcDriver")]
public static int DriverDeleteEqEvent(CpuContext ctx)
{
var equeue = ctx[CpuRegister.Rdi];
var eventId = ctx[CpuRegister.Rsi];
if (!KernelEventQueueCompatExports.DeleteRegisteredEvent(
equeue,
eventId,
KernelEventQueueCompatExports.KernelEventFilterGraphics))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
TraceAgc($"agc.driver_delete_eq_event eq=0x{equeue:X16} id=0x{eventId:X16}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "5CdQTZIQPxM",
ExportName = "sceAgcDriverGetEqEventType",
Target = Generation.Gen5,
LibraryName = "libSceAgcDriver")]
public static int DriverGetEqEventType(CpuContext ctx)
{
var eventAddress = ctx[CpuRegister.Rdi];
if (!TryReadUInt32(ctx, eventAddress, out var eventType))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
ctx[CpuRegister.Rax] = eventType;
TraceAgcEqAccessor(ctx, "event_type", eventAddress, eventType);
return unchecked((int)eventType);
}
[SysAbiExport(
Nid = "Zw7uUVPulbw",
ExportName = "sceAgcDriverGetEqContextId",
Target = Generation.Gen5,
LibraryName = "libSceAgcDriver")]
public static int DriverGetEqContextId(CpuContext ctx)
{
var eventAddress = ctx[CpuRegister.Rdi];
if (!TryReadUInt64(ctx, eventAddress + 0x10, out var eventData))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
var contextId = (uint)eventData & 0x07FF_FFFFu;
ctx[CpuRegister.Rax] = contextId;
TraceAgcEqAccessor(ctx, "context_id", eventAddress, contextId);
return unchecked((int)contextId);
}
private static void TraceAgcEqAccessor(
CpuContext ctx,
string accessor,
ulong eventAddress,
uint result)
{
if (!_traceAgcEqAccessors)
{
return;
}
var count = Interlocked.Increment(ref _agcEqAccessorTraceCount);
if (count > 64 && (count & (count - 1)) != 0)
{
return;
}
Span<byte> eventBytes = stackalloc byte[0x20];
if (!ctx.Memory.TryRead(eventAddress, eventBytes))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] agc.eq_accessor count={count} " +
$"accessor={accessor} event=0x{eventAddress:X16} " +
$"result=0x{result:X8} snapshot=unreadable");
return;
}
var ident = BinaryPrimitives.ReadUInt64LittleEndian(eventBytes[0x00..]);
var filter = BinaryPrimitives.ReadInt16LittleEndian(eventBytes[0x08..]);
var flags = BinaryPrimitives.ReadUInt16LittleEndian(eventBytes[0x0A..]);
var filterFlags = BinaryPrimitives.ReadUInt32LittleEndian(eventBytes[0x0C..]);
var data = BinaryPrimitives.ReadUInt64LittleEndian(eventBytes[0x10..]);
var userData = BinaryPrimitives.ReadUInt64LittleEndian(eventBytes[0x18..]);
Console.Error.WriteLine(
$"[LOADER][TRACE] agc.eq_accessor count={count} accessor={accessor} " +
$"event=0x{eventAddress:X16} result=0x{result:X8} " +
$"ident=0x{ident:X16} filter={filter} flags=0x{flags:X4} " +
$"fflags=0x{filterFlags:X8} data=0x{data:X16} " +
$"udata=0x{userData:X16} thread='{Thread.CurrentThread.Name}' " +
$"managed={Environment.CurrentManagedThreadId}");
}
private static bool TryReadQueuedInterruptCondition(
CpuContext ctx,
uint interrupt,
ulong address,
out ulong value)
{
value = 0;
if (interrupt == 5)
{
if (!TryReadLiveUInt32(ctx, address, out var value32))
{
return false;
}
value = value32;
return true;
}
return interrupt == 6 && TryReadLiveUInt64(ctx, address, out value);
}
}