Agent/fix gen5 thread agc compat (#130)

* Fix Gen5 thread and AGC compatibility

* Trim compatibility comments

* Report selected Vulkan GPU

* Clean up CPU title label

* Improve emulator frame pacing and performance

* Regenerate package locks with pinned SDK

---------

Co-authored-by: Spooks4576 <Spooks4576@users.noreply.github.com>
This commit is contained in:
Spooks
2026-07-14 05:41:42 -06:00
committed by GitHub
parent cf6964710a
commit d43edc865a
24 changed files with 1318 additions and 229 deletions
+266 -36
View File
@@ -52,6 +52,8 @@ public static class AgcExports
private const uint RFlip = 0x17;
private const uint RReleaseMem = 0x18;
private const uint RDmaData = 0x19;
private const uint RPredication = 0x1A;
private const uint RJump = 0x1B;
private const uint SpiShaderPgmLoPs = 0x8;
private const uint SpiShaderPgmHiPs = 0x9;
private const uint SpiShaderPgmLoEs = 0xC8;
@@ -389,6 +391,7 @@ public static class AgcExports
public Dictionary<uint, SubmittedDcbState> ComputeQueues { get; } = new();
public Dictionary<ulong, ComputeImageWriter> ComputeImageWriters { get; } = new();
public ulong WorkSequence { get; set; }
public ulong FlipSequence { get; set; }
}
private readonly record struct RegisterDefaultValue(uint Offset, uint Value);
@@ -1923,6 +1926,90 @@ public static class AgcExports
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "bbFueFP+J4k",
ExportName = "sceAgcDcbSetPredication",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetPredication(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var enable = (uint)ctx[CpuRegister.Rsi];
var predicateType = (uint)ctx[CpuRegister.Rdx];
var continuePredicate = (uint)ctx[CpuRegister.Rcx];
var predicateAddress = ctx[CpuRegister.R8];
if (commandBufferAddress == 0 || enable > 1 || continuePredicate > 1)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(5, ItNop, RPredication)) ||
!ctx.TryWriteUInt32(commandAddress + 4, enable) ||
!ctx.TryWriteUInt32(commandAddress + 8, predicateType) ||
!ctx.TryWriteUInt32(commandAddress + 12, continuePredicate) ||
!ctx.TryWriteUInt32(commandAddress + 16, unchecked((uint)predicateAddress)))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_set_predication buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"enable={enable} type={predicateType} continue={continuePredicate} addr=0x{predicateAddress:X16}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "xSAR0LTcRKM",
ExportName = "sceAgcDcbJump",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbJump(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var targetAddress = ctx[CpuRegister.Rsi];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 3, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(3, ItNop, RJump)) ||
!ctx.TryWriteUInt32(commandAddress + 4, unchecked((uint)targetAddress)) ||
!ctx.TryWriteUInt32(commandAddress + 8, unchecked((uint)(targetAddress >> 32))))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_jump buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"target=0x{targetAddress:X16}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "w6Dj1VJt5qY",
ExportName = "sceAgcSetPacketPredication",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SetPacketPredication(CpuContext ctx)
{
var packetAddress = ctx[CpuRegister.Rdi];
var enabled = ctx[CpuRegister.Rsi] != 0;
if (packetAddress == 0 || !ctx.TryReadUInt32(packetAddress, out var packetHeader))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
packetHeader = enabled ? packetHeader | 1u : packetHeader & ~1u;
if (!ctx.TryWriteUInt32(packetAddress, packetHeader))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "w2rJhmD+dsE",
ExportName = "sceAgcDriverAddEqEvent",
@@ -1975,9 +2062,22 @@ public static class AgcExports
public static int DriverSubmitDcb(CpuContext ctx)
{
var packetAddress = ctx[CpuRegister.Rdi];
if (packetAddress == 0 ||
!ctx.TryReadUInt64(packetAddress, out var commandAddress) ||
!ctx.TryReadUInt32(packetAddress + 8, out var dwordCount))
// Gen5 supports direct RSI/RDX and packed RDI submissions.
var commandAddress = ctx[CpuRegister.Rsi];
var directDwordCount = ctx[CpuRegister.Rdx];
uint dwordCount;
var directArguments = commandAddress != 0 &&
directDwordCount is > 0 and <= 1_000_000UL;
if (directArguments)
{
dwordCount = (uint)directDwordCount;
}
else if (packetAddress == 0 ||
!ctx.TryReadUInt64(packetAddress, out commandAddress) ||
!ctx.TryReadUInt32(packetAddress + 8, out dwordCount) ||
commandAddress == 0 ||
dwordCount == 0 ||
dwordCount > 1_000_000)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
@@ -1993,16 +2093,29 @@ public static class AgcExports
if (tracePackets)
{
TraceAgc($"agc.driver_submit_dcb packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
TraceAgc(
$"agc.driver_submit_dcb abi={(directArguments ? "direct" : "packed")} " +
$"packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
}
var gpuState = _submittedGpuStates.GetValue(ctx.Memory, static _ => new SubmittedGpuState());
ulong flipSequenceBefore;
lock (gpuState.Gate)
{
flipSequenceBefore = gpuState.FlipSequence;
ParseSubmittedDcb(ctx, gpuState, gpuState.Graphics, commandAddress, dwordCount, tracePackets);
DrainResumableDcbs(ctx, gpuState, tracePackets);
}
// Flip submissions are completed through VideoOut pacing.
if (gpuState.FlipSequence == flipSequenceBefore)
{
KernelEventQueueCompatExports.TriggerRegisteredEvents(
0,
KernelEventQueueCompatExports.KernelEventFilterGraphics,
0);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -2028,8 +2141,10 @@ public static class AgcExports
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal);
var gpuState = _submittedGpuStates.GetValue(ctx.Memory, static _ => new SubmittedGpuState());
ulong flipSequenceBefore;
lock (gpuState.Gate)
{
flipSequenceBefore = gpuState.FlipSequence;
for (uint i = 0; i < bufferCount; i++)
{
if (!ctx.TryReadUInt64(addressArray + i * 8, out var commandAddress) ||
@@ -2053,6 +2168,14 @@ public static class AgcExports
DrainResumableDcbs(ctx, gpuState, tracePackets);
}
if (gpuState.FlipSequence == flipSequenceBefore)
{
KernelEventQueueCompatExports.TriggerRegisteredEvents(
0,
KernelEventQueueCompatExports.KernelEventFilterGraphics,
0);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -2301,6 +2424,40 @@ public static class AgcExports
}
var packetType = header >> 30;
if (packetType == 0)
{
// Consume alignment padding one dword at a time.
if (header == 0)
{
offset++;
continue;
}
// Preserve stream alignment across type-0 packets.
var type0Length = ((header >> 16) & 0x3FFFu) + 2u;
if (offset + type0Length > dwordCount)
{
offset++;
continue;
}
offset += type0Length;
continue;
}
if (packetType == 1)
{
const uint type1Length = 3;
if (offset + type1Length > dwordCount)
{
offset++;
continue;
}
offset += type1Length;
continue;
}
if (packetType == 2)
{
if (tracePackets)
@@ -2315,13 +2472,15 @@ public static class AgcExports
if (packetType != 3)
{
return;
offset++;
continue;
}
var length = Pm4Length(header);
if (length == 0 || offset + length > dwordCount)
{
return;
offset++;
continue;
}
var op = (header >> 8) & 0xFFu;
@@ -2559,6 +2718,7 @@ public static class AgcExports
if (op == ItNop && register == RFlip && length >= 6)
{
gpuState.FlipSequence++;
if (!ctx.TryReadUInt32(currentAddress + 4, out var videoOutHandle) ||
!ctx.TryReadUInt32(currentAddress + 8, out var displayBufferIndexRaw) ||
!ctx.TryReadUInt32(currentAddress + 12, out var flipMode) ||
@@ -2571,23 +2731,8 @@ public static class AgcExports
var flipArg = unchecked((long)(((ulong)flipArgHi << 32) | flipArgLo));
var displayBufferIndex = unchecked((int)displayBufferIndexRaw);
var handle = unchecked((int)videoOutHandle);
if (VideoOutExports.TryGetDisplayBufferInfo(
handle,
displayBufferIndex,
out var cachedDisplayBuffer) &&
VulkanVideoPresenter.TrySubmitGuestImage(
cachedDisplayBuffer.Address,
cachedDisplayBuffer.Width,
cachedDisplayBuffer.Height,
cachedDisplayBuffer.PitchInPixel))
{
TraceDisplayBuffer(
handle,
displayBufferIndex,
cachedDisplayBuffer,
"gpu-cache");
}
else if (state.SawIndexedDraw &&
// Prefer output decoded from the current DCB.
if (state.SawIndexedDraw &&
state.TranslatedDraw is { } translatedDraw &&
VideoOutExports.TryGetDisplayBufferInfo(
handle,
@@ -2651,6 +2796,22 @@ public static class AgcExports
displayBuffer.Width,
displayBuffer.Height);
}
else if (VideoOutExports.TryGetDisplayBufferInfo(
handle,
displayBufferIndex,
out var cachedDisplayBuffer) &&
VulkanVideoPresenter.TrySubmitGuestImage(
cachedDisplayBuffer.Address,
cachedDisplayBuffer.Width,
cachedDisplayBuffer.Height,
cachedDisplayBuffer.PitchInPixel))
{
TraceDisplayBuffer(
handle,
displayBufferIndex,
cachedDisplayBuffer,
"gpu-cache");
}
_ = VideoOutExports.SubmitFlipFromAgc(ctx, handle, displayBufferIndex, unchecked((int)flipMode), flipArg);
state.SawIndexedDraw = false;
@@ -3972,25 +4133,28 @@ public static class AgcExports
TraceTextureHash(descriptor, source);
var nonZero = 0;
for (var i = 0; i < source.Length; i++)
if (_traceAgcShader)
{
if (source[i] != 0)
var nonZero = 0;
for (var i = 0; i < source.Length; i++)
{
nonZero++;
if (nonZero >= 64)
if (source[i] != 0)
{
break;
nonZero++;
if (nonZero >= 64)
{
break;
}
}
}
}
TraceAgcShader(
$"agc.texture_source addr=0x{descriptor.Address:X16} " +
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
$"size={descriptor.Width}x{descriptor.Height} pitch={descriptor.Pitch} " +
$"dst=0x{descriptor.DstSelect:X3} " +
$"bytes={source.Length} nonzero64={nonZero}");
TraceAgcShader(
$"agc.texture_source addr=0x{descriptor.Address:X16} " +
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
$"size={descriptor.Width}x{descriptor.Height} pitch={descriptor.Pitch} " +
$"dst=0x{descriptor.DstSelect:X3} " +
$"bytes={source.Length} nonzero64={nonZero}");
}
var rgba = source;
texture = new VulkanGuestDrawTexture(
@@ -5701,6 +5865,16 @@ public static class AgcExports
Console.Error.WriteLine($"[LOADER][TRACE] {message}");
}
private static void TraceAgc(ref AgcTraceHandler message)
{
if (!_traceAgc)
{
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] {message.ToStringAndClear()}");
}
private static void TraceAgcShader(string message)
{
if (!_traceAgcShader)
@@ -5711,6 +5885,62 @@ public static class AgcExports
Console.Error.WriteLine($"[LOADER][TRACE] {message}");
}
private static void TraceAgcShader(ref AgcShaderTraceHandler message)
{
if (!_traceAgcShader)
{
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] {message.ToStringAndClear()}");
}
[InterpolatedStringHandler]
internal ref struct AgcTraceHandler
{
private DefaultInterpolatedStringHandler _inner;
public AgcTraceHandler(int literalLength, int formattedCount, out bool isEnabled)
{
isEnabled = _traceAgc;
_inner = isEnabled
? new DefaultInterpolatedStringHandler(literalLength, formattedCount)
: default;
}
public void AppendLiteral(string value) => _inner.AppendLiteral(value);
public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
public void AppendFormatted<T>(T value, string? format) =>
_inner.AppendFormatted(value, format);
public string ToStringAndClear() => _inner.ToStringAndClear();
}
[InterpolatedStringHandler]
internal ref struct AgcShaderTraceHandler
{
private DefaultInterpolatedStringHandler _inner;
public AgcShaderTraceHandler(int literalLength, int formattedCount, out bool isEnabled)
{
isEnabled = _traceAgcShader;
_inner = isEnabled
? new DefaultInterpolatedStringHandler(literalLength, formattedCount)
: default;
}
public void AppendLiteral(string value) => _inner.AppendLiteral(value);
public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
public void AppendFormatted<T>(T value, string? format) =>
_inner.AppendFormatted(value, format);
public string ToStringAndClear() => _inner.ToStringAndClear();
}
private static string FormatShaderDwords(IReadOnlyList<uint> values) =>
values.Count == 0
? "none"