Fix Gen5 boot and restore stable AGC rendering (#139)

Co-authored-by: Spooks4576 <Spooks4576@users.noreply.github.com>
This commit is contained in:
Spooks
2026-07-14 07:13:42 -06:00
committed by GitHub
parent 884584da67
commit 787d3a1efb
5 changed files with 99 additions and 279 deletions
@@ -736,6 +736,9 @@ public sealed partial class DirectExecutionBackend
var expectedEqueueTimeout =
string.Equals(nid, "fzyMKs9kim0", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
var expectedEventFlagTimeout =
string.Equals(nid, "JTvBflhYazQ", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
var expectedMutexTrylockBusy =
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
@@ -748,6 +751,7 @@ public sealed partial class DirectExecutionBackend
if (!expectedFileProbeMiss &&
!expectedTimedWaitTimeout &&
!expectedEqueueTimeout &&
!expectedEventFlagTimeout &&
!expectedMutexTrylockBusy &&
!expectedUserServiceNoEvent &&
!expectedPrivacyInvalidParameter)
+36 -266
View File
@@ -52,8 +52,6 @@ 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;
@@ -391,7 +389,6 @@ 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);
@@ -1926,90 +1923,6 @@ 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",
@@ -2062,22 +1975,9 @@ public static class AgcExports
public static int DriverSubmitDcb(CpuContext ctx)
{
var packetAddress = ctx[CpuRegister.Rdi];
// 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)
if (packetAddress == 0 ||
!ctx.TryReadUInt64(packetAddress, out var commandAddress) ||
!ctx.TryReadUInt32(packetAddress + 8, out var dwordCount))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
@@ -2093,29 +1993,16 @@ public static class AgcExports
if (tracePackets)
{
TraceAgc(
$"agc.driver_submit_dcb abi={(directArguments ? "direct" : "packed")} " +
$"packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
TraceAgc($"agc.driver_submit_dcb 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;
}
@@ -2141,10 +2028,8 @@ 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) ||
@@ -2168,14 +2053,6 @@ 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;
}
@@ -2424,40 +2301,6 @@ 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)
@@ -2472,15 +2315,13 @@ public static class AgcExports
if (packetType != 3)
{
offset++;
continue;
return;
}
var length = Pm4Length(header);
if (length == 0 || offset + length > dwordCount)
{
offset++;
continue;
return;
}
var op = (header >> 8) & 0xFFu;
@@ -2718,7 +2559,6 @@ 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) ||
@@ -2731,8 +2571,23 @@ public static class AgcExports
var flipArg = unchecked((long)(((ulong)flipArgHi << 32) | flipArgLo));
var displayBufferIndex = unchecked((int)displayBufferIndexRaw);
var handle = unchecked((int)videoOutHandle);
// Prefer output decoded from the current DCB.
if (state.SawIndexedDraw &&
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 &&
state.TranslatedDraw is { } translatedDraw &&
VideoOutExports.TryGetDisplayBufferInfo(
handle,
@@ -2796,22 +2651,6 @@ 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;
@@ -4133,29 +3972,26 @@ public static class AgcExports
TraceTextureHash(descriptor, source);
if (_traceAgcShader)
var nonZero = 0;
for (var i = 0; i < source.Length; i++)
{
var nonZero = 0;
for (var i = 0; i < source.Length; i++)
if (source[i] != 0)
{
if (source[i] != 0)
nonZero++;
if (nonZero >= 64)
{
nonZero++;
if (nonZero >= 64)
{
break;
}
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(
descriptor.Address,
@@ -5865,16 +5701,6 @@ 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)
@@ -5885,62 +5711,6 @@ 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"
@@ -1683,6 +1683,30 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "E6ao34wPw+U",
ExportName = "stat",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixStat(CpuContext ctx)
{
var result = KernelStat(ctx);
if (result == (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
return 0;
}
var errno = result switch
{
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT => Einval,
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT => Efault,
_ => 2,
};
KernelRuntimeCompatExports.TrySetErrno(ctx, errno);
ctx[CpuRegister.Rax] = ulong.MaxValue;
return -1;
}
[SysAbiExport(
Nid = "gEpBkcwxUjw",
ExportName = "sceKernelAprResolveFilepathsToIdsAndFileSizes",
@@ -431,6 +431,18 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "HoLVWNanBBc",
ExportName = "getpid",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int GetProcessId(CpuContext ctx)
{
var processId = Environment.ProcessId;
ctx[CpuRegister.Rax] = unchecked((uint)processId);
return processId;
}
[SysAbiExport(
Nid = "fgxnMeTNUtY",
ExportName = "sceKernelGetProcessTimeCounter",
+23 -13
View File
@@ -30,13 +30,17 @@ public static class SaveDataExports
private const uint MountModeCreate2 = 1u << 5;
private const int MountResultSize = 0x40;
private static readonly object _stateGate = new();
private static readonly HashSet<int> _transactionResources = [];
private static string? _titleId;
private static int _nextTransactionResource;
public static void ConfigureApplicationInfo(string? titleId)
{
lock (_stateGate)
{
_titleId = string.IsNullOrWhiteSpace(titleId) ? null : SanitizePathSegment(titleId.Trim());
_transactionResources.Clear();
_nextTransactionResource = 0;
}
}
@@ -251,26 +255,32 @@ public static class SaveDataExports
LibraryName = "libSceSaveData")]
public static int SaveDataCreateTransactionResource(CpuContext ctx)
{
// Gen5 ABI: memory size, resource output, reserved.
var memorySize = ctx[CpuRegister.Rdi];
var resourceAddress = ctx[CpuRegister.Rsi];
var reserved = ctx[CpuRegister.Rdx];
if (resourceAddress == 0)
int resource;
lock (_stateGate)
{
return ctx.SetReturn(OrbisSaveDataErrorParameter);
resource = ++_nextTransactionResource;
_transactionResources.Add(resource);
}
// Offline HLE operations carry no transaction state.
if (!ctx.TryWriteUInt64(resourceAddress, 0))
TraceSaveData($"create_transaction_resource memory_size=0x{memorySize:X} resource={resource}");
return ctx.SetReturn(resource);
}
[SysAbiExport(
Nid = "lJUQuaKqoKY",
ExportName = "sceSaveDataDeleteTransactionResource",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSaveData")]
public static int SaveDataDeleteTransactionResource(CpuContext ctx)
{
var resource = unchecked((int)ctx[CpuRegister.Rdi]);
lock (_stateGate)
{
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
_transactionResources.Remove(resource);
}
TraceSaveData(
$"create_transaction_resource memory_size=0x{memorySize:X} reserved=0x{reserved:X} " +
$"resource_addr=0x{resourceAddress:X} resource=0x0");
TraceSaveData($"delete_transaction_resource resource={resource}");
return ctx.SetReturn(0);
}