Files
sharpemu/src/SharpEmu.Libs/Agc/AgcExports.cs
T

5639 lines
210 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using SharpEmu.Libs.VideoOut;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
namespace SharpEmu.Libs.Agc;
public static class AgcExports
{
private const uint ShaderFileHeader = 0x34333231;
private const uint ShaderVersion = 0x18;
private const uint ItNop = 0x10;
private const uint ItSetBase = 0x11;
private const uint ItIndexBufferSize = 0x13;
private const uint ItIndexBase = 0x26;
private const uint ItDrawIndirect = 0x24;
private const uint ItDrawIndexIndirect = 0x25;
private const uint ItDrawIndex2 = 0x27;
private const uint ItIndexType = 0x2A;
private const uint ItDrawIndexAuto = 0x2D;
private const uint ItNumInstances = 0x2F;
private const uint ItDrawIndexMultiAuto = 0x30;
private const uint ItDrawIndexOffset2 = 0x35;
private const uint ItWriteData = 0x37;
private const uint ItDispatchDirect = 0x15;
private const uint ItDispatchIndirect = 0x16;
private const uint ItWaitRegMem = 0x3C;
private const uint ItEventWrite = 0x46;
private const uint ItDmaData = 0x50;
private const uint ItSetContextReg = 0x69;
private const uint ItSetShReg = 0x76;
private const uint ItSetUconfigReg = 0x79;
private const uint ItGetLodStats = 0x8E;
private const uint RZero = 0x00;
private const uint RDrawIndexAuto = 0x04;
private const uint RDrawReset = 0x05;
private const uint RWaitFlipDone = 0x06;
private const uint RAcbReset = 0x09;
private const uint RWaitMem32 = 0x0A;
private const uint RPushMarker = 0x0B;
private const uint RPopMarker = 0x0C;
private const uint RShRegsIndirect = 0x11;
private const uint RCxRegsIndirect = 0x12;
private const uint RUcRegsIndirect = 0x13;
private const uint RAcquireMem = 0x14;
private const uint RWriteData = 0x15;
private const uint RWaitMem64 = 0x16;
private const uint RFlip = 0x17;
private const uint RReleaseMem = 0x18;
private const uint RDmaData = 0x19;
private const uint SpiShaderPgmLoPs = 0x8;
private const uint SpiShaderPgmHiPs = 0x9;
private const uint SpiShaderPgmLoEs = 0xC8;
private const uint SpiShaderPgmHiEs = 0xC9;
private const uint SpiShaderPgmLoLs = 0x148;
private const uint SpiShaderPgmHiLs = 0x149;
private const uint SpiShaderPgmLoGs = 0x8A;
private const uint SpiShaderPgmHiGs = 0x8B;
private const uint SpiPsInputEna = 0x1B3;
private const uint SpiPsInputAddr = 0x1B4;
private const uint ComputePgmLo = 0x20C;
private const uint ComputePgmHi = 0x20D;
private const uint ComputePgmRsrc2 = 0x213;
private const uint ComputeNumThreadX = 0x207;
private const uint ComputeNumThreadY = 0x208;
private const uint ComputeNumThreadZ = 0x209;
private const uint SpiPsInputCntl0 = 0x191;
private const uint VgtPrimitiveType = 0x242;
private const uint PaScScreenScissorTl = 0x0C;
private const uint PaScScreenScissorBr = 0x0D;
private const uint CbTargetMask = 0x8E;
private const uint PaScWindowOffset = 0x80;
private const uint PaScWindowScissorTl = 0x81;
private const uint PaScWindowScissorBr = 0x82;
private const uint PaScGenericScissorTl = 0x90;
private const uint PaScGenericScissorBr = 0x91;
private const uint PaScVportScissor0Tl = 0x94;
private const uint PaScVportScissor0Br = 0x95;
private const uint PaClVportXScale = 0x10F;
private const uint PaClVportXOffset = 0x110;
private const uint PaClVportYScale = 0x111;
private const uint PaClVportYOffset = 0x112;
private const uint PaScVportZMin0 = 0xB4;
private const uint PaScVportZMax0 = 0xB5;
private const uint CbBlendRed = 0x105;
private const uint CbBlendGreen = 0x106;
private const uint CbBlendBlue = 0x107;
private const uint CbBlendAlpha = 0x108;
private const uint CbColorControl = 0x202;
private const uint CbColor0Base = 0x318;
private const uint CbColorRegisterStride = 15;
private const uint CbColor0Info = 0x31C;
private const uint CbColor0BaseExt = 0x390;
private const uint CbColor0Attrib2 = 0x3B0;
private const uint CbColor0Attrib3 = 0x3B8;
private const uint CbBlend0Control = 0x1E0;
private const uint PaScModeCntl0 = 0x292;
private const int ColorTargetCount = 8;
private const uint PsTextureUserDataRegister = 0xC;
private const uint VsUserDataRegister = 0x4C;
private const uint GsUserDataRegister = 0x8C;
private const uint EsUserDataRegister = 0xCC;
private const uint ComputeUserDataRegister = 0x240;
private const uint NggUserDataScalarRegisterBase = 8;
private const uint Gen5TextureFormatR8G8B8A8Unorm = 56;
private const uint Gen5TextureFormatR16G16B16A16Float = 71;
private const uint Gen5TextureType2D = 9;
private const ulong MaxPresentedTextureBytes = 128UL * 1024UL * 1024UL;
private const ulong VideoOutPixelFormatA8R8G8B8Srgb = 0x80000000;
private const ulong VideoOutPixelFormatA8B8G8R8Srgb = 0x80002200;
private const ulong VideoOutPixelFormatB8G8R8A8Unorm = 0x8100000000000000;
private const ulong VideoOutPixelFormatR8G8B8A8Unorm = 0x8100000022000000;
private const uint RegisterDefaultsVersion7 = 7;
private const uint RegisterDefaultsVersion8 = 8;
private const uint RegisterDefaultsVersion10 = 10;
private const uint RegisterDefaultsVersion13 = 13;
private const int RegisterDefaultsSize = 0x40;
private const int RegisterDefaultBlockSize = 16 * 8;
private const ulong ShaderUserDataOffset = 0x08;
private const ulong ShaderCodeOffset = 0x10;
private const ulong ShaderCxRegistersOffset = 0x18;
private const ulong ShaderShRegistersOffset = 0x20;
private const ulong ShaderSpecialsOffset = 0x28;
private const ulong ShaderInputSemanticsOffset = 0x30;
private const ulong ShaderOutputSemanticsOffset = 0x38;
private const ulong ShaderNumInputSemanticsOffset = 0x50;
private const ulong ShaderNumOutputSemanticsOffset = 0x56;
private const ulong ShaderTypeOffset = 0x5A;
private const ulong ShaderNumShRegistersOffset = 0x5C;
private const ulong CommandBufferCursorUpOffset = 0x10;
private const ulong CommandBufferCursorDownOffset = 0x18;
private const ulong CommandBufferCallbackOffset = 0x20;
private const ulong CommandBufferReservedDwOffset = 0x30;
private const ulong ShaderSpecialGeCntlOffset = 0x00;
private const ulong ShaderSpecialVgtShaderStagesEnOffset = 0x08;
private const ulong ShaderSpecialVgtGsOutPrimTypeOffset = 0x20;
private const ulong ShaderSpecialGeUserVgprEnOffset = 0x28;
private const uint CbSetShRegisterRangeMarker = 0x6875000D;
private static readonly object _submitTraceGate = new();
private static readonly object _textureHashTraceGate = new();
private static readonly HashSet<uint> _tracedDcbSizes = new();
private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new();
private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new();
private static readonly HashSet<(ulong Es, ulong Ps, ulong Target, ulong Texture, uint VertexCount)> _tracedShaderDraws = new();
private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new();
private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new();
private static readonly HashSet<ulong> _tracedComputeShaders = new();
private static readonly Dictionary<(ulong Address, uint Width, uint Height), ulong> _tracedTextureHashes = [];
private static readonly HashSet<uint> _tracedSubmittedDrawOpcodes = new();
private static readonly Dictionary<(ulong Ps, ulong State, Gen5PixelOutputKind Output), byte[]> _pixelSpirvCache = new();
private static readonly Dictionary<
(ulong Es, ulong EsState, ulong Ps, ulong PsState, Gen5PixelOutputKind Output),
(byte[] Vertex, byte[] Pixel)> _graphicsSpirvCache = new();
private static readonly Dictionary<
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
byte[]> _computeSpirvCache = new();
private static readonly Dictionary<ulong, ulong> _shaderHeadersByCode = new();
private static readonly bool _traceAgc = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"),
"1",
StringComparison.Ordinal);
private static readonly bool _traceAgcShader =
_traceAgc ||
string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"),
"1",
StringComparison.Ordinal);
private static readonly bool _traceTextureHashes = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_TRACE_TEXTURE_HASHES"),
"1",
StringComparison.Ordinal);
private static long _dcbWriteDataTraceCount;
private static long _dcbWaitRegMemTraceCount;
private static long _createShaderTraceCount;
private static long _packetPayloadTraceCount;
private static bool _tracedMissingPixelShaderBindings;
private static long _unsatisfiedWaitTraceCount;
private static long _shaderTranslationMissTraceCount;
private static long _translatedDrawTraceCount;
private static long _standardDmaTraceCount;
private static readonly object _softwarePresenterGate = new();
private static readonly Dictionary<(ulong Source, ulong Destination), ulong> _softwarePresenterFingerprints = new();
private static readonly Dictionary<(ulong Shader, ulong Source, ulong Destination), ulong> _softwareComputeBlitFingerprints = new();
private static readonly object _registerDefaultsGate = new();
private static readonly ConditionalWeakTable<object, RegisterDefaultsAllocation> _registerDefaultsAllocations = new();
private static readonly ConditionalWeakTable<object, SubmittedGpuState> _submittedGpuStates = new();
private static readonly RegisterDefaultGroup[] PrimaryRegisterDefaults =
[
new(0, 0, 0xE24F806D, [new(CbColorControl, 0x00CC0010)]),
new(0, 3, 0x0BC65DA4, [new(0x08F, 0)]),
new(0, 4, 0x9E5AD592, [new(0x08E, 0)]),
new(0, 12, 0x6DE4C312, [new(0x203, 0)]),
new(0, 28, 0x1EB8D73A, [new(PaScModeCntl0, 0x00000002)]),
new(0, 31, 0xA20EFC70, [new(PaScWindowOffset, 0)]),
new(0, 58, 0x43FBD769,
[
new(CbBlendRed, 0),
new(CbBlendBlue, 0),
new(CbBlendGreen, 0),
new(CbBlendAlpha, 0),
]),
new(0, 59, 0xEF550356, [new(CbBlend0Control, 0x20010001)]),
new(0, 67, 0x918106BB,
[
new(PaScGenericScissorTl, 0x80000000),
new(PaScGenericScissorBr, 0x40004000),
]),
new(0, 72, 0x38E92C91,
[
new(0x318, 0),
new(0x31B, 0),
new(0x31C, 0),
new(0x31D, 0),
new(0x31E, 0x48),
new(0x31F, 0),
new(0x321, 0),
new(0x323, 0),
new(0x324, 0),
new(0x325, 0),
new(0x390, 0),
new(0x398, 0),
new(0x3A0, 0),
new(0x3A8, 0),
new(0x3B0, 0),
new(0x3B8, 0x0006C000),
]),
new(0, 73, 0x0B177B43, [new(0x00C, 0), new(0x00D, 0x40004000)]),
new(0, 74, 0x48531062, [new(0x191, 0)]),
new(0, 76, 0x7690AF6F,
[
new(0x10F, 0x4E7E0000),
new(0x111, 0x4E7E0000),
new(0x113, 0x4E7E0000),
new(0x110, 0),
new(0x112, 0),
new(0x114, 0),
new(0x094, 0x80000000),
new(0x095, 0x40004000),
new(0x0B4, 0),
new(0x0B5, 0),
]),
new(0, 77, 0x078D7060,
[
new(PaScWindowScissorTl, 0x80000000),
new(PaScWindowScissorBr, 0x40004000),
]),
new(1, 13, 0xC918DF3E, [new(0x20C, 0), new(0x20D, 0)]),
new(1, 14, 0xC9751C9C, [new(0x0C8, 0), new(0x0C9, 0)]),
new(1, 18, 0xC9E01B31, [new(0x008, 0), new(0x009, 0)]),
new(2, 3, 0x105971C2, [new(0x25B, 0)]),
new(2, 7, 0x40D49AD1, [new(0x262, 0)]),
new(2, 12, 0x9EBFAB10, [new(0x242, 0)]),
];
private static readonly RegisterDefaultGroup[] InternalRegisterDefaults =
[
new(0, 0, 0x8FB4EDB5, [new(0x00E, 0)]),
new(0, 1, 0xB994AD29, [new(0x2AF, 0)]),
new(0, 2, 0xD427322F, [new(0x314, 0)]),
new(0, 3, 0xF58FEA31, [new(0x1B5, 0)]),
new(1, 0, 0x6AC156EF, [new(0x216, 0)]),
new(1, 1, 0x6AC15610, [new(0x217, 0)]),
new(1, 2, 0x6AC15009, [new(0x219, 0)]),
new(1, 3, 0x6AC153BA, [new(0x21A, 0)]),
new(1, 4, 0xBE7DCD73, [new(0x27D, 0)]),
new(1, 5, 0x0C4B1438, [new(0x22A, 0)]),
new(1, 6, 0xDB00D71A, [new(0x204, 0)]),
new(1, 7, 0xDB00D249, [new(0x205, 0)]),
new(1, 8, 0xDB00EC60, [new(0x206, 0)]),
new(1, 9, 0x0C4D6FE4, [new(0x080, 0)]),
new(1, 10, 0x0C4A80EF, [new(0x100, 0)]),
new(1, 11, 0x0DD283E7, [new(0x006, 0)]),
new(1, 12, 0xC620E68C, [new(0x081, 0)]),
new(1, 13, 0xC67EFACF, [new(0x101, 0)]),
new(1, 14, 0xD9E6D9F7, [new(0x001, 0)]),
new(2, 0, 0x31F34B9F, [new(0x24F, 0)]),
new(2, 1, 0xAC0F9E76, [new(0x80003FFF, 0)]),
new(2, 2, 0x929FD95D, [new(0x250, 0)]),
];
private readonly record struct TextureDescriptor(
ulong Address,
uint Width,
uint Height,
uint Format,
uint NumberType,
uint TileMode,
uint Type,
uint BaseLevel,
uint LastLevel,
uint Pitch,
uint DstSelect)
{
public uint MipLevels
{
get
{
var largestDimension = Math.Max(Width, Height);
uint maximumMipLevels = 1;
while (largestDimension > 1)
{
largestDimension >>= 1;
maximumMipLevels++;
}
var descriptorMipLevels = LastLevel >= BaseLevel
? LastLevel - BaseLevel + 1
: 1;
return Math.Min(descriptorMipLevels, maximumMipLevels);
}
}
}
private readonly record struct RenderTargetDescriptor(
uint Slot,
ulong Address,
uint Width,
uint Height,
uint Format,
uint NumberType,
uint TileMode);
private sealed record TranslatedGuestDraw(
ulong ExportShaderAddress,
ulong PixelShaderAddress,
uint PrimitiveType,
byte[] VertexSpirv,
byte[] PixelSpirv,
uint AttributeCount,
uint VertexCount,
uint InstanceCount,
VulkanGuestIndexBuffer? IndexBuffer,
IReadOnlyList<TranslatedImageBinding> Textures,
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
IReadOnlyList<Gen5VertexInputBinding> VertexInputs,
IReadOnlyList<RenderTargetDescriptor> RenderTargets,
VulkanGuestRenderState RenderState);
private sealed record TranslatedImageBinding(
TextureDescriptor Descriptor,
bool IsStorage,
uint MipLevel,
IReadOnlyList<uint> SamplerDescriptor);
private readonly record struct RenderTargetWriter(
ulong Sequence,
ulong ExportShaderAddress,
ulong PixelShaderAddress,
uint VertexCount,
uint PrimitiveType);
private readonly record struct ComputeImageWriter(
ulong Sequence,
ulong ShaderAddress,
string Opcode);
private readonly record struct ComputeDispatch(
uint GroupCountX,
uint GroupCountY,
uint GroupCountZ);
private sealed class SubmittedDcbState
{
public Dictionary<uint, uint> CxRegisters { get; } = new();
public Dictionary<uint, uint> ShRegisters { get; } = new();
public Dictionary<uint, uint> UcRegisters { get; } = new();
public TextureDescriptor? PresenterTexture { get; set; }
public GuestDrawKind GuestDrawKind { get; set; }
public TranslatedGuestDraw? TranslatedDraw { get; set; }
public Dictionary<ulong, RenderTargetWriter> RenderTargetWriters { get; } = new();
public ulong IndirectArgsAddress { get; set; }
public bool SawIndexedDraw { get; set; }
public ulong IndexBufferAddress { get; set; }
public uint IndexBufferCount { get; set; }
public uint IndexSize { get; set; }
public uint InstanceCount { get; set; } = 1;
public uint DrawIndexOffset { get; set; }
}
private sealed class SubmittedGpuState
{
public object Gate { get; } = new();
public SubmittedDcbState Graphics { get; } = new();
public Dictionary<uint, SubmittedDcbState> ComputeQueues { get; } = new();
public Dictionary<ulong, ComputeImageWriter> ComputeImageWriters { get; } = new();
public ulong WorkSequence { get; set; }
}
private readonly record struct RegisterDefaultValue(uint Offset, uint Value);
private readonly record struct RegisterDefaultGroup(
uint Space,
uint Index,
uint Type,
RegisterDefaultValue[] Registers);
private sealed record RegisterDefaultsAllocation(ulong Primary, ulong Internal);
[SysAbiExport(
Nid = "23LRUSvYu1M",
ExportName = "sceAgcInit",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int Init(CpuContext ctx)
{
var stateAddress = ctx[CpuRegister.Rdi];
var version = (uint)ctx[CpuRegister.Rsi];
if (stateAddress == 0 || !IsSupportedRegisterDefaultsVersion(version))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
TraceAgc($"agc.init state=0x{stateAddress:X16} version={version}");
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "2JtWUUiYBXs",
ExportName = "sceAgcGetRegisterDefaults2",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int GetRegisterDefaults2(CpuContext ctx) =>
ReturnRegisterDefaults(ctx, internalDefaults: false);
[SysAbiExport(
Nid = "wRbq6ZjNop4",
ExportName = "sceAgcGetRegisterDefaults2Internal",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int GetRegisterDefaults2Internal(CpuContext ctx) =>
ReturnRegisterDefaults(ctx, internalDefaults: true);
[SysAbiExport(
Nid = "f3dg2CSgRKY",
ExportName = "sceAgcCreateShader",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CreateShader(CpuContext ctx)
{
var destinationAddress = ctx[CpuRegister.Rdi];
var headerAddress = ctx[CpuRegister.Rsi];
var codeAddress = ctx[CpuRegister.Rdx];
if (headerAddress == 0 || codeAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!ctx.TryReadUInt32(headerAddress, out var fileHeader) ||
!ctx.TryReadUInt32(headerAddress + sizeof(uint), out var version))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (fileHeader != ShaderFileHeader || version != ShaderVersion)
{
TraceCreateShader(destinationAddress, headerAddress, codeAddress, $"invalid-header file=0x{fileHeader:X8} version=0x{version:X8}");
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!RelocatePointerField(ctx, headerAddress + ShaderCxRegistersOffset) ||
!RelocatePointerField(ctx, headerAddress + ShaderShRegistersOffset) ||
!RelocatePointerField(ctx, headerAddress + ShaderUserDataOffset) ||
!RelocatePointerField(ctx, headerAddress + ShaderSpecialsOffset) ||
!RelocatePointerField(ctx, headerAddress + ShaderInputSemanticsOffset) ||
!RelocatePointerField(ctx, headerAddress + ShaderOutputSemanticsOffset) ||
!ctx.TryWriteUInt64(headerAddress + ShaderCodeOffset, codeAddress))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!ctx.TryReadUInt64(headerAddress + ShaderUserDataOffset, out var userDataAddress))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (userDataAddress != 0 &&
(!RelocatePointerField(ctx, userDataAddress) ||
!RelocatePointerField(ctx, userDataAddress + 0x08) ||
!RelocatePointerField(ctx, userDataAddress + 0x10) ||
!RelocatePointerField(ctx, userDataAddress + 0x18) ||
!RelocatePointerField(ctx, userDataAddress + 0x20)))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!PatchShaderProgramRegisters(ctx, headerAddress, codeAddress))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (destinationAddress != 0 &&
!ctx.TryWriteUInt64(destinationAddress, headerAddress))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
lock (_submitTraceGate)
{
_shaderHeadersByCode[codeAddress] = headerAddress;
}
TraceCreateShader(destinationAddress, headerAddress, codeAddress, "ok");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "vcmNN+AAXnY",
ExportName = "sceAgcSetCxRegIndirectPatchSetAddress",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SetCxRegIndirectPatchSetAddress(CpuContext ctx) =>
SetIndirectPatchAddress(ctx, "cx");
[SysAbiExport(
Nid = "Qrj4c+61z4A",
ExportName = "sceAgcSetShRegIndirectPatchSetAddress",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SetShRegIndirectPatchSetAddress(CpuContext ctx) =>
SetIndirectPatchAddress(ctx, "sh");
[SysAbiExport(
Nid = "6lNcCp+fxi4",
ExportName = "sceAgcSetUcRegIndirectPatchSetAddress",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SetUcRegIndirectPatchSetAddress(CpuContext ctx) =>
SetIndirectPatchAddress(ctx, "uc");
[SysAbiExport(
Nid = "d-6uF9sZDIU",
ExportName = "sceAgcSetCxRegIndirectPatchAddRegisters",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SetCxRegIndirectPatchAddRegisters(CpuContext ctx) =>
AddIndirectPatchRegisters(ctx, "cx");
[SysAbiExport(
Nid = "z2duB-hHQSM",
ExportName = "sceAgcSetShRegIndirectPatchAddRegisters",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SetShRegIndirectPatchAddRegisters(CpuContext ctx) =>
AddIndirectPatchRegisters(ctx, "sh");
[SysAbiExport(
Nid = "vRoArM9zaIk",
ExportName = "sceAgcSetUcRegIndirectPatchAddRegisters",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SetUcRegIndirectPatchAddRegisters(CpuContext ctx) =>
AddIndirectPatchRegisters(ctx, "uc");
[SysAbiExport(
Nid = "D9sr1xGUriE",
ExportName = "sceAgcCreatePrimState",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CreatePrimState(CpuContext ctx)
{
var cxRegistersAddress = ctx[CpuRegister.Rdi];
var ucRegistersAddress = ctx[CpuRegister.Rsi];
var hullShaderAddress = ctx[CpuRegister.Rdx];
var geometryShaderAddress = ctx[CpuRegister.Rcx];
var primitiveType = (uint)ctx[CpuRegister.R8];
if (cxRegistersAddress == 0 || ucRegistersAddress == 0 || hullShaderAddress != 0 || geometryShaderAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!ctx.TryReadByte(geometryShaderAddress + ShaderTypeOffset, out var shaderType) || !IsEsGeometryShaderType(shaderType) ||
!ctx.TryReadUInt64(geometryShaderAddress + ShaderSpecialsOffset, out var specialsAddress) ||
specialsAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!CopyShaderRegister(ctx, specialsAddress + ShaderSpecialVgtShaderStagesEnOffset, cxRegistersAddress) ||
!CopyShaderRegister(ctx, specialsAddress + ShaderSpecialVgtGsOutPrimTypeOffset, cxRegistersAddress + 8) ||
!CopyShaderRegister(ctx, specialsAddress + ShaderSpecialGeCntlOffset, ucRegistersAddress) ||
!CopyShaderRegister(ctx, specialsAddress + ShaderSpecialGeUserVgprEnOffset, ucRegistersAddress + 8) ||
!ctx.TryWriteUInt32(ucRegistersAddress + 16, VgtPrimitiveType) ||
!ctx.TryWriteUInt32(ucRegistersAddress + 20, primitiveType))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.create_prim_state cx=0x{cxRegistersAddress:X16} uc=0x{ucRegistersAddress:X16} gs=0x{geometryShaderAddress:X16} type={shaderType} prim=0x{primitiveType:X8}");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "HV4j+E0MBHE",
ExportName = "sceAgcCreateInterpolantMapping",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CreateInterpolantMapping(CpuContext ctx)
{
var registersAddress = ctx[CpuRegister.Rdi];
var geometryShaderAddress = ctx[CpuRegister.Rsi];
var pixelShaderAddress = ctx[CpuRegister.Rdx];
if (registersAddress == 0 || geometryShaderAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!ctx.TryReadUInt64(geometryShaderAddress + ShaderOutputSemanticsOffset, out var outputSemanticsAddress) ||
!ctx.TryReadUInt32(geometryShaderAddress + ShaderNumOutputSemanticsOffset, out var outputSemanticsCount))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
ulong inputSemanticsAddress = 0;
if (pixelShaderAddress != 0 &&
(!ctx.TryReadUInt64(pixelShaderAddress + ShaderInputSemanticsOffset, out inputSemanticsAddress) ||
!ctx.TryReadUInt32(pixelShaderAddress + ShaderNumInputSemanticsOffset, out _)))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
for (uint i = 0; i < 32; i++)
{
uint value = 0;
if (i < outputSemanticsCount && outputSemanticsAddress != 0)
{
var flat = false;
if (pixelShaderAddress != 0 && inputSemanticsAddress != 0 &&
ctx.TryReadUInt32(inputSemanticsAddress + (i * sizeof(uint)), out var inputSemantic))
{
flat = ((inputSemantic >> 22) & 0x1) != 0;
}
value = i | (flat ? 0x400u : 0u);
}
var destination = registersAddress + (i * 8);
if (!ctx.TryWriteUInt32(destination, SpiPsInputCntl0 + i) ||
!ctx.TryWriteUInt32(destination + sizeof(uint), value))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
TraceAgc($"agc.create_interpolant_mapping regs=0x{registersAddress:X16} gs=0x{geometryShaderAddress:X16} ps=0x{pixelShaderAddress:X16}");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "V++UgBtQhn0",
ExportName = "sceAgcGetDataPacketPayloadAddress",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int GetDataPacketPayloadAddress(CpuContext ctx)
{
var outputAddress = ctx[CpuRegister.Rdi];
var commandAddress = ctx[CpuRegister.Rsi];
var type = (int)ctx[CpuRegister.Rdx];
if (outputAddress == 0 || commandAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var payloadAddress = commandAddress + 8;
if (type == 0)
{
if (!ctx.TryReadUInt32(commandAddress, out var header))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
payloadAddress = (header & 0x3FFF_0000u) == 0x3FFF_0000u
? 0
: commandAddress + 4;
}
if (!ctx.TryWriteUInt64(outputAddress, payloadAddress))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (ShouldTraceHotPath(ref _packetPayloadTraceCount))
{
TraceAgc(
$"agc.get_packet_payload out=0x{outputAddress:X16} cmd=0x{commandAddress:X16} " +
$"type={type} payload=0x{payloadAddress:X16}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "LtTouSCZjHM",
ExportName = "sceAgcCbNop",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CbNop(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var dwordCount = (uint)ctx[CpuRegister.Rsi];
if (commandBufferAddress == 0 || dwordCount < 2 || dwordCount > 0x4001)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, dwordCount, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(dwordCount, ItNop, RZero)))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
for (uint index = 1; index < dwordCount; index++)
{
if (!ctx.TryWriteUInt32(commandAddress + ((ulong)index * sizeof(uint)), 0))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "k3GhuSNmBLU",
ExportName = "sceAgcCbDispatch",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CbDispatch(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var groupCountX = (uint)ctx[CpuRegister.Rsi];
var groupCountY = (uint)ctx[CpuRegister.Rdx];
var groupCountZ = (uint)ctx[CpuRegister.Rcx];
var modifier = (uint)ctx[CpuRegister.R8];
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(5, ItDispatchDirect, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, groupCountX) ||
!ctx.TryWriteUInt32(commandAddress + 8, groupCountY) ||
!ctx.TryWriteUInt32(commandAddress + 12, groupCountZ) ||
!ctx.TryWriteUInt32(commandAddress + 16, (modifier & 0xA038u) | 0x41u))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "UZbQjYAwwXM",
ExportName = "sceAgcCbSetShRegistersDirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CbSetShRegistersDirect(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var registersAddress = ctx[CpuRegister.Rsi];
var registerCount = (uint)ctx[CpuRegister.Rdx];
if (registerCount == 0)
{
return ReturnPointer(ctx, 0);
}
if (commandBufferAddress == 0 || registersAddress == 0 || registerCount > 4096)
{
return ReturnPointer(ctx, 0);
}
var registers = new RegisterDefaultValue[registerCount];
for (uint index = 0; index < registerCount; index++)
{
var entryAddress = registersAddress + ((ulong)index * 8);
if (!ctx.TryReadUInt32(entryAddress, out var offset) ||
!ctx.TryReadUInt32(entryAddress + sizeof(uint), out var value))
{
return ReturnPointer(ctx, 0);
}
registers[index] = new RegisterDefaultValue(offset, value);
}
Array.Sort(registers, static (left, right) => left.Offset.CompareTo(right.Offset));
ulong firstCommandAddress = 0;
var startIndex = 0;
while (startIndex < registers.Length)
{
var endIndex = startIndex + 1;
while (endIndex < registers.Length &&
registers[endIndex].Offset == registers[endIndex - 1].Offset + 1)
{
endIndex++;
}
var valueCount = (uint)(endIndex - startIndex);
var packetDwords = valueCount + 2;
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, packetDwords, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(packetDwords, ItSetShReg, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, registers[startIndex].Offset & 0xFFFFu))
{
return ReturnPointer(ctx, 0);
}
firstCommandAddress = firstCommandAddress == 0 ? commandAddress : firstCommandAddress;
for (var index = startIndex; index < endIndex; index++)
{
if (!ctx.TryWriteUInt32(
commandAddress + 8 + ((ulong)(index - startIndex) * sizeof(uint)),
registers[index].Value))
{
return ReturnPointer(ctx, 0);
}
}
startIndex = endIndex;
}
return ReturnPointer(ctx, firstCommandAddress);
}
[SysAbiExport(
Nid = "JrtiDtKeS38",
ExportName = "sceAgcAcbResetQueue",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AcbResetQueue(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(2, ItNop, RAcbReset)) ||
!ctx.TryWriteUInt32(commandAddress + 4, 0))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[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) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(packetDwords, ItEventWrite, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, hasAddress ? eventType | 0x100u : eventType & 0x3Fu))
{
return ReturnPointer(ctx, 0);
}
if (hasAddress &&
(!ctx.TryWriteUInt32(commandAddress + 8, (uint)eventAddress & ~7u) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)(eventAddress >> 32))))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "KT-hTp-Ch14",
ExportName = "sceAgcAcbAcquireMem",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AcbAcquireMem(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var gcrControl = (uint)ctx[CpuRegister.Rsi];
var baseAddress = ctx[CpuRegister.Rdx];
var sizeBytes = ctx[CpuRegister.Rcx];
var pollCycles = (uint)ctx[CpuRegister.R8];
var noSize = sizeBytes == ulong.MaxValue;
if (commandBufferAddress == 0 ||
(!noSize && (sizeBytes & 0xFF) != 0) ||
(!noSize && (sizeBytes >> 40) != 0) ||
(baseAddress & 0xFF) != 0 ||
(baseAddress >> 40) != 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 8, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(8, ItNop, RAcquireMem)) ||
!ctx.TryWriteUInt32(commandAddress + 4, 0x8000_0000u) ||
!ctx.TryWriteUInt32(commandAddress + 8, noSize ? 0 : (uint)(sizeBytes >> 8)) ||
!ctx.TryWriteUInt32(commandAddress + 12, 0) ||
!ctx.TryWriteUInt32(commandAddress + 16, (uint)(baseAddress >> 8)) ||
!ctx.TryWriteUInt32(commandAddress + 20, 0) ||
!ctx.TryWriteUInt32(commandAddress + 24, pollCycles / 40) ||
!ctx.TryWriteUInt32(commandAddress + 28, gcrControl))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "htn36gPnBk4",
ExportName = "sceAgcAcbWaitRegMem",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AcbWaitRegMem(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var size = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var compareFunction = (uint)(ctx[CpuRegister.Rdx] & 0xFF);
var cachePolicy = (uint)(ctx[CpuRegister.Rcx] & 0xFF);
var address = ctx[CpuRegister.R8];
var reference = ctx[CpuRegister.R9];
var stackAddress = ctx[CpuRegister.Rsp];
if (!ctx.TryReadUInt64(stackAddress + sizeof(ulong), out var mask) ||
!ctx.TryReadUInt32(stackAddress + (2 * sizeof(ulong)), out var pollCycles) ||
commandBufferAddress == 0 ||
size > 1 ||
compareFunction > 7 ||
cachePolicy > 3)
{
return ReturnPointer(ctx, 0);
}
var packetDwords = size == 0 ? 6u : 9u;
var packetRegister = size == 0 ? RWaitMem32 : RWaitMem64;
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, packetDwords, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(packetDwords, ItNop, packetRegister)) ||
!ctx.TryWriteUInt32(commandAddress + 4, (uint)address) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)(address >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)mask))
{
return ReturnPointer(ctx, 0);
}
if (size == 0)
{
if (!ctx.TryWriteUInt32(commandAddress + 16, compareFunction) ||
!ctx.TryWriteUInt32(commandAddress + 20, (uint)reference))
{
return ReturnPointer(ctx, 0);
}
}
else if (!ctx.TryWriteUInt32(commandAddress + 16, (uint)(mask >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 20, (uint)reference) ||
!ctx.TryWriteUInt32(commandAddress + 24, (uint)(reference >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 28, compareFunction) ||
!ctx.TryWriteUInt32(commandAddress + 32, pollCycles / 40))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "eZ4+17OQz4Q",
ExportName = "sceAgcAcbWriteData",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AcbWriteData(CpuContext ctx) =>
DcbWriteData(ctx);
[SysAbiExport(
Nid = "j3EtxFkSIhQ",
ExportName = "sceAgcAcbDispatchIndirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int AcbDispatchIndirect(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var argumentsAddress = ctx[CpuRegister.Rsi];
var modifier = (uint)ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 4, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(4, ItDispatchIndirect, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, (uint)argumentsAddress) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)(argumentsAddress >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 12, (modifier & 0xA038u) | 0x41u))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "n2fD4A+pb+g",
ExportName = "sceAgcCbSetShRegisterRangeDirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CbSetShRegisterRangeDirect(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var offset = (uint)ctx[CpuRegister.Rsi];
var valuesAddress = ctx[CpuRegister.Rdx];
var valueCount = (uint)ctx[CpuRegister.Rcx];
if (commandBufferAddress == 0 || offset == 0 || offset > 0x3FF || valueCount == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var markerAddress) ||
!ctx.TryWriteUInt32(markerAddress, Pm4(2, ItNop, RZero)) ||
!ctx.TryWriteUInt32(markerAddress + 4, CbSetShRegisterRangeMarker) ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, valueCount + 2, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(valueCount + 2, ItSetShReg, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, offset))
{
return ReturnPointer(ctx, 0);
}
for (uint i = 0; i < valueCount; i++)
{
var value = 0u;
if (valuesAddress != 0 &&
!ctx.TryReadUInt32(valuesAddress + (i * sizeof(uint)), out value))
{
return ReturnPointer(ctx, 0);
}
if (!ctx.TryWriteUInt32(commandAddress + 8 + (i * sizeof(uint)), value))
{
return ReturnPointer(ctx, 0);
}
}
TraceAgc($"agc.cb_set_sh_range buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} offset=0x{offset:X8} count={valueCount}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "wr23dPKyWc0",
ExportName = "sceAgcCbReleaseMem",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int CbReleaseMem(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var action = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var gcrControl = (uint)(ctx[CpuRegister.Rdx] & 0xFFFF);
var destination = (uint)(ctx[CpuRegister.Rcx] & 0xFF);
var cachePolicy = (uint)(ctx[CpuRegister.R8] & 0xFF);
var destinationAddress = ctx[CpuRegister.R9];
var stackAddress = ctx[CpuRegister.Rsp];
if (!ctx.TryReadUInt64(stackAddress + 8, out var dataSelectionRaw) ||
!ctx.TryReadUInt64(stackAddress + 16, out var data) ||
!ctx.TryReadUInt64(stackAddress + 24, out var gdsOffsetRaw) ||
!ctx.TryReadUInt64(stackAddress + 32, out var gdsSizeRaw) ||
!ctx.TryReadUInt64(stackAddress + 40, out var interruptRaw) ||
!ctx.TryReadUInt64(stackAddress + 48, out var interruptContextIdRaw))
{
return ReturnPointer(ctx, 0);
}
var dataSelection = (uint)(dataSelectionRaw & 0xFF);
var gdsOffset = (uint)(gdsOffsetRaw & 0xFFFF);
var gdsSize = (uint)(gdsSizeRaw & 0xFFFF);
var interrupt = (uint)(interruptRaw & 0xFF);
var interruptContextId = (uint)interruptContextIdRaw;
if (commandBufferAddress == 0 ||
destination > 1 ||
dataSelection > 3 ||
gdsOffset != 0 ||
gdsSize > 2 ||
interrupt > 3)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 8, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(8, ItNop, RReleaseMem)) ||
!ctx.TryWriteUInt32(commandAddress + 4, action | (cachePolicy << 8)) ||
!ctx.TryWriteUInt32(
commandAddress + 8,
gcrControl | (dataSelection << 16) | (interrupt << 24)) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)destinationAddress) ||
!ctx.TryWriteUInt32(commandAddress + 16, (uint)(destinationAddress >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 20, (uint)data) ||
!ctx.TryWriteUInt32(commandAddress + 24, (uint)(data >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 28, interruptContextId))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.cb_release_mem buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"action=0x{action:X2} gcr=0x{gcrControl:X4} dst=0x{destinationAddress:X16} data_sel={dataSelection} data=0x{data:X16}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "TRO721eVt4g",
ExportName = "sceAgcDcbResetQueue",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbResetQueue(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var op = (uint)ctx[CpuRegister.Rsi];
var state = (uint)ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0 || op != 0x3FF || state != 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(2, ItNop, RDrawReset)) ||
!ctx.TryWriteUInt32(commandAddress + 4, 0))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_reset_queue buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "ZvwO9euwYzc",
ExportName = "sceAgcDcbSetCxRegistersIndirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetCxRegistersIndirect(CpuContext ctx) =>
DcbSetRegistersIndirect(ctx, RCxRegsIndirect, "cx");
[SysAbiExport(
Nid = "-HOOCn0JY48",
ExportName = "sceAgcDcbSetShRegistersIndirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetShRegistersIndirect(CpuContext ctx) =>
DcbSetRegistersIndirect(ctx, RShRegsIndirect, "sh");
[SysAbiExport(
Nid = "hvUfkUIQcOE",
ExportName = "sceAgcDcbSetUcRegistersIndirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetUcRegistersIndirect(CpuContext ctx) =>
DcbSetRegistersIndirect(ctx, RUcRegsIndirect, "uc");
[SysAbiExport(
Nid = "GIIW2J37e70",
ExportName = "sceAgcDcbSetIndexSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetIndexSize(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var indexSize = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var cachePolicy = (uint)(ctx[CpuRegister.Rdx] & 0xFF);
if (commandBufferAddress == 0 || cachePolicy != 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(2, ItIndexType, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, indexSize))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_set_index_size buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} size={indexSize}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "tSBxhAPyytQ",
ExportName = "sceAgcDcbSetNumInstances",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetNumInstances(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var instanceCount = (uint)ctx[CpuRegister.Rsi];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(2, ItNumInstances, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, instanceCount))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_set_num_instances buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} count={instanceCount}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "q88lQ+GP5Yk",
ExportName = "sceAgcDcbDrawIndex",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndex(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var indexCount = (uint)ctx[CpuRegister.Rsi];
var indexAddress = ctx[CpuRegister.Rdx];
var modifier = (uint)ctx[CpuRegister.Rcx];
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var baseCommand) ||
!ctx.TryWriteUInt32(baseCommand, Pm4(3, ItIndexBase, 0)) ||
!ctx.TryWriteUInt32(baseCommand + 4, (uint)indexAddress) ||
!ctx.TryWriteUInt32(baseCommand + 8, (uint)(indexAddress >> 32)) ||
!ctx.TryWriteUInt32(baseCommand + 12, Pm4(2, ItIndexBufferSize, 0)) ||
!ctx.TryWriteUInt32(baseCommand + 16, indexCount))
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var drawCommand) ||
!ctx.TryWriteUInt32(drawCommand, Pm4(5, ItDrawIndex2, 0)) ||
!ctx.TryWriteUInt32(drawCommand + 4, indexCount) ||
!ctx.TryWriteUInt32(drawCommand + 8, 0) ||
!ctx.TryWriteUInt32(drawCommand + 12, 0) ||
!ctx.TryWriteUInt32(drawCommand + 16, 0))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_draw_index buf=0x{commandBufferAddress:X16} " +
$"base=0x{baseCommand:X16} draw=0x{drawCommand:X16} " +
$"count={indexCount} index=0x{indexAddress:X16}");
return ReturnPointer(ctx, drawCommand);
}
[SysAbiExport(
Nid = "Yw0jKSqop+E",
ExportName = "sceAgcDcbDrawIndexAuto",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndexAuto(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var indexCount = (uint)ctx[CpuRegister.Rsi];
var modifier = ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 7, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(7, ItNop, RDrawIndexAuto)) ||
!ctx.TryWriteUInt32(commandAddress + 4, indexCount) ||
!ctx.TryWriteUInt32(commandAddress + 8, 0) ||
!ctx.TryWriteUInt32(commandAddress + 12, 0) ||
!ctx.TryWriteUInt32(commandAddress + 16, 0) ||
!ctx.TryWriteUInt32(commandAddress + 20, 0) ||
!ctx.TryWriteUInt32(commandAddress + 24, 0))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_draw_index_auto buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} count={indexCount}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "rUuVjyR+Rd4",
ExportName = "sceAgcDcbGetLodStatsGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbGetLodStatsGetSize(CpuContext ctx)
{
var counterCount = (uint)ctx[CpuRegister.Rdi];
ctx[CpuRegister.Rax] = 0x10u + (counterCount * sizeof(uint));
return (int)ctx[CpuRegister.Rax];
}
[SysAbiExport(
Nid = "vuSXe69VILM",
ExportName = "sceAgcDcbGetLodStats",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbGetLodStats(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var cachePolicy = (uint)ctx[CpuRegister.Rsi] & 0x3u;
var destinationAddress = ctx[CpuRegister.Rdx];
var control = (uint)ctx[CpuRegister.Rcx];
var counterMask = (uint)ctx[CpuRegister.R8] & 0xFFu;
var resetCounters = (uint)ctx[CpuRegister.R9] & 0x1u;
if (!ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + sizeof(ulong), out var enableRaw) ||
!ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + (2 * sizeof(ulong)), out var counterSelectRaw) ||
commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
var enable = (uint)enableRaw & 0x1u;
var counterSelect = (uint)counterSelectRaw & 0xFFu;
var packetControl =
(cachePolicy << 28) |
(enable << 19) |
(resetCounters << 18) |
(counterMask << 10) |
(counterSelect << 2);
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(5, ItGetLodStats, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, control) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)destinationAddress & ~0x3Fu) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)(destinationAddress >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 16, packetControl))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_get_lod_stats buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"dst=0x{destinationAddress:X16} control=0x{control:X8} counters=0x{counterMask:X2}");
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 (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(2, ItEventWrite, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, 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 = "57labkp+rSQ",
ExportName = "sceAgcDcbAcquireMem",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbAcquireMem(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var engine = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var cbDbOp = (uint)ctx[CpuRegister.Rdx];
var gcrControl = (uint)ctx[CpuRegister.Rcx];
var baseAddress = ctx[CpuRegister.R8];
var sizeBytes = ctx[CpuRegister.R9];
if (!ctx.TryReadUInt32(ctx[CpuRegister.Rsp] + sizeof(ulong), out var pollCycles))
{
return ReturnPointer(ctx, 0);
}
var noSize = sizeBytes == ulong.MaxValue;
if (commandBufferAddress == 0 ||
engine > 1 ||
(!noSize && (sizeBytes & 0xFF) != 0) ||
(!noSize && (sizeBytes >> 40) != 0) ||
(baseAddress & 0xFF) != 0 ||
(baseAddress >> 40) != 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 8, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(8, ItNop, RAcquireMem)) ||
!ctx.TryWriteUInt32(commandAddress + 4, (engine << 31) | cbDbOp) ||
!ctx.TryWriteUInt32(commandAddress + 8, noSize ? 0 : (uint)(sizeBytes >> 8)) ||
!ctx.TryWriteUInt32(commandAddress + 12, 0) ||
!ctx.TryWriteUInt32(commandAddress + 16, (uint)(baseAddress >> 8)) ||
!ctx.TryWriteUInt32(commandAddress + 20, 0) ||
!ctx.TryWriteUInt32(commandAddress + 24, pollCycles / 40) ||
!ctx.TryWriteUInt32(commandAddress + 28, gcrControl))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_acquire_mem buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"engine={engine} cbdb=0x{cbDbOp:X8} gcr=0x{gcrControl:X8} base=0x{baseAddress:X16} size=0x{sizeBytes:X16}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "i1jyy49AjXU",
ExportName = "sceAgcDcbWriteData",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbWriteData(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var destination = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var cachePolicy = (uint)(ctx[CpuRegister.Rdx] & 0xFF);
var destinationAddress = ctx[CpuRegister.Rcx];
var dataAddress = ctx[CpuRegister.R8];
var dwordCount = (uint)ctx[CpuRegister.R9];
var stackAddress = ctx[CpuRegister.Rsp];
if (!ctx.TryReadUInt64(stackAddress + sizeof(ulong), out var incrementRaw) ||
!ctx.TryReadUInt64(stackAddress + (2 * sizeof(ulong)), out var writeConfirmRaw))
{
return ReturnPointer(ctx, 0);
}
var increment = (uint)(incrementRaw & 0xFF);
var writeConfirm = (uint)(writeConfirmRaw & 0xFF);
if (commandBufferAddress == 0 ||
destinationAddress == 0 ||
dataAddress == 0 ||
dwordCount > 0x3FFD)
{
return ReturnPointer(ctx, 0);
}
var packetDwords = dwordCount + 4;
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, packetDwords, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(packetDwords, ItNop, RWriteData)) ||
!ctx.TryWriteUInt32(
commandAddress + 4,
destination | (cachePolicy << 8) | (increment << 16) | (writeConfirm << 24)) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)destinationAddress) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)(destinationAddress >> 32)))
{
return ReturnPointer(ctx, 0);
}
for (uint index = 0; index < dwordCount; index++)
{
if (!ctx.TryReadUInt32(dataAddress + ((ulong)index * sizeof(uint)), out var value) ||
!ctx.TryWriteUInt32(commandAddress + 16 + ((ulong)index * sizeof(uint)), value))
{
return ReturnPointer(ctx, 0);
}
}
if (ShouldTraceHotPath(ref _dcbWriteDataTraceCount))
{
TraceAgc(
$"agc.dcb_write_data buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"dst={destination} cache={cachePolicy} addr=0x{destinationAddress:X16} count={dwordCount} " +
$"increment={increment} confirm={writeConfirm}");
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "VmW0Tdpy420",
ExportName = "sceAgcDcbWaitRegMem",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbWaitRegMem(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var size = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var compareFunction = (uint)(ctx[CpuRegister.Rdx] & 0xFF);
var operation = (uint)(ctx[CpuRegister.Rcx] & 0xFF);
var cachePolicy = (uint)(ctx[CpuRegister.R8] & 0xFF);
var address = ctx[CpuRegister.R9];
var stackAddress = ctx[CpuRegister.Rsp];
if (!ctx.TryReadUInt64(stackAddress + sizeof(ulong), out var reference) ||
!ctx.TryReadUInt64(stackAddress + (2 * sizeof(ulong)), out var mask) ||
!ctx.TryReadUInt32(stackAddress + (3 * sizeof(ulong)), out var pollCycles))
{
return ReturnPointer(ctx, 0);
}
if (commandBufferAddress == 0 ||
size > 1 ||
compareFunction > 7 ||
operation > 4 ||
cachePolicy > 3)
{
return ReturnPointer(ctx, 0);
}
var standardWait = operation is 2 or 3;
var packetDwords = standardWait ? 7u : size == 0 ? 6u : 9u;
var packetRegister = size == 0 ? RWaitMem32 : RWaitMem64;
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, packetDwords, out var commandAddress))
{
return ReturnPointer(ctx, 0);
}
if (standardWait)
{
if (!ctx.TryWriteUInt32(commandAddress, Pm4(packetDwords, ItWaitRegMem, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, compareFunction | ((operation & 1) << 8)) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)address) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)(address >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 16, (uint)reference) ||
!ctx.TryWriteUInt32(commandAddress + 20, (uint)mask) ||
!ctx.TryWriteUInt32(commandAddress + 24, pollCycles / 40))
{
return ReturnPointer(ctx, 0);
}
}
else if (!ctx.TryWriteUInt32(commandAddress, Pm4(packetDwords, ItNop, packetRegister)) ||
!ctx.TryWriteUInt32(commandAddress + 4, (uint)address) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)(address >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)mask))
{
return ReturnPointer(ctx, 0);
}
else if (size == 0)
{
if (!ctx.TryWriteUInt32(commandAddress + 16, compareFunction | (operation << 8)) ||
!ctx.TryWriteUInt32(commandAddress + 20, (uint)reference))
{
return ReturnPointer(ctx, 0);
}
}
else if (!ctx.TryWriteUInt32(commandAddress + 16, (uint)(mask >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 20, (uint)reference) ||
!ctx.TryWriteUInt32(commandAddress + 24, (uint)(reference >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 28, compareFunction | (operation << 8)) ||
!ctx.TryWriteUInt32(commandAddress + 32, pollCycles / 40))
{
return ReturnPointer(ctx, 0);
}
if (ShouldTraceHotPath(ref _dcbWaitRegMemTraceCount))
{
TraceAgc(
$"agc.dcb_wait_reg_mem buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"size={size} compare={compareFunction} op={operation} cache={cachePolicy} " +
$"addr=0x{address:X16} ref=0x{reference:X16} mask=0x{mask:X16} poll={pollCycles}");
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "WmAc2MEj6Io",
ExportName = "sceAgcDcbDmaData",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDmaData(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var destination = (uint)(ctx[CpuRegister.Rsi] & 0xFF);
var destinationCachePolicy = (uint)(ctx[CpuRegister.Rdx] & 0xFF);
var source = (uint)(ctx[CpuRegister.Rcx] & 0xFF);
var destinationAddress = ctx[CpuRegister.R8];
var sourceCachePolicy = (uint)(ctx[CpuRegister.R9] & 0xFF);
var stackAddress = ctx[CpuRegister.Rsp];
if (!ctx.TryReadUInt64(stackAddress + sizeof(ulong), out var control4Raw) ||
!ctx.TryReadUInt64(stackAddress + (2 * sizeof(ulong)), out var sourceAddress) ||
!ctx.TryReadUInt32(stackAddress + (3 * sizeof(ulong)), out var byteCount) ||
!ctx.TryReadUInt64(stackAddress + (4 * sizeof(ulong)), out var control7Raw) ||
!ctx.TryReadUInt64(stackAddress + (5 * sizeof(ulong)), out var control8Raw) ||
!ctx.TryReadUInt64(stackAddress + (6 * sizeof(ulong)), out var control9Raw))
{
return ReturnPointer(ctx, 0);
}
if (commandBufferAddress == 0 || byteCount == 0 || (byteCount & 3) != 0)
{
return ReturnPointer(ctx, 0);
}
var control4 = (uint)(control4Raw & 0xFF);
var control7 = (uint)(control7Raw & 0xFF);
var control8 = (uint)(control8Raw & 0xFF);
var control9 = (uint)(control9Raw & 0xFF);
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 8, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(8, ItNop, RDmaData)) ||
!ctx.TryWriteUInt32(
commandAddress + 4,
destination |
(destinationCachePolicy << 8) |
(source << 16) |
(sourceCachePolicy << 24)) ||
!ctx.TryWriteUInt32(
commandAddress + 8,
control4 | (control7 << 8) | (control8 << 16) | (control9 << 24)) ||
!ctx.TryWriteUInt32(commandAddress + 12, byteCount) ||
!ctx.TryWriteUInt64(commandAddress + 16, destinationAddress) ||
!ctx.TryWriteUInt64(commandAddress + 24, sourceAddress))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_dma_data buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"dst=0x{destinationAddress:X16} src=0x{sourceAddress:X16} bytes={byteCount} " +
$"control0=0x{destination | (destinationCachePolicy << 8) | (source << 16) | (sourceCachePolicy << 24):X8}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "RmaJwLtc8rY",
ExportName = "sceAgcDcbSetBaseIndirectArgs",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetBaseIndirectArgs(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var baseIndex = (uint)ctx[CpuRegister.Rsi];
var address = ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 4, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(4, ItSetBase, 0) | (baseIndex << 1)) ||
!ctx.TryWriteUInt32(commandAddress + 4, 1) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)address & ~7u) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)(address >> 32)))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "CtB+A9-VxO0",
ExportName = "sceAgcDcbDispatchIndirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDispatchIndirect(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var dataOffset = (uint)ctx[CpuRegister.Rsi];
var modifier = (uint)ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 3, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(3, ItDispatchIndirect, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, dataOffset) ||
!ctx.TryWriteUInt32(commandAddress + 8, (modifier & 0xA038u) | 0x41u))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "+kSrjIVxKFE",
ExportName = "sceAgcDcbPushMarker",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbPushMarker(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var markerAddress = ctx[CpuRegister.Rsi];
if (commandBufferAddress == 0 ||
!TryReadGuestCString(ctx, markerAddress, 4095, out var marker))
{
return ReturnPointer(ctx, 0);
}
var payloadDwords = Math.Max(((uint)marker.Length + 4) / 4, 1);
var packetDwords = payloadDwords + 1;
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, packetDwords, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(packetDwords, ItNop, RPushMarker)))
{
return ReturnPointer(ctx, 0);
}
for (uint index = 0; index < payloadDwords; index++)
{
uint value = 0;
for (uint byteIndex = 0; byteIndex < sizeof(uint); byteIndex++)
{
var markerIndex = (index * sizeof(uint)) + byteIndex;
if (markerIndex < (uint)marker.Length)
{
value |= (uint)marker[(int)markerIndex] << ((int)byteIndex * 8);
}
}
if (!ctx.TryWriteUInt32(commandAddress + 4 + ((ulong)index * sizeof(uint)), value))
{
return ReturnPointer(ctx, 0);
}
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "H7uZqCoNuWk",
ExportName = "sceAgcDcbPopMarker",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbPopMarker(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(2, ItNop, RPopMarker)) ||
!ctx.TryWriteUInt32(commandAddress + 4, 0))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "IxYiarKlXxM",
ExportName = "sceAgcDmaDataPatchSetDstAddressOrOffset",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DmaDataPatchSetDstAddressOrOffset(CpuContext ctx)
{
var commandAddress = ctx[CpuRegister.Rdi];
var destinationAddress = ctx[CpuRegister.Rsi];
if (!TryGetPacketIdentity(ctx, commandAddress, out var op, out var register) ||
op != ItNop ||
register != RDmaData)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
return ctx.TryWriteUInt64(commandAddress + 16, destinationAddress)
? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK)
: ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "3KDcnM3lrcU",
ExportName = "sceAgcWaitRegMemPatchAddress",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int WaitRegMemPatchAddress(CpuContext ctx)
{
var commandAddress = ctx[CpuRegister.Rdi];
var address = ctx[CpuRegister.Rsi];
if (!TryGetPacketIdentity(ctx, commandAddress, out var op, out var register))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var fieldOffset = op == ItWaitRegMem
? 8UL
: op == ItNop && register is RWaitMem32 or RWaitMem64
? 4UL
: 0;
if (fieldOffset == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
return ctx.TryWriteUInt64(commandAddress + fieldOffset, address)
? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK)
: ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "0fWWK5uG9rQ",
ExportName = "sceAgcQueueEndOfPipeActionPatchAddress",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int QueueEndOfPipeActionPatchAddress(CpuContext ctx)
{
var commandAddress = ctx[CpuRegister.Rdi];
var address = ctx[CpuRegister.Rsi];
if (!TryGetPacketIdentity(ctx, commandAddress, out var op, out var register) ||
op != ItNop ||
register != RReleaseMem)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
return ctx.TryWriteUInt64(commandAddress + 12, address)
? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK)
: ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "l4fM9K-Lyks",
ExportName = "sceAgcDcbSetIndexBuffer",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetIndexBuffer(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var indexBufferAddress = ctx[CpuRegister.Rsi];
var indexCount = (uint)ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(3, ItIndexBase, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, (uint)(indexBufferAddress & 0xFFFF_FFFFUL)) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)(indexBufferAddress >> 32)) ||
!ctx.TryWriteUInt32(commandAddress + 12, Pm4(2, ItIndexBufferSize, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 16, indexCount))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_set_index_buffer buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} addr=0x{indexBufferAddress:X16} count={indexCount}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "B+aG9DUnTKA",
ExportName = "sceAgcDcbDrawIndexOffset",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndexOffset(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var indexOffset = (uint)ctx[CpuRegister.Rsi];
var indexCount = (uint)ctx[CpuRegister.Rdx];
var flags = (uint)ctx[CpuRegister.Rcx];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(5, ItDrawIndexOffset2, 0)) ||
!ctx.TryWriteUInt32(commandAddress + 4, indexCount) ||
!ctx.TryWriteUInt32(commandAddress + 8, indexOffset) ||
!ctx.TryWriteUInt32(commandAddress + 12, indexCount) ||
!ctx.TryWriteUInt32(commandAddress + 16, flags & 0xE000_0001u))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_draw_index_offset buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} offset={indexOffset} count={indexCount} flags=0x{flags:X8}");
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 (!TryAllocateCommandDwords(ctx, commandBufferAddress, 7, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(7, ItNop, RWaitFlipDone)) ||
!ctx.TryWriteUInt32(commandAddress + 4, videoOutHandle) ||
!ctx.TryWriteUInt32(commandAddress + 8, displayBufferIndex) ||
!ctx.TryWriteUInt32(commandAddress + 12, 0) ||
!ctx.TryWriteUInt32(commandAddress + 16, 0) ||
!ctx.TryWriteUInt32(commandAddress + 20, 0) ||
!ctx.TryWriteUInt32(commandAddress + 24, 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 (!TryAllocateCommandDwords(ctx, commandBufferAddress, 6, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(6, ItNop, RFlip)) ||
!ctx.TryWriteUInt32(commandAddress + 4, videoOutHandle) ||
!ctx.TryWriteUInt32(commandAddress + 8, unchecked((uint)displayBufferIndex)) ||
!ctx.TryWriteUInt32(commandAddress + 12, flipMode) ||
!ctx.TryWriteUInt32(commandAddress + 16, (uint)(flipArg & 0xFFFF_FFFFUL)) ||
!ctx.TryWriteUInt32(commandAddress + 20, (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 ctx.SetReturn(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 ctx.SetReturn(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 ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
TraceAgc($"agc.driver_delete_eq_event eq=0x{equeue:X16} id=0x{eventId:X16}");
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "UglJIZjGssM",
ExportName = "sceAgcDriverSubmitDcb",
Target = Generation.Gen5,
LibraryName = "libSceAgcDriver")]
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))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var tracePackets = false;
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal))
{
lock (_submitTraceGate)
{
tracePackets = _tracedDcbSizes.Add(dwordCount);
}
}
if (tracePackets)
{
TraceAgc($"agc.driver_submit_dcb packet=0x{packetAddress:X16} addr=0x{commandAddress:X16} dwords={dwordCount}");
}
var gpuState = _submittedGpuStates.GetValue(ctx.Memory, static _ => new SubmittedGpuState());
lock (gpuState.Gate)
{
ParseSubmittedDcb(ctx, gpuState, gpuState.Graphics, commandAddress, dwordCount, tracePackets);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "gSRnr79F8tQ",
ExportName = "sceAgcDriverSubmitAcb",
Target = Generation.Gen5,
LibraryName = "libSceAgcDriver")]
public static int DriverSubmitAcb(CpuContext ctx)
{
var ownerHandle = (uint)ctx[CpuRegister.Rdi];
var packetAddress = ctx[CpuRegister.Rsi];
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);
}
var tracePackets = false;
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal))
{
lock (_submitTraceGate)
{
tracePackets = _tracedDcbSizes.Add(dwordCount);
}
}
if (tracePackets)
{
TraceAgc(
$"agc.driver_submit_acb owner={ownerHandle} packet=0x{packetAddress:X16} " +
$"addr=0x{commandAddress:X16} dwords={dwordCount}");
}
var gpuState = _submittedGpuStates.GetValue(ctx.Memory, static _ => new SubmittedGpuState());
lock (gpuState.Gate)
{
if (!gpuState.ComputeQueues.TryGetValue(ownerHandle, out var queueState))
{
queueState = new SubmittedDcbState();
gpuState.ComputeQueues.Add(ownerHandle, queueState);
}
ParseSubmittedDcb(ctx, gpuState, queueState, commandAddress, dwordCount, tracePackets);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "uJziRsODk1c",
ExportName = "sceAgcDriverGetResourceRegistrationMaxNameLength",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverGetResourceRegistrationMaxNameLength(CpuContext ctx)
{
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!ctx.TryWriteUInt32(outAddress, 256))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.driver_get_resource_registration_max_name_length out=0x{outAddress:X16} value=256");
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
private const uint DefaultAgcOwner = 1;
[SysAbiExport(
Nid = "F0ZXt5q0ZTA",
ExportName = "sceAgcDriverGetDefaultOwner",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverGetDefaultOwner(CpuContext ctx)
{
var ownerAddress = ctx[CpuRegister.Rdi];
if (ownerAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!ctx.TryWriteUInt32(ownerAddress, DefaultAgcOwner))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.driver_get_default_owner out=0x{ownerAddress:X16} owner={DefaultAgcOwner}");
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "W5z4eZrjEas",
ExportName = "sceAgcDriverRegisterResource",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverRegisterResource(CpuContext ctx)
{
var resourceAddress = ctx[CpuRegister.Rdi];
var owner = (uint)ctx[CpuRegister.Rsi];
var nameAddress = ctx[CpuRegister.Rdx];
var type = (uint)ctx[CpuRegister.R8];
var flags = (uint)ctx[CpuRegister.R9];
TraceAgc(
$"agc.driver_register_resource resource=0x{resourceAddress:X16} owner={owner} " +
$"name=0x{nameAddress:X16} type={type} flags={flags}");
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "-KRzWekV120",
ExportName = "sceAgcDriverUnknown_KRzWekV120",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverUnknownKRzWekV120(CpuContext ctx)
{
TraceAgc(
$"agc.driver_unknown_krz rdi=0x{ctx[CpuRegister.Rdi]:X16} " +
$"rsi=0x{ctx[CpuRegister.Rsi]:X16} rdx=0x{ctx[CpuRegister.Rdx]:X16} " +
$"rcx=0x{ctx[CpuRegister.Rcx]:X16} r8=0x{ctx[CpuRegister.R8]:X16} r9=0x{ctx[CpuRegister.R9]:X16}");
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "h9z6+0hEydk",
ExportName = "sceAgcSuspendPoint",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int SuspendPoint(CpuContext ctx)
{
TraceAgc("agc.suspend_point");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "qj7QZpgr9Uw",
ExportName = "sceAgcUnknownQj7QZpgr9Uw",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int UnknownQj7QZpgr9Uw(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 1, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, 0x8000_0000))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.unknown_qj7 buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} " +
$"arg1=0x{ctx[CpuRegister.Rsi]:X16} arg2=0x{ctx[CpuRegister.Rdx]:X16}");
return ReturnPointer(ctx, commandAddress);
}
private static void ParseSubmittedDcb(
CpuContext ctx,
SubmittedGpuState gpuState,
SubmittedDcbState state,
ulong commandAddress,
uint dwordCount,
bool tracePackets)
{
if (commandAddress == 0 || dwordCount == 0 || dwordCount > 1_000_000)
{
return;
}
var offset = 0u;
while (offset < dwordCount)
{
var currentAddress = commandAddress + ((ulong)offset * sizeof(uint));
if (!ctx.TryReadUInt32(currentAddress, out var header))
{
return;
}
var packetType = header >> 30;
if (packetType == 2)
{
if (tracePackets)
{
TraceAgc(
$"agc.dcb.packet dw={offset} addr=0x{currentAddress:X16} " +
$"header=0x{header:X8} len=1 type=2");
}
offset++;
continue;
}
if (packetType != 3)
{
return;
}
var length = Pm4Length(header);
if (length == 0 || offset + length > dwordCount)
{
return;
}
var op = (header >> 8) & 0xFFu;
var register = (header >> 2) & 0x3Fu;
if (tracePackets)
{
TraceSubmittedPacket(ctx, currentAddress, offset, header, length, op, register);
}
if (op == ItSetShReg &&
TryReadTextureDescriptor(ctx, currentAddress, length, out var texture))
{
state.PresenterTexture = texture;
}
ApplySubmittedRegisters(ctx, state, currentAddress, length, op, register);
if (op == ItSetBase &&
length >= 4 &&
ctx.TryReadUInt32(currentAddress + 4, out var baseSelector) &&
baseSelector == 1 &&
ctx.TryReadUInt64(currentAddress + 8, out var indirectArgsAddress))
{
state.IndirectArgsAddress = indirectArgsAddress;
}
if (op == ItEventWrite &&
length >= 2 &&
ctx.TryReadUInt32(currentAddress + sizeof(uint), out var eventTypeRaw))
{
var eventType = eventTypeRaw & 0x3Fu;
var triggered = KernelEventQueueCompatExports.TriggerRegisteredEvents(
eventType,
KernelEventQueueCompatExports.KernelEventFilterGraphics,
eventType);
if (tracePackets)
{
TraceAgc($"agc.dcb.event type=0x{eventType:X2} queues={triggered}");
}
}
if (op == ItNop && register == RReleaseMem && length >= 7)
{
ApplySubmittedReleaseMem(ctx, currentAddress, tracePackets);
}
if (op == ItNop && register == RWriteData && length >= 4)
{
ApplySubmittedWriteData(ctx, currentAddress, length, tracePackets);
}
if (op == ItWriteData && length >= 4)
{
ApplySubmittedWriteData(ctx, currentAddress, length, tracePackets);
}
if (op == ItNop && register == RDmaData && length >= 8)
{
ApplySubmittedDmaData(ctx, currentAddress, tracePackets);
}
if (op == ItDmaData && length >= 7)
{
ApplySubmittedStandardDmaData(ctx, currentAddress);
}
if (op == ItIndexBase &&
length >= 3 &&
ctx.TryReadUInt32(currentAddress + 4, out var indexBaseLo) &&
ctx.TryReadUInt32(currentAddress + 8, out var indexBaseHi))
{
state.IndexBufferAddress =
indexBaseLo | ((ulong)indexBaseHi << 32);
}
if (op == ItIndexBufferSize &&
length >= 2 &&
ctx.TryReadUInt32(currentAddress + 4, out var indexBufferCount))
{
state.IndexBufferCount = indexBufferCount;
}
if (op == ItIndexType &&
length >= 2 &&
ctx.TryReadUInt32(currentAddress + 4, out var indexSize))
{
state.IndexSize = indexSize & 0x3;
}
if (op == ItNumInstances &&
length >= 2 &&
ctx.TryReadUInt32(currentAddress + 4, out var instanceCount))
{
state.InstanceCount = Math.Max(instanceCount, 1);
}
if (op == ItNop &&
register is RWaitMem32 or RWaitMem64 &&
length >= (register == RWaitMem32 ? 6u : 9u))
{
ObserveSubmittedWaitRegMem(ctx, currentAddress, register == RWaitMem64, tracePackets);
}
if (op == ItWaitRegMem && length >= 7)
{
ObserveSubmittedStandardWaitRegMem(ctx, currentAddress, tracePackets);
}
if (TryReadSubmittedDrawCount(
ctx,
state,
currentAddress,
length,
op,
out var indexCount) &&
indexCount != 0)
{
lock (_submitTraceGate)
{
if (_tracedSubmittedDrawOpcodes.Add(op))
{
TraceAgcShader(
$"agc.draw_packet op=0x{op:X2} count={indexCount}");
}
}
var indexed = op is
ItDrawIndex2 or
ItDrawIndexOffset2 or
ItDrawIndexIndirect;
state.SawIndexedDraw |= indexed;
TryTranslateGuestDraw(ctx, gpuState, state, indexCount, indexed);
}
if (op == ItNop &&
register == RDrawIndexAuto &&
length >= 2 &&
ctx.TryReadUInt32(currentAddress + 4, out var autoIndexCount) &&
autoIndexCount != 0)
{
TryTranslateGuestDraw(
ctx,
gpuState,
state,
autoIndexCount,
indexed: false);
}
if ((op is ItDispatchDirect or ItDispatchIndirect) &&
TryReadComputeDispatch(
ctx,
state,
currentAddress,
length,
op,
out var dispatch))
{
ObserveComputeDispatch(ctx, gpuState, state, dispatch);
}
if (op == ItNop && register == RFlip && length >= 6)
{
if (!ctx.TryReadUInt32(currentAddress + 4, out var videoOutHandle) ||
!ctx.TryReadUInt32(currentAddress + 8, out var displayBufferIndexRaw) ||
!ctx.TryReadUInt32(currentAddress + 12, out var flipMode) ||
!ctx.TryReadUInt32(currentAddress + 16, out var flipArgLo) ||
!ctx.TryReadUInt32(currentAddress + 20, out var flipArgHi))
{
return;
}
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 &&
state.TranslatedDraw is { } translatedDraw &&
VideoOutExports.TryGetDisplayBufferInfo(
handle,
displayBufferIndex,
out var translatedDisplayBuffer))
{
TraceDisplayBuffer(
handle,
displayBufferIndex,
translatedDisplayBuffer,
"draw-fallback");
var textures = CreateVulkanGuestDrawTextures(ctx, translatedDraw.Textures, out var fallbackTextureCount);
var globalMemoryBuffers =
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
VulkanVideoPresenter.SubmitTranslatedDraw(
translatedDraw.PixelSpirv,
textures,
globalMemoryBuffers,
translatedDisplayBuffer.Width,
translatedDisplayBuffer.Height,
translatedDraw.AttributeCount);
TraceAgcShader(
$"agc.shader_present ps=0x{translatedDraw.PixelShaderAddress:X16} " +
$"spirv={translatedDraw.PixelSpirv.Length} textures={textures.Count} " +
$"global_buffers={globalMemoryBuffers.Count} " +
$"fallback={fallbackTextureCount} {translatedDisplayBuffer.Width}x{translatedDisplayBuffer.Height}");
for (var i = 0; i < translatedDraw.Textures.Count; i++)
{
var binding = translatedDraw.Textures[i];
var d = binding.Descriptor;
TraceAgcShader(
$"agc.present_desc[{i}] " +
$"addr=0x{d.Address:X16} " +
$"size={d.Width}x{d.Height} " +
$"fmt={d.Format} " +
$"num={d.NumberType} " +
$"type={d.Type} " +
$"tile={d.TileMode} " +
$"storage={binding.IsStorage}");
}
}
else if (state.SawIndexedDraw && state.PresenterTexture is { } sourceTexture)
{
_ = TrySoftwarePresent(
ctx,
sourceTexture,
unchecked((int)videoOutHandle),
displayBufferIndex);
}
else if (state.SawIndexedDraw &&
state.GuestDrawKind != GuestDrawKind.None &&
VideoOutExports.TryGetDisplayBufferInfo(
handle,
displayBufferIndex,
out var displayBuffer))
{
VulkanVideoPresenter.SubmitGuestDraw(
state.GuestDrawKind,
displayBuffer.Width,
displayBuffer.Height);
}
_ = VideoOutExports.SubmitFlipFromAgc(ctx, handle, displayBufferIndex, unchecked((int)flipMode), flipArg);
state.SawIndexedDraw = false;
state.GuestDrawKind = GuestDrawKind.None;
state.TranslatedDraw = null;
}
offset += length;
}
}
private static void TraceDisplayBuffer(
int handle,
int index,
VideoOutExports.DisplayBufferInfo buffer,
string path)
{
lock (_submitTraceGate)
{
if (!_tracedDisplayBuffers.Add((handle, index, buffer.Address, path)))
{
return;
}
}
TraceAgcShader(
$"agc.display_buffer handle={handle} index={index} " +
$"addr=0x{buffer.Address:X16} fmt=0x{buffer.PixelFormat:X16} " +
$"tile={buffer.TilingMode} size={buffer.Width}x{buffer.Height} " +
$"pitch={buffer.PitchInPixel} path={path}");
}
private static void ApplySubmittedDmaData(
CpuContext ctx,
ulong packetAddress,
bool tracePacket)
{
if (!ctx.TryReadUInt32(packetAddress + 12, out var byteCount) ||
!ctx.TryReadUInt64(packetAddress + 16, out var destinationAddress) ||
!ctx.TryReadUInt64(packetAddress + 24, out var sourceAddress))
{
return;
}
var copied =
byteCount != 0 &&
byteCount <= 256u * 1024u * 1024u &&
destinationAddress != 0 &&
sourceAddress != 0 &&
TryCopyGuestMemory(ctx, sourceAddress, destinationAddress, byteCount);
if (tracePacket)
{
TraceAgc(
$"agc.dcb.dma_data dst=0x{destinationAddress:X16} src=0x{sourceAddress:X16} " +
$"bytes={byteCount} copied={copied}");
}
}
private static void ApplySubmittedStandardDmaData(
CpuContext ctx,
ulong packetAddress)
{
if (!ctx.TryReadUInt32(packetAddress + 4, out var control) ||
!ctx.TryReadUInt32(packetAddress + 8, out var sourceLow) ||
!ctx.TryReadUInt32(packetAddress + 12, out var sourceHigh) ||
!ctx.TryReadUInt32(packetAddress + 16, out var destinationLow) ||
!ctx.TryReadUInt32(packetAddress + 20, out var destinationHigh) ||
!ctx.TryReadUInt32(packetAddress + 24, out var command))
{
return;
}
var byteCount = command & 0x1F_FFFFu;
var sourceSelect = (control >> 29) & 0x3u;
var destinationSelect = (control >> 20) & 0x3u;
var destinationSwap = (command >> 24) & 0x3u;
var sourceAddressSpace = (command >> 26) & 0x1u;
var destinationAddressSpace = (command >> 27) & 0x1u;
var sourceAddressIncrement = (command >> 28) & 0x1u;
if (byteCount == 0 ||
destinationSwap != 0 ||
destinationSelect is not (0 or 3) ||
(destinationSelect == 0 && destinationAddressSpace != 0))
{
return;
}
var destinationAddress =
destinationLow | ((ulong)destinationHigh << 32);
bool copied;
ulong sourceAddress;
if (sourceSelect is 0 or 3 &&
(sourceSelect == 3 || sourceAddressSpace == 0))
{
sourceAddress = sourceLow | ((ulong)sourceHigh << 32);
if (sourceAddressIncrement != 0)
{
copied =
ctx.TryReadUInt32(sourceAddress, out var fillValue) &&
TryFillGuestMemory(
ctx,
fillValue,
destinationAddress,
byteCount);
}
else
{
copied = TryCopyGuestMemory(
ctx,
sourceAddress,
destinationAddress,
byteCount);
}
}
else if (sourceSelect == 2)
{
sourceAddress = 0;
copied = TryFillGuestMemory(
ctx,
sourceLow,
destinationAddress,
byteCount);
}
else
{
return;
}
if (ShouldTraceHotPath(ref _standardDmaTraceCount))
{
TraceAgcShader(
$"agc.dma_packet dst=0x{destinationAddress:X16} " +
$"src=0x{sourceAddress:X16} bytes={byteCount} " +
$"src_sel={sourceSelect} fill={sourceAddressIncrement != 0 || sourceSelect == 2} " +
$"copied={copied}");
}
}
private static void ApplySubmittedWriteData(
CpuContext ctx,
ulong packetAddress,
uint packetLength,
bool tracePacket)
{
if (!ctx.TryReadUInt32(packetAddress + 4, out var control) ||
!ctx.TryReadUInt64(packetAddress + 8, out var destinationAddress))
{
return;
}
var destination = control & 0xFFu;
var increment = (control >> 16) & 0xFFu;
var dwordCount = packetLength - 4;
var wroteData = destination is 1 or 2 or 4 or 5;
for (uint index = 0; wroteData && index < dwordCount; index++)
{
var sourceAddress = packetAddress + 16 + ((ulong)index * sizeof(uint));
var targetAddress = destinationAddress +
(increment == 0 ? (ulong)index * sizeof(uint) : 0);
wroteData =
ctx.TryReadUInt32(sourceAddress, out var value) &&
ctx.TryWriteUInt32(targetAddress, value);
}
if (tracePacket)
{
TraceAgc(
$"agc.dcb.write_data dst={destination} addr=0x{destinationAddress:X16} " +
$"count={dwordCount} increment={increment} wrote={wroteData}");
}
}
private static void ObserveSubmittedWaitRegMem(
CpuContext ctx,
ulong packetAddress,
bool is64Bit,
bool tracePacket)
{
if (!ctx.TryReadUInt64(packetAddress + 4, out var address) ||
!ctx.TryReadUInt32(packetAddress + (is64Bit ? 28u : 16u), out var control))
{
return;
}
ulong mask;
ulong reference;
ulong value;
if (is64Bit)
{
if (!ctx.TryReadUInt64(packetAddress + 12, out mask) ||
!ctx.TryReadUInt64(packetAddress + 20, out reference) ||
!ctx.TryReadUInt64(address, out value))
{
return;
}
}
else
{
if (!ctx.TryReadUInt32(packetAddress + 12, out var mask32) ||
!ctx.TryReadUInt32(packetAddress + 20, out var reference32) ||
!ctx.TryReadUInt32(address, out var value32))
{
return;
}
mask = mask32;
reference = reference32;
value = value32;
}
var compareFunction = control & 0xFFu;
TraceSubmittedWait(
address,
value,
mask,
reference,
compareFunction,
is64Bit ? 64 : 32,
tracePacket);
}
private static void ObserveSubmittedStandardWaitRegMem(
CpuContext ctx,
ulong packetAddress,
bool tracePacket)
{
if (!ctx.TryReadUInt32(packetAddress + 4, out var control) ||
!ctx.TryReadUInt64(packetAddress + 8, out var address) ||
!ctx.TryReadUInt32(packetAddress + 16, out var reference) ||
!ctx.TryReadUInt32(packetAddress + 20, out var mask) ||
!ctx.TryReadUInt32(address, out var value))
{
return;
}
TraceSubmittedWait(address, value, mask, reference, control & 0x7u, 32, tracePacket);
}
private static void TraceSubmittedWait(
ulong address,
ulong value,
ulong mask,
ulong reference,
uint compareFunction,
int bits,
bool tracePacket)
{
var maskedValue = value & mask;
var satisfied = compareFunction switch
{
0 => false,
1 => maskedValue < reference,
2 => maskedValue <= reference,
3 => maskedValue == reference,
4 => maskedValue != reference,
5 => maskedValue >= reference,
6 => maskedValue > reference,
_ => true,
};
if (!tracePacket && (satisfied || !ShouldTraceHotPath(ref _unsatisfiedWaitTraceCount)))
{
return;
}
TraceAgc(
$"agc.dcb.wait_reg_mem bits={bits} addr=0x{address:X16} " +
$"value=0x{value:X16} mask=0x{mask:X16} ref=0x{reference:X16} " +
$"compare={compareFunction} satisfied={satisfied}");
}
private static void ApplySubmittedReleaseMem(
CpuContext ctx,
ulong packetAddress,
bool tracePacket)
{
if (!ctx.TryReadUInt32(packetAddress + 8, out var control) ||
!ctx.TryReadUInt32(packetAddress + 12, out var destinationLo) ||
!ctx.TryReadUInt32(packetAddress + 16, out var destinationHi) ||
!ctx.TryReadUInt32(packetAddress + 20, out var dataLo) ||
!ctx.TryReadUInt32(packetAddress + 24, out var dataHi))
{
return;
}
var dataSelection = (control >> 16) & 0xFFu;
var destinationAddress = ((ulong)destinationHi << 32) | destinationLo;
var data = ((ulong)dataHi << 32) | dataLo;
var wroteData = dataSelection switch
{
1 or 2 => ctx.TryWriteUInt32(destinationAddress, dataLo),
3 => ctx.TryWriteUInt64(destinationAddress, data),
_ => false,
};
if (tracePacket)
{
TraceAgc(
$"agc.dcb.release_mem dst=0x{destinationAddress:X16} data_sel={dataSelection} " +
$"data=0x{data:X16} wrote={wroteData}");
}
}
private static void ApplySubmittedRegisters(
CpuContext ctx,
SubmittedDcbState state,
ulong packetAddress,
uint packetLength,
uint op,
uint register)
{
if (op is ItSetShReg or ItSetContextReg or ItSetUconfigReg)
{
if (packetLength < 3 ||
!ctx.TryReadUInt32(packetAddress + sizeof(uint), out var startRegister))
{
return;
}
var directDestination = op switch
{
ItSetShReg => state.ShRegisters,
ItSetContextReg => state.CxRegisters,
_ => state.UcRegisters,
};
for (uint index = 0; index < packetLength - 2; index++)
{
if (!ctx.TryReadUInt32(
packetAddress + 8 + ((ulong)index * sizeof(uint)),
out var value))
{
return;
}
directDestination[startRegister + index] = value;
}
return;
}
if (op != ItNop ||
register is not (RCxRegsIndirect or RShRegsIndirect or RUcRegsIndirect) ||
packetLength < 4 ||
!ctx.TryReadUInt32(packetAddress + sizeof(uint), out var registerCount) ||
!ctx.TryReadUInt64(packetAddress + 8, out var registersAddress))
{
return;
}
var destination = register switch
{
RCxRegsIndirect => state.CxRegisters,
RShRegsIndirect => state.ShRegisters,
_ => state.UcRegisters,
};
for (uint index = 0; index < registerCount; index++)
{
var entryAddress = registersAddress + ((ulong)index * 8);
if (!ctx.TryReadUInt32(entryAddress, out var registerOffset) ||
!ctx.TryReadUInt32(entryAddress + sizeof(uint), out var value))
{
return;
}
if (registerOffset != 0)
{
destination[registerOffset] = value;
}
}
}
private static bool TryReadSubmittedDrawCount(
CpuContext ctx,
SubmittedDcbState state,
ulong packetAddress,
uint packetLength,
uint op,
out uint drawCount)
{
drawCount = 0;
switch (op)
{
case ItDrawIndexAuto when packetLength >= 3:
return ctx.TryReadUInt32(packetAddress + 4, out drawCount);
case ItDrawIndex2 when packetLength >= 6:
state.DrawIndexOffset = 0;
return ctx.TryReadUInt32(packetAddress + 16, out drawCount);
case ItDrawIndexOffset2 when packetLength >= 5:
if (!ctx.TryReadUInt32(packetAddress + 8, out var indexOffset))
{
return false;
}
state.DrawIndexOffset = indexOffset;
return ctx.TryReadUInt32(packetAddress + 12, out drawCount);
case ItDrawIndexMultiAuto when packetLength >= 4:
if (!ctx.TryReadUInt32(packetAddress + 12, out var control))
{
return false;
}
drawCount = (control >> 21) & 0x7FFu;
return true;
case ItDrawIndirect or ItDrawIndexIndirect
when packetLength >= 5 && state.IndirectArgsAddress != 0:
if (!ctx.TryReadUInt32(packetAddress + 4, out var dataOffset))
{
return false;
}
return ctx.TryReadUInt32(
state.IndirectArgsAddress + dataOffset,
out drawCount);
default:
return false;
}
}
private static void TryTranslateGuestDraw(
CpuContext ctx,
SubmittedGpuState gpuState,
SubmittedDcbState state,
uint vertexCount,
bool indexed)
{
var hasExportShader = TryGetShaderAddress(
state.ShRegisters,
SpiShaderPgmLoEs,
SpiShaderPgmHiEs,
out var exportShaderAddress);
var hasPixelShader = TryGetShaderAddress(
state.ShRegisters,
SpiShaderPgmLoPs,
SpiShaderPgmHiPs,
out var pixelShaderAddress);
var hasPsInputEna = state.CxRegisters.TryGetValue(SpiPsInputEna, out var psInputEna);
var hasPsInputAddr = state.CxRegisters.TryGetValue(SpiPsInputAddr, out var psInputAddr);
state.UcRegisters.TryGetValue(VgtPrimitiveType, out var primitiveType);
var renderTargets = GetRenderTargets(state.CxRegisters);
var drawSequence = ++gpuState.WorkSequence;
state.TranslatedDraw = null;
state.GuestDrawKind = GuestDrawKind.None;
foreach (var target in renderTargets)
{
state.RenderTargetWriters[target.Address] = new RenderTargetWriter(
drawSequence,
hasExportShader ? exportShaderAddress : 0,
hasPixelShader ? pixelShaderAddress : 0,
vertexCount,
primitiveType);
TraceAgcShader(
$"agc.rt_writer seq={drawSequence} target=0x{target.Address:X16} " +
$"fmt={target.Format} tile={target.TileMode} " +
$"size={target.Width}x{target.Height} vertices={vertexCount} " +
$"es=0x{(hasExportShader ? exportShaderAddress : 0):X16} " +
$"ps=0x{(hasPixelShader ? pixelShaderAddress : 0):X16}");
}
if (vertexCount == 0 || vertexCount > 1_048_576)
{
return;
}
var translationError = string.Empty;
if (hasExportShader &&
hasPixelShader &&
hasPsInputEna &&
hasPsInputAddr &&
TryCreateTranslatedGuestDraw(
ctx,
state,
exportShaderAddress,
pixelShaderAddress,
vertexCount,
indexed,
out var translatedDraw,
out translationError))
{
state.TranslatedDraw = translatedDraw;
var firstTarget = translatedDraw.RenderTargets.FirstOrDefault();
if (firstTarget.Address != 0)
{
var textures = CreateVulkanGuestDrawTextures(
ctx,
translatedDraw.Textures,
out _);
var globalMemoryBuffers =
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
var vertexBuffers =
CreateVulkanGuestVertexBuffers(translatedDraw.VertexInputs);
VulkanVideoPresenter.SubmitOffscreenTranslatedDraw(
translatedDraw.PixelSpirv,
textures,
globalMemoryBuffers,
translatedDraw.AttributeCount,
new VulkanGuestRenderTarget(
firstTarget.Address,
firstTarget.Width,
firstTarget.Height,
firstTarget.Format,
firstTarget.NumberType),
translatedDraw.VertexSpirv,
translatedDraw.VertexCount,
translatedDraw.InstanceCount,
translatedDraw.PrimitiveType,
translatedDraw.IndexBuffer,
vertexBuffers,
translatedDraw.RenderState);
}
else
{
var storageTarget = translatedDraw.Textures
.FirstOrDefault(binding => binding.IsStorage);
if (storageTarget is not null)
{
var textures = CreateVulkanGuestDrawTextures(
ctx,
translatedDraw.Textures,
out _);
var globalMemoryBuffers =
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
VulkanVideoPresenter.SubmitStorageTranslatedDraw(
translatedDraw.PixelSpirv,
textures,
globalMemoryBuffers,
translatedDraw.AttributeCount,
storageTarget.Descriptor.Width,
storageTarget.Descriptor.Height);
}
}
if (ShouldTraceHotPath(ref _translatedDrawTraceCount))
{
TraceAgcShader(
$"agc.shader_draw_seen seq={drawSequence} " +
$"es=0x{exportShaderAddress:X16} ps=0x{pixelShaderAddress:X16} " +
$"target=0x{firstTarget.Address:X16}:{firstTarget.Width}x{firstTarget.Height}:fmt{firstTarget.Format}/tile{firstTarget.TileMode} " +
$"textures={translatedDraw.Textures.Count}");
}
lock (_submitTraceGate)
{
var firstTextureAddress = translatedDraw.Textures.FirstOrDefault()?.Descriptor.Address ?? 0;
if (_tracedShaderDraws.Add(
(exportShaderAddress, pixelShaderAddress, firstTarget.Address, firstTextureAddress, vertexCount)))
{
TraceTranslatedGuestDraw(
ctx,
gpuState,
state,
translatedDraw,
psInputEna,
psInputAddr);
}
}
return;
}
TraceShaderTranslationMiss(
ctx,
state,
vertexCount,
hasExportShader,
exportShaderAddress,
hasPixelShader,
pixelShaderAddress,
hasPsInputEna,
psInputEna,
hasPsInputAddr,
psInputAddr,
hasExportShader && hasPixelShader ? translationError : null);
}
private static bool TryCreateTranslatedGuestDraw(
CpuContext ctx,
SubmittedDcbState state,
ulong exportShaderAddress,
ulong pixelShaderAddress,
uint vertexCount,
bool indexed,
out TranslatedGuestDraw draw,
out string error)
{
draw = default!;
error = string.Empty;
ulong exportShaderHeader;
ulong pixelShaderHeader;
lock (_submitTraceGate)
{
_shaderHeadersByCode.TryGetValue(exportShaderAddress, out exportShaderHeader);
_shaderHeadersByCode.TryGetValue(pixelShaderAddress, out pixelShaderHeader);
}
if (!Gen5ShaderTranslator.TryCreateState(
ctx,
exportShaderAddress,
exportShaderHeader,
state.ShRegisters,
SelectExportUserDataRegister(state.ShRegisters),
out var exportState,
out error,
userDataScalarRegisterBase: NggUserDataScalarRegisterBase) ||
!Gen5ShaderScalarEvaluator.TryEvaluate(
ctx,
exportState,
out var exportEvaluation,
out error,
resolveVertexInputs: true) ||
!Gen5ShaderTranslator.TryCreateState(
ctx,
pixelShaderAddress,
pixelShaderHeader,
state.ShRegisters,
PsTextureUserDataRegister,
out var pixelState,
out error) ||
!Gen5ShaderScalarEvaluator.TryEvaluate(
ctx,
pixelState,
out var pixelEvaluation,
out error))
{
return false;
}
var renderTargets = GetRenderTargets(state.CxRegisters)
.Where(target =>
target.Slot == 0 &&
HasPixelColorExport(pixelState, target.Slot))
.ToArray();
var outputKind = GetPixelOutputKind(renderTargets.FirstOrDefault().NumberType);
var exportStateFingerprint = ComputeShaderStateFingerprint(exportEvaluation);
var pixelStateFingerprint = ComputeShaderStateFingerprint(pixelEvaluation);
var shaderKey = (
exportShaderAddress,
exportStateFingerprint,
pixelShaderAddress,
pixelStateFingerprint,
outputKind);
(byte[] Vertex, byte[] Pixel) compiled;
lock (_submitTraceGate)
{
_graphicsSpirvCache.TryGetValue(shaderKey, out compiled);
}
if (compiled.Vertex is null || compiled.Pixel is null)
{
var totalGlobalBuffers =
pixelEvaluation.GlobalMemoryBindings.Count +
exportEvaluation.GlobalMemoryBindings.Count;
if (!Gen5SpirvTranslator.TryCompilePixelShader(
pixelState,
pixelEvaluation,
outputKind,
out var pixelShader,
out error,
globalBufferBase: 0,
totalGlobalBufferCount: totalGlobalBuffers,
imageBindingBase: 0) ||
!Gen5SpirvTranslator.TryCompileVertexShader(
exportState,
exportEvaluation,
out var vertexShader,
out error,
globalBufferBase: pixelEvaluation.GlobalMemoryBindings.Count,
totalGlobalBufferCount: totalGlobalBuffers,
imageBindingBase: pixelEvaluation.ImageBindings.Count))
{
return false;
}
compiled = (vertexShader.Spirv, pixelShader.Spirv);
DumpSpirv(
"vs",
exportShaderAddress,
exportStateFingerprint,
compiled.Vertex,
exportState.Program);
DumpSpirv(
"ps",
pixelShaderAddress,
pixelStateFingerprint,
compiled.Pixel,
pixelState.Program);
lock (_submitTraceGate)
{
_graphicsSpirvCache.TryAdd(shaderKey, compiled);
}
}
var imageBindings = pixelEvaluation.ImageBindings
.Concat(exportEvaluation.ImageBindings);
var textures = new List<TranslatedImageBinding>(
pixelEvaluation.ImageBindings.Count +
exportEvaluation.ImageBindings.Count);
foreach (var binding in imageBindings)
{
if (!TryDecodeTextureDescriptor(binding.ResourceDescriptor, out var texture))
{
error = $"invalid texture descriptor at pc=0x{binding.Pc:X}";
return false;
}
TraceAgcShader(
$"agc.texture_binding ps=0x{pixelShaderAddress:X16} es=0x{exportShaderAddress:X16} " +
$"pc=0x{binding.Pc:X} op={binding.Opcode} storage={(Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode) ? 1 : 0)} " +
$"decoded={FormatTextureDescriptor(texture)} " +
$"raw={FormatShaderDwords(binding.ResourceDescriptor)} sampler={FormatShaderDwords(binding.SamplerDescriptor)}");
textures.Add(
new TranslatedImageBinding(
texture,
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode),
binding.MipLevel ?? 0,
binding.SamplerDescriptor));
}
var globalMemoryBindings = pixelEvaluation.GlobalMemoryBindings
.Concat(exportEvaluation.GlobalMemoryBindings)
.ToArray();
IReadOnlyList<Gen5VertexInputBinding> vertexInputs =
exportEvaluation.VertexInputs ?? [];
state.UcRegisters.TryGetValue(VgtPrimitiveType, out var primitiveType);
draw = new TranslatedGuestDraw(
exportShaderAddress,
pixelShaderAddress,
primitiveType,
compiled.Vertex,
compiled.Pixel,
GetInterpolatedAttributeCount(pixelState),
vertexCount,
state.InstanceCount,
indexed ? CreateVulkanIndexBuffer(ctx, state, vertexCount) : null,
textures,
globalMemoryBindings,
vertexInputs,
renderTargets,
CreateRenderState(state.CxRegisters, renderTargets.FirstOrDefault()));
return true;
}
private static VulkanGuestIndexBuffer? CreateVulkanIndexBuffer(
CpuContext ctx,
SubmittedDcbState state,
uint indexCount)
{
if (state.IndexBufferAddress == 0 || indexCount == 0)
{
return null;
}
var is32Bit = state.IndexSize != 0;
var bytesPerIndex = is32Bit ? sizeof(uint) : sizeof(ushort);
var byteOffset = checked((ulong)state.DrawIndexOffset * (uint)bytesPerIndex);
var byteCount = checked((int)(indexCount * (uint)bytesPerIndex));
var data = new byte[byteCount];
var address = state.IndexBufferAddress + byteOffset;
return (ctx.Memory.TryRead(address, data) ||
KernelMemoryCompatExports.TryReadTrackedLibcHeap(address, data))
? new VulkanGuestIndexBuffer(data, is32Bit)
: null;
}
private static Gen5PixelOutputKind GetPixelOutputKind(uint numberType) =>
numberType switch
{
4 => Gen5PixelOutputKind.Uint,
5 => Gen5PixelOutputKind.Sint,
_ => Gen5PixelOutputKind.Float,
};
private static bool HasPixelColorExport(Gen5ShaderState state, uint target) =>
state.Program.Instructions.Any(instruction =>
instruction.Control is Gen5ExportControl export &&
export.Target == target &&
export.EnableMask != 0);
private static uint GetInterpolatedAttributeCount(Gen5ShaderState state)
{
var maxAttribute = -1;
foreach (var instruction in state.Program.Instructions)
{
if (instruction.Control is Gen5InterpolationControl interpolation)
{
maxAttribute = Math.Max(maxAttribute, (int)interpolation.Attribute);
}
}
return (uint)(maxAttribute + 1);
}
private static ulong ComputeShaderStateFingerprint(Gen5ShaderEvaluation evaluation)
{
const ulong offsetBasis = 14695981039346656037UL;
const ulong prime = 1099511628211UL;
var hash = offsetBasis;
foreach (var value in evaluation.ScalarRegisters)
{
hash = (hash ^ value) * prime;
}
if (evaluation.ComputeSystemRegisters is { } computeSystemRegisters)
{
hash = (hash ^ (computeSystemRegisters.WorkGroupXRegister ?? uint.MaxValue)) * prime;
hash = (hash ^ (computeSystemRegisters.WorkGroupYRegister ?? uint.MaxValue)) * prime;
hash = (hash ^ (computeSystemRegisters.WorkGroupZRegister ?? uint.MaxValue)) * prime;
hash = (hash ^ (computeSystemRegisters.ThreadGroupSizeRegister ?? uint.MaxValue)) * prime;
}
return hash;
}
private static IReadOnlyList<RenderTargetDescriptor> GetRenderTargets(
IReadOnlyDictionary<uint, uint> registers)
{
var hasTargetMask = registers.TryGetValue(CbTargetMask, out var targetMask);
var targets = new List<RenderTargetDescriptor>(ColorTargetCount);
for (uint slot = 0; slot < ColorTargetCount; slot++)
{
var baseRegister = CbColor0Base + slot * CbColorRegisterStride;
if (!registers.TryGetValue(baseRegister, out var baseLow) ||
!registers.TryGetValue(CbColor0BaseExt + slot, out var baseHigh) ||
!registers.TryGetValue(CbColor0Attrib2 + slot, out var attrib2) ||
!registers.TryGetValue(CbColor0Attrib3 + slot, out var attrib3) ||
!registers.TryGetValue(CbColor0Info + slot * CbColorRegisterStride, out var info))
{
continue;
}
var address = ((ulong)(baseHigh & 0xFFu) << 40) | ((ulong)baseLow << 8);
var writeMask = (targetMask >> ((int)slot * 4)) & 0xFu;
if (address == 0 || (hasTargetMask && writeMask == 0))
{
continue;
}
targets.Add(new RenderTargetDescriptor(
slot,
address,
((attrib2 >> 14) & 0x3FFFu) + 1,
(attrib2 & 0x3FFFu) + 1,
(info >> 2) & 0x1Fu,
(info >> 8) & 0x7u,
(attrib3 >> 14) & 0x1Fu));
}
return targets;
}
private static VulkanGuestRenderState CreateRenderState(
IReadOnlyDictionary<uint, uint> registers,
RenderTargetDescriptor target)
{
var scissor = DecodeScissor(registers, target.Width, target.Height);
return new VulkanGuestRenderState(
DecodeBlendState(registers, target.Slot),
scissor,
DecodeViewport(registers, target.Width, target.Height, scissor));
}
private static VulkanGuestBlendState DecodeBlendState(
IReadOnlyDictionary<uint, uint> registers,
uint slot)
{
var writeMask = 0xFu;
if (registers.TryGetValue(CbTargetMask, out var targetMask))
{
writeMask = (targetMask >> checked((int)(slot * 4))) & 0xFu;
}
registers.TryGetValue(CbBlend0Control + slot, out var control);
return new VulkanGuestBlendState(
((control >> 30) & 1u) != 0,
control & 0x1Fu,
(control >> 8) & 0x1Fu,
(control >> 5) & 0x7u,
(control >> 16) & 0x1Fu,
(control >> 24) & 0x1Fu,
(control >> 21) & 0x7u,
((control >> 29) & 1u) != 0,
writeMask == 0 ? 0xFu : writeMask);
}
private static VulkanGuestRect? DecodeScissor(
IReadOnlyDictionary<uint, uint> registers,
uint targetWidth,
uint targetHeight)
{
if (targetWidth == 0 || targetHeight == 0)
{
return new VulkanGuestRect(0, 0, 0, 0);
}
var left = 0;
var top = 0;
var right = checked((int)Math.Min(targetWidth, int.MaxValue));
var bottom = checked((int)Math.Min(targetHeight, int.MaxValue));
var windowOffsetX = 0;
var windowOffsetY = 0;
var enableWindowOffset = true;
if (registers.TryGetValue(PaScWindowScissorTl, out var windowScissorTl))
{
enableWindowOffset = (windowScissorTl & 0x80000000u) == 0;
}
if (enableWindowOffset &&
registers.TryGetValue(PaScWindowOffset, out var windowOffset))
{
windowOffsetX = (short)(windowOffset & 0xFFFFu);
windowOffsetY = (short)(windowOffset >> 16);
}
IntersectScissorPair(registers, PaScScreenScissorTl, PaScScreenScissorBr, ref left, ref top, ref right, ref bottom);
IntersectScissorPair(
registers,
PaScWindowScissorTl,
PaScWindowScissorBr,
ref left,
ref top,
ref right,
ref bottom,
windowOffsetX,
windowOffsetY);
IntersectScissorPair(
registers,
PaScGenericScissorTl,
PaScGenericScissorBr,
ref left,
ref top,
ref right,
ref bottom,
windowOffsetX,
windowOffsetY);
var vportScissorEnabled =
!registers.TryGetValue(PaScModeCntl0, out var modeControl) ||
((modeControl >> 1) & 1u) != 0;
if (vportScissorEnabled)
{
IntersectScissorPair(registers, PaScVportScissor0Tl, PaScVportScissor0Br, ref left, ref top, ref right, ref bottom);
}
left = Math.Clamp(left, 0, checked((int)targetWidth));
top = Math.Clamp(top, 0, checked((int)targetHeight));
right = Math.Clamp(right, left, checked((int)targetWidth));
bottom = Math.Clamp(bottom, top, checked((int)targetHeight));
if (left == 0 &&
top == 0 &&
right == (int)targetWidth &&
bottom == (int)targetHeight)
{
return null;
}
return new VulkanGuestRect(
left,
top,
checked((uint)(right - left)),
checked((uint)(bottom - top)));
}
private static VulkanGuestViewport? DecodeViewport(
IReadOnlyDictionary<uint, uint> registers,
uint targetWidth,
uint targetHeight,
VulkanGuestRect? scissor)
{
if (targetWidth == 0 || targetHeight == 0)
{
return new VulkanGuestViewport(0, 0, 0, 0, 0, 1);
}
var minDepth = 0f;
var maxDepth = 1f;
if (registers.TryGetValue(PaScVportZMin0, out var zMinBits) &&
registers.TryGetValue(PaScVportZMax0, out var zMaxBits))
{
var decodedMin = BitConverter.UInt32BitsToSingle(zMinBits);
var decodedMax = BitConverter.UInt32BitsToSingle(zMaxBits);
if (float.IsFinite(decodedMin) &&
float.IsFinite(decodedMax) &&
decodedMax > decodedMin)
{
minDepth = decodedMin;
maxDepth = decodedMax;
}
}
if (TryDecodeFiniteFloat(registers, PaClVportXScale, out var xScale) &&
TryDecodeFiniteFloat(registers, PaClVportXOffset, out var xOffset) &&
TryDecodeFiniteFloat(registers, PaClVportYScale, out var yScale) &&
TryDecodeFiniteFloat(registers, PaClVportYOffset, out var yOffset) &&
xScale > 0f &&
yScale != 0f)
{
return new VulkanGuestViewport(
xOffset - xScale,
yOffset - yScale,
xScale * 2f,
yScale * 2f,
minDepth,
maxDepth);
}
if (scissor is not { } rect)
{
return minDepth == 0f && maxDepth == 1f
? null
: new VulkanGuestViewport(0, 0, targetWidth, targetHeight, minDepth, maxDepth);
}
return new VulkanGuestViewport(
rect.X,
rect.Y,
rect.Width,
rect.Height,
minDepth,
maxDepth);
}
private static bool TryDecodeFiniteFloat(
IReadOnlyDictionary<uint, uint> registers,
uint register,
out float value)
{
value = 0;
if (!registers.TryGetValue(register, out var bits))
{
return false;
}
value = BitConverter.UInt32BitsToSingle(bits);
return float.IsFinite(value);
}
private static void IntersectScissorPair(
IReadOnlyDictionary<uint, uint> registers,
uint tlRegister,
uint brRegister,
ref int left,
ref int top,
ref int right,
ref int bottom,
int offsetX = 0,
int offsetY = 0)
{
if (!TryDecodeScissorPair(registers, tlRegister, brRegister, out var pairLeft, out var pairTop, out var pairRight, out var pairBottom))
{
return;
}
pairLeft += offsetX;
pairTop += offsetY;
pairRight += offsetX;
pairBottom += offsetY;
left = Math.Max(left, pairLeft);
top = Math.Max(top, pairTop);
right = Math.Min(right, pairRight);
bottom = Math.Min(bottom, pairBottom);
}
private static bool TryDecodeScissorPair(
IReadOnlyDictionary<uint, uint> registers,
uint tlRegister,
uint brRegister,
out int left,
out int top,
out int right,
out int bottom)
{
left = 0;
top = 0;
right = 0;
bottom = 0;
if (!registers.TryGetValue(tlRegister, out var tl) ||
!registers.TryGetValue(brRegister, out var br))
{
return false;
}
left = (int)(tl & 0x7FFFu);
top = (int)((tl >> 16) & 0x7FFFu);
right = (int)(br & 0x7FFFu);
bottom = (int)((br >> 16) & 0x7FFFu);
return true;
}
private static void TraceTranslatedGuestDraw(
CpuContext ctx,
SubmittedGpuState gpuState,
SubmittedDcbState state,
TranslatedGuestDraw draw,
uint psInputEna,
uint psInputAddr)
{
var targets = draw.RenderTargets.Count == 0
? "none"
: string.Join(
',',
draw.RenderTargets.Select(target =>
$"{target.Slot}:0x{target.Address:X16}:{target.Width}x{target.Height}:" +
$"fmt{target.Format}/num{target.NumberType}/tile{target.TileMode}"));
var probes = new Dictionary<ulong, string>();
var textures = string.Join(
',',
draw.Textures.Select(binding =>
{
var texture = binding.Descriptor;
var targetSlot = draw.RenderTargets
.FirstOrDefault(target => target.Address == texture.Address)
.Slot;
var target = draw.RenderTargets.Any(candidate => candidate.Address == texture.Address)
? $"/rt{targetSlot}"
: string.Empty;
if (!probes.TryGetValue(texture.Address, out var probe))
{
probe = ProbeTexture(ctx, texture);
probes.Add(texture.Address, probe);
}
state.RenderTargetWriters.TryGetValue(texture.Address, out var sourceWriter);
gpuState.ComputeImageWriters.TryGetValue(texture.Address, out var computeWriter);
var writer = sourceWriter.Sequence >= computeWriter.Sequence && sourceWriter.Sequence != 0
? $"/writer={sourceWriter.Sequence}:" +
$"es0x{sourceWriter.ExportShaderAddress:X}:" +
$"ps0x{sourceWriter.PixelShaderAddress:X}:" +
$"v{sourceWriter.VertexCount}:prim0x{sourceWriter.PrimitiveType:X}"
: computeWriter.Sequence != 0
? $"/compute={computeWriter.Sequence}:" +
$"cs0x{computeWriter.ShaderAddress:X}:{computeWriter.Opcode}"
: "/writer=none";
return
$"0x{texture.Address:X16}:{texture.Width}x{texture.Height}:" +
$"fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}" +
$"/storage={binding.IsStorage}{target}/{probe}{writer}";
}));
var buffers = string.Join(
',',
draw.GlobalMemoryBindings.Select((binding, index) =>
$"{index}:0x{binding.BaseAddress:X16}:{binding.Data.Length}:" +
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 256)))));
var indices = draw.IndexBuffer is { } indexBuffer
? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" +
Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32)))
: "none";
var vertexInputs = draw.VertexInputs.Count == 0
? "none"
: string.Join(
',',
draw.VertexInputs.Select(input =>
$"{input.Location}:pc=0x{input.Pc:X}:0x{input.BaseAddress:X16}" +
$":stride{input.Stride}:off{input.OffsetBytes}:c{input.ComponentCount}" +
$":fmt{input.DataFormat}/num{input.NumberFormat}"));
var scissor = draw.RenderState.Scissor is { } drawScissor
? $"{drawScissor.X},{drawScissor.Y},{drawScissor.Width}x{drawScissor.Height}"
: "full";
var viewport = draw.RenderState.Viewport is { } drawViewport
? $"{drawViewport.X:0.###},{drawViewport.Y:0.###}," +
$"{drawViewport.Width:0.###}x{drawViewport.Height:0.###}:" +
$"{drawViewport.MinDepth:0.###}-{drawViewport.MaxDepth:0.###}"
: "full";
var rasterRegisters = new (string Name, uint Offset)[]
{
("screen_tl", PaScScreenScissorTl),
("screen_br", PaScScreenScissorBr),
("window_off", PaScWindowOffset),
("window_tl", PaScWindowScissorTl),
("window_br", PaScWindowScissorBr),
("generic_tl", PaScGenericScissorTl),
("generic_br", PaScGenericScissorBr),
("vport_tl", PaScVportScissor0Tl),
("vport_br", PaScVportScissor0Br),
("mode", PaScModeCntl0),
("xscale", PaClVportXScale),
("xoffset", PaClVportXOffset),
("yscale", PaClVportYScale),
("yoffset", PaClVportYOffset),
};
var raster = string.Join(
',',
rasterRegisters.Select(entry =>
state.CxRegisters.TryGetValue(entry.Offset, out var value)
? $"{entry.Name}=0x{value:X8}"
: $"{entry.Name}=missing"));
var blend = draw.RenderState.Blend;
TraceAgcShader(
$"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " +
$"ps=0x{draw.PixelShaderAddress:X16} spirv={draw.PixelSpirv.Length} " +
$"primitive=0x{draw.PrimitiveType:X} " +
$"blend={(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}/{blend.ColorDstFactor}/{blend.ColorFunc} " +
$"write_mask=0x{blend.WriteMask:X} scissor={scissor} viewport={viewport} " +
$"raster=[{raster}] " +
$"ps_ena=0x{psInputEna:X8} ps_addr=0x{psInputAddr:X8} " +
$"targets=[{targets}] textures=[{textures}] " +
$"buffers=[{buffers}] vertex=[{vertexInputs}] indices=[{indices}]");
}
private static IReadOnlyList<VulkanGuestDrawTexture> CreateVulkanGuestDrawTextures(
CpuContext ctx,
IReadOnlyList<TranslatedImageBinding> bindings,
out int fallbackTextureCount)
{
var textures = new List<VulkanGuestDrawTexture>(bindings.Count);
fallbackTextureCount = 0;
foreach (var binding in bindings)
{
if (TryCreateVulkanGuestDrawTexture(
ctx,
binding.Descriptor,
binding.IsStorage,
binding.MipLevel,
binding.SamplerDescriptor,
out var texture))
{
textures.Add(texture);
if (texture.IsFallback)
{
fallbackTextureCount++;
}
}
}
return textures;
}
private static IReadOnlyList<VulkanGuestMemoryBuffer> CreateVulkanGuestMemoryBuffers(
IReadOnlyList<Gen5GlobalMemoryBinding> bindings)
{
var buffers = new VulkanGuestMemoryBuffer[bindings.Count];
for (var index = 0; index < bindings.Count; index++)
{
buffers[index] = new VulkanGuestMemoryBuffer(
bindings[index].BaseAddress,
bindings[index].Data);
}
return buffers;
}
private static IReadOnlyList<VulkanGuestVertexBuffer> CreateVulkanGuestVertexBuffers(
IReadOnlyList<Gen5VertexInputBinding> bindings)
{
var buffers = new VulkanGuestVertexBuffer[bindings.Count];
for (var index = 0; index < bindings.Count; index++)
{
var binding = bindings[index];
buffers[index] = new VulkanGuestVertexBuffer(
binding.Location,
binding.ComponentCount,
binding.DataFormat,
binding.NumberFormat,
binding.BaseAddress,
binding.Stride,
binding.OffsetBytes,
binding.Data);
}
return buffers;
}
private static bool TryCreateVulkanGuestDrawTexture(
CpuContext ctx,
TextureDescriptor descriptor,
bool isStorage,
uint mipLevel,
IReadOnlyList<uint> samplerDescriptor,
out VulkanGuestDrawTexture texture)
{
texture = default!;
if (descriptor.Type != Gen5TextureType2D ||
descriptor.Width == 0 ||
descriptor.Height == 0 ||
descriptor.Width > 8192 ||
descriptor.Height > 8192)
{
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true;
}
var sourceWidth = descriptor.TileMode == 0
? GetLinearTexturePitch(
Math.Max(descriptor.Width, descriptor.Pitch),
descriptor.Format)
: descriptor.Width;
var sourceByteCount = GetTextureByteCount(
descriptor.Format,
sourceWidth,
descriptor.Height);
if (sourceByteCount == 0 ||
sourceByteCount > MaxPresentedTextureBytes ||
sourceByteCount > int.MaxValue)
{
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true;
}
if (!isStorage &&
descriptor.Address != 0 &&
VulkanVideoPresenter.IsGpuGuestImageAvailable(
descriptor.Address,
descriptor.Format,
descriptor.NumberType))
{
texture = new VulkanGuestDrawTexture(
descriptor.Address,
descriptor.Width,
descriptor.Height,
descriptor.Format,
descriptor.NumberType,
[],
IsFallback: false,
IsStorage: false,
MipLevels: descriptor.MipLevels,
MipLevel: mipLevel,
Pitch: sourceWidth,
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor));
return true;
}
if (isStorage)
{
var initialPixels = Array.Empty<byte>();
if (descriptor.Address != 0)
{
var storageSource = new byte[(int)sourceByteCount];
if (ctx.Memory.TryRead(descriptor.Address, storageSource) &&
storageSource.AsSpan().IndexOfAnyExcept((byte)0) >= 0)
{
initialPixels = storageSource;
}
}
texture = new VulkanGuestDrawTexture(
descriptor.Address,
descriptor.Width,
descriptor.Height,
descriptor.Format,
descriptor.NumberType,
initialPixels,
IsFallback: descriptor.Address == 0,
IsStorage: true,
MipLevels: descriptor.MipLevels,
MipLevel: mipLevel,
Pitch: sourceWidth,
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor));
return true;
}
var source = new byte[(int)sourceByteCount];
if (!ctx.Memory.TryRead(descriptor.Address, source))
{
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true;
}
TraceTextureHash(descriptor, source);
var nonZero = 0;
for (var i = 0; i < source.Length; i++)
{
if (source[i] != 0)
{
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}");
var rgba = source;
texture = new VulkanGuestDrawTexture(
descriptor.Address,
descriptor.Width,
descriptor.Height,
descriptor.Format,
descriptor.NumberType,
rgba,
IsFallback: false,
IsStorage: isStorage,
MipLevels: descriptor.MipLevels,
MipLevel: mipLevel,
Pitch: sourceWidth,
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor));
return true;
}
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(
bool isStorage,
uint format,
uint numberType)
{
var fallbackFormat = format == 0 ? 10u : format;
var fallbackNumberType = numberType;
return new(
0,
1,
1,
fallbackFormat,
fallbackNumberType,
[0, 0, 0, 255],
IsFallback: true,
IsStorage: isStorage,
MipLevels: 1,
MipLevel: 0);
}
private static void TraceTextureHash(TextureDescriptor descriptor, ReadOnlySpan<byte> source)
{
if (!_traceTextureHashes ||
descriptor.Address == 0 ||
descriptor.Width > 256 ||
descriptor.Height > 256)
{
return;
}
var hash = ComputeFingerprint(source);
var key = (descriptor.Address, descriptor.Width, descriptor.Height);
lock (_textureHashTraceGate)
{
if (_tracedTextureHashes.TryGetValue(key, out var previousHash) &&
previousHash == hash)
{
return;
}
_tracedTextureHashes[key] = hash;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] agc.texture_hash addr=0x{descriptor.Address:X16} " +
$"size={descriptor.Width}x{descriptor.Height} bytes={source.Length} hash=0x{hash:X16}");
}
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
descriptor.Count >= 4
? new VulkanGuestSampler(
descriptor[0],
descriptor[1],
descriptor[2],
descriptor[3])
: default;
private static byte[] ConvertRgba16FloatToRgba8(ReadOnlySpan<byte> source, uint width, uint height)
{
var destination = new byte[checked((int)((ulong)width * height * 4))];
var pixelCount = destination.Length / 4;
for (var pixel = 0; pixel < pixelCount; pixel++)
{
var sourceOffset = pixel * 8;
var destinationOffset = pixel * 4;
destination[destinationOffset + 0] = HalfToByte(BinaryPrimitives.ReadUInt16LittleEndian(source[sourceOffset..]));
destination[destinationOffset + 1] = HalfToByte(BinaryPrimitives.ReadUInt16LittleEndian(source[(sourceOffset + 2)..]));
destination[destinationOffset + 2] = HalfToByte(BinaryPrimitives.ReadUInt16LittleEndian(source[(sourceOffset + 4)..]));
destination[destinationOffset + 3] = HalfToByte(BinaryPrimitives.ReadUInt16LittleEndian(source[(sourceOffset + 6)..]));
}
return destination;
}
private static byte HalfToByte(ushort bits)
{
var value = (float)BitConverter.UInt16BitsToHalf(bits);
if (!float.IsFinite(value))
{
return 0;
}
return (byte)Math.Clamp((int)MathF.Round(value * 255.0f), 0, 255);
}
private static bool TryReadComputeDispatch(
CpuContext ctx,
SubmittedDcbState state,
ulong packetAddress,
uint packetLength,
uint opcode,
out ComputeDispatch dispatch)
{
dispatch = default;
ulong dimensionsAddress;
uint initiator;
if (opcode == ItDispatchDirect)
{
if (packetLength < 5 ||
!ctx.TryReadUInt32(packetAddress + 16, out initiator))
{
return false;
}
dimensionsAddress = packetAddress + 4;
}
else if (packetLength >= 4)
{
if (!ctx.TryReadUInt64(packetAddress + 4, out dimensionsAddress) ||
!ctx.TryReadUInt32(packetAddress + 12, out initiator))
{
return false;
}
}
else
{
if (packetLength < 3 ||
state.IndirectArgsAddress == 0 ||
!ctx.TryReadUInt32(packetAddress + 4, out var dataOffset) ||
!ctx.TryReadUInt32(packetAddress + 8, out initiator))
{
return false;
}
dimensionsAddress = state.IndirectArgsAddress + dataOffset;
}
if ((initiator & 1) == 0 ||
!ctx.TryReadUInt32(dimensionsAddress, out var groupCountX) ||
!ctx.TryReadUInt32(dimensionsAddress + 4, out var groupCountY) ||
!ctx.TryReadUInt32(dimensionsAddress + 8, out var groupCountZ) ||
groupCountX == 0 ||
groupCountY == 0 ||
groupCountZ == 0)
{
return false;
}
dispatch = new ComputeDispatch(groupCountX, groupCountY, groupCountZ);
return true;
}
private static void ObserveComputeDispatch(
CpuContext ctx,
SubmittedGpuState gpuState,
SubmittedDcbState state,
ComputeDispatch dispatch)
{
if (!TryGetShaderAddress(
state.ShRegisters,
ComputePgmLo,
ComputePgmHi,
out var shaderAddress))
{
return;
}
var sequence = ++gpuState.WorkSequence;
ulong shaderHeader;
lock (_submitTraceGate)
{
_shaderHeadersByCode.TryGetValue(shaderAddress, out shaderHeader);
}
var computeSystemRegisters = DecodeComputeSystemRegisters(state.ShRegisters);
if (!Gen5ShaderTranslator.TryCreateState(
ctx,
shaderAddress,
shaderHeader,
state.ShRegisters,
ComputeUserDataRegister,
out var shaderState,
out var error,
computeSystemRegisters) ||
!Gen5ShaderScalarEvaluator.TryEvaluate(
ctx,
shaderState,
out var evaluation,
out error))
{
lock (_submitTraceGate)
{
if (_tracedComputeShaders.Add(shaderAddress))
{
TraceAgcShader(
$"agc.compute_shader cs=0x{shaderAddress:X16} error={error}");
}
}
return;
}
var bindings = evaluation.ImageBindings;
var descriptions = new List<string>(bindings.Count);
var translatedBindings = new List<TranslatedImageBinding>(bindings.Count);
var hasStorageBinding = false;
foreach (var binding in bindings)
{
var isStorage = Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
var descriptorValid = TryDecodeTextureDescriptor(binding.ResourceDescriptor, out var texture);
if (!descriptorValid)
{
texture = CreateFallbackTextureDescriptor(binding.ResourceDescriptor);
}
translatedBindings.Add(
new TranslatedImageBinding(
texture,
isStorage,
binding.MipLevel ?? 0,
binding.SamplerDescriptor));
hasStorageBinding |= isStorage;
var descriptorState = descriptorValid ? string.Empty : "/invalid-desc";
descriptions.Add(
$"{binding.Opcode}@0x{binding.Pc:X}:" +
$"0x{texture.Address:X16}:{texture.Width}x{texture.Height}:" +
$"fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}" +
$"{descriptorState}/{ProbeTexture(ctx, texture)}");
if (isStorage && descriptorValid && texture.Address != 0)
{
gpuState.ComputeImageWriters[texture.Address] = new ComputeImageWriter(
sequence,
shaderAddress,
binding.Opcode);
TraceAgcShader(
$"agc.compute_writer addr=0x{texture.Address:X16} " +
$"fmt={texture.Format} num={texture.NumberType} tile={texture.TileMode} " +
$"size={texture.Width}x{texture.Height} " +
$"cs=0x{shaderAddress:X16} op={binding.Opcode}");
}
}
var localSizeX = GetComputeLocalSize(state.ShRegisters, ComputeNumThreadX);
var localSizeY = GetComputeLocalSize(state.ShRegisters, ComputeNumThreadY);
var localSizeZ = GetComputeLocalSize(state.ShRegisters, ComputeNumThreadZ);
var gpuDispatch = false;
var computeError = string.Empty;
if (hasStorageBinding &&
(ulong)localSizeX * localSizeY * localSizeZ <= 1024)
{
var shaderKey = (
shaderAddress,
ComputeShaderStateFingerprint(evaluation),
localSizeX,
localSizeY,
localSizeZ);
byte[] computeSpirv;
lock (_submitTraceGate)
{
_computeSpirvCache.TryGetValue(shaderKey, out computeSpirv!);
}
if (computeSpirv is null &&
Gen5SpirvTranslator.TryCompileComputeShader(
shaderState,
evaluation,
localSizeX,
localSizeY,
localSizeZ,
out var compiledCompute,
out computeError))
{
computeSpirv = compiledCompute.Spirv;
DumpSpirv(
"cs",
shaderAddress,
shaderKey.Item2,
computeSpirv,
shaderState.Program);
}
if (computeSpirv is not null)
{
lock (_submitTraceGate)
{
_computeSpirvCache.TryAdd(shaderKey, computeSpirv);
}
var textures = CreateVulkanGuestDrawTextures(
ctx,
translatedBindings,
out _);
var globalMemoryBuffers =
CreateVulkanGuestMemoryBuffers(evaluation.GlobalMemoryBindings);
VulkanVideoPresenter.SubmitComputeDispatch(
shaderAddress,
computeSpirv,
textures,
globalMemoryBuffers,
dispatch.GroupCountX,
dispatch.GroupCountY,
dispatch.GroupCountZ);
gpuDispatch = true;
}
}
const int blitCount = 0;
lock (_submitTraceGate)
{
if (_tracedComputeShaders.Add(shaderAddress))
{
TraceAgcShader(
$"agc.compute_shader cs=0x{shaderAddress:X16} " +
$"groups={dispatch.GroupCountX}x{dispatch.GroupCountY}x{dispatch.GroupCountZ} " +
$"local={localSizeX}x{localSizeY}x{localSizeZ} " +
$"sys={DescribeComputeSystemRegisters(computeSystemRegisters)} " +
$"gpu={gpuDispatch} blits={blitCount}" +
(computeError.Length == 0 ? string.Empty : $" error={computeError}") +
$" bindings=[{string.Join(',', descriptions)}]");
}
}
}
private static Gen5ComputeSystemRegisters DecodeComputeSystemRegisters(
IReadOnlyDictionary<uint, uint> registers)
{
registers.TryGetValue(ComputePgmRsrc2, out var rsrc2);
var nextRegister = (rsrc2 >> 1) & 0x1Fu;
uint? workGroupX = null;
uint? workGroupY = null;
uint? workGroupZ = null;
uint? threadGroupSize = null;
if ((rsrc2 & (1u << 7)) != 0)
{
workGroupX = nextRegister++;
}
if ((rsrc2 & (1u << 8)) != 0)
{
workGroupY = nextRegister++;
}
if ((rsrc2 & (1u << 9)) != 0)
{
workGroupZ = nextRegister++;
}
if ((rsrc2 & (1u << 10)) != 0)
{
threadGroupSize = nextRegister++;
}
return new Gen5ComputeSystemRegisters(
workGroupX,
workGroupY,
workGroupZ,
threadGroupSize);
}
private static string DescribeComputeSystemRegisters(Gen5ComputeSystemRegisters registers) =>
$"x={DescribeRegister(registers.WorkGroupXRegister)}," +
$"y={DescribeRegister(registers.WorkGroupYRegister)}," +
$"z={DescribeRegister(registers.WorkGroupZRegister)}," +
$"size={DescribeRegister(registers.ThreadGroupSizeRegister)}";
private static string DescribeRegister(uint? register) =>
register.HasValue ? $"s{register.Value}" : "-";
private static uint SelectExportUserDataRegister(
IReadOnlyDictionary<uint, uint> registers)
{
if (HasUserDataRange(registers, GsUserDataRegister))
{
return GsUserDataRegister;
}
if (HasUserDataRange(registers, EsUserDataRegister))
{
return EsUserDataRegister;
}
if (HasUserDataRange(registers, VsUserDataRegister))
{
return VsUserDataRegister;
}
var esValues = CountUserDataValues(registers, EsUserDataRegister);
var vsValues = CountUserDataValues(registers, VsUserDataRegister);
return esValues == 0 && vsValues != 0
? VsUserDataRegister
: EsUserDataRegister;
}
private static bool HasUserDataRange(
IReadOnlyDictionary<uint, uint> registers,
uint startRegister)
{
for (var index = 0u; index < 16; index++)
{
if (registers.ContainsKey(startRegister + index))
{
return true;
}
}
return false;
}
private static int CountUserDataValues(
IReadOnlyDictionary<uint, uint> registers,
uint startRegister)
{
var count = 0;
for (var index = 0u; index < 16; index++)
{
count += registers.TryGetValue(startRegister + index, out var value) &&
value != 0
? 1
: 0;
}
return count;
}
private static uint GetComputeLocalSize(
IReadOnlyDictionary<uint, uint> registers,
uint register)
{
return registers.TryGetValue(register, out var value)
? Math.Max(value & 0x3FFu, 1u)
: 1u;
}
private static int TryApplySoftwareComputeBlits(
CpuContext ctx,
ulong shaderAddress,
IReadOnlyList<(Gen5ImageBinding Binding, TextureDescriptor Texture)> bindings)
{
var blits = 0;
TextureDescriptor? source = null;
foreach (var (binding, texture) in bindings)
{
if (binding.Opcode.StartsWith("ImageStore", StringComparison.Ordinal))
{
if (source is { } sourceTexture &&
TrySoftwareTextureBlit(ctx, sourceTexture, texture, out var fingerprint))
{
blits++;
var key = (shaderAddress, sourceTexture.Address, texture.Address);
lock (_softwarePresenterGate)
{
if (!_softwareComputeBlitFingerprints.TryGetValue(key, out var previous) ||
previous != fingerprint)
{
_softwareComputeBlitFingerprints[key] = fingerprint;
TraceAgcShader(
$"agc.compute_blit cs=0x{shaderAddress:X16} " +
$"src=0x{sourceTexture.Address:X16}:{sourceTexture.Width}x{sourceTexture.Height}:fmt{sourceTexture.Format}/num{sourceTexture.NumberType}/tile{sourceTexture.TileMode} " +
$"dst=0x{texture.Address:X16}:{texture.Width}x{texture.Height}:fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode} " +
$"fingerprint=0x{fingerprint:X16}");
}
}
}
else if (source is { } cachedSourceTexture &&
VulkanVideoPresenter.TrySubmitGuestImageBlit(
cachedSourceTexture.Address,
cachedSourceTexture.Width,
cachedSourceTexture.Height,
cachedSourceTexture.Format,
texture.Address,
texture.Width,
texture.Height,
texture.Format))
{
blits++;
TraceAgcShader(
$"agc.compute_gpu_blit cs=0x{shaderAddress:X16} " +
$"src=0x{cachedSourceTexture.Address:X16}:{cachedSourceTexture.Width}x{cachedSourceTexture.Height}:fmt{cachedSourceTexture.Format}/num{cachedSourceTexture.NumberType}/tile{cachedSourceTexture.TileMode} " +
$"dst=0x{texture.Address:X16}:{texture.Width}x{texture.Height}:fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}");
}
continue;
}
if (binding.Opcode.StartsWith("Image", StringComparison.Ordinal))
{
source = texture;
}
}
return blits;
}
private static bool TrySoftwareTextureBlit(
CpuContext ctx,
TextureDescriptor source,
TextureDescriptor destination,
out ulong fingerprint)
{
fingerprint = 0;
var bytesPerTexel = GetTextureBytesPerTexel(source.Format);
if (bytesPerTexel == 0 ||
bytesPerTexel != GetTextureBytesPerTexel(destination.Format) ||
source.Type != Gen5TextureType2D ||
destination.Type != Gen5TextureType2D ||
source.Width == 0 ||
source.Height == 0 ||
destination.Width == 0 ||
destination.Height == 0 ||
source.Width > 8192 ||
source.Height > 8192 ||
destination.Width > 8192 ||
destination.Height > 8192)
{
return false;
}
var sourceBytes = checked((ulong)source.Width * source.Height * bytesPerTexel);
var destinationBytes = checked((ulong)destination.Width * destination.Height * bytesPerTexel);
if (sourceBytes == 0 ||
destinationBytes == 0 ||
sourceBytes > MaxPresentedTextureBytes ||
destinationBytes > MaxPresentedTextureBytes ||
sourceBytes > int.MaxValue ||
destinationBytes > int.MaxValue)
{
return false;
}
var sourceData = new byte[(int)sourceBytes];
if (!ctx.Memory.TryRead(source.Address, sourceData))
{
return false;
}
var nonzero = 0;
foreach (var value in sourceData)
{
if (value != 0)
{
nonzero++;
break;
}
}
if (nonzero == 0)
{
return false;
}
var destinationData = new byte[(int)destinationBytes];
for (uint y = 0; y < destination.Height; y++)
{
var sourceY = (uint)(((ulong)y * source.Height) / destination.Height);
for (uint x = 0; x < destination.Width; x++)
{
var sourceX = (uint)(((ulong)x * source.Width) / destination.Width);
var sourceOffset = checked((int)(((ulong)sourceY * source.Width + sourceX) * bytesPerTexel));
var destinationOffset = checked((int)(((ulong)y * destination.Width + x) * bytesPerTexel));
sourceData.AsSpan(sourceOffset, (int)bytesPerTexel)
.CopyTo(destinationData.AsSpan(destinationOffset, (int)bytesPerTexel));
}
}
if (!ctx.Memory.TryWrite(destination.Address, destinationData))
{
return false;
}
fingerprint = ComputeFingerprint(destinationData);
return true;
}
private static string ProbeTexture(CpuContext ctx, TextureDescriptor texture)
{
if (texture.Width == 0 ||
texture.Height == 0)
{
return "probe=unsupported";
}
var totalBytes = GetTextureByteCount(
texture.Format,
texture.Width,
texture.Height);
if (totalBytes == 0)
{
return "probe=unsupported";
}
const int sampleCount = 32;
const int sampleSize = 256;
var sample = new byte[sampleSize];
var reads = 0;
var nonzero = 0;
const ulong offsetBasis = 14695981039346656037UL;
const ulong prime = 1099511628211UL;
var hash = offsetBasis;
for (var index = 0; index < sampleCount; index++)
{
var maxOffset = totalBytes > sampleSize ? totalBytes - sampleSize : 0;
var offset = sampleCount == 1
? 0
: maxOffset * (ulong)index / (sampleCount - 1);
if (!ctx.Memory.TryRead(texture.Address + offset, sample))
{
continue;
}
reads++;
foreach (var value in sample)
{
if (value != 0)
{
nonzero++;
}
hash = (hash ^ value) * prime;
}
}
var bytesPerTexel = GetTextureBytesPerTexel(texture.Format);
var texels = bytesPerTexel is > 0 and <= 16
? string.Join(
'/',
ProbeTextureTexel(ctx, texture.Address, (int)bytesPerTexel),
ProbeTextureTexel(
ctx,
texture.Address +
(((ulong)(texture.Height / 2) * texture.Width) + (texture.Width / 2)) *
bytesPerTexel,
(int)bytesPerTexel),
ProbeTextureTexel(
ctx,
texture.Address + totalBytes - bytesPerTexel,
(int)bytesPerTexel))
: "unsupported";
return $"probe={reads}/{sampleCount}:{nonzero}:0x{hash:X16}:texels={texels}";
}
private static string ProbeTextureTexel(CpuContext ctx, ulong address, int size)
{
var texel = new byte[size];
return ctx.Memory.TryRead(address, texel)
? Convert.ToHexString(texel)
: "unreadable";
}
private static ulong GetTextureBytesPerTexel(uint format) =>
format switch
{
1 => 1UL,
2 => 2UL,
3 => 2UL,
4 => 4UL,
5 => 4UL,
6 => 4UL,
7 => 4UL,
9 => 4UL,
10 => 4UL,
11 => 8UL,
12 => 8UL,
13 => 12UL,
14 => 16UL,
20 => 4UL,
22 => 8UL,
29 => 4UL,
36 => 1UL,
49 => 1UL,
Gen5TextureFormatR8G8B8A8Unorm => 4UL,
62 => 4UL,
64 => 4UL,
Gen5TextureFormatR16G16B16A16Float => 8UL,
75 => 8UL,
_ => 0UL,
};
private static ulong GetTextureByteCount(uint format, uint width, uint height)
{
var bytesPerTexel = GetTextureBytesPerTexel(format);
if (bytesPerTexel != 0)
{
return checked((ulong)width * height * bytesPerTexel);
}
var blockBytes = format switch
{
169 or 170 => 8UL,
171 or 172 or 173 or 174 or 175 or 176 or
177 or 178 or 179 or 180 or 181 or 182 => 16UL,
_ => 0UL,
};
return blockBytes == 0
? 0
: checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes);
}
private static uint GetLinearTexturePitch(uint pitch, uint format)
{
var bytesPerTexel = GetTextureBytesPerTexel(format);
if (bytesPerTexel == 0)
{
return pitch;
}
// GFX10 ADDR_SW_LINEAR aligns each row to 256 bytes.
var pitchAlignment = Math.Max(1UL, 256UL / bytesPerTexel);
return checked((uint)AlignUp(pitch, pitchAlignment));
}
private static ulong AlignUp(ulong value, ulong alignment) =>
(value + alignment - 1) & ~(alignment - 1);
private static void TraceShaderTranslationMiss(
CpuContext ctx,
SubmittedDcbState state,
uint vertexCount,
bool hasExportShader,
ulong exportShaderAddress,
bool hasPixelShader,
ulong pixelShaderAddress,
bool hasPsInputEna,
uint psInputEna,
bool hasPsInputAddr,
uint psInputAddr,
string? translationError = null)
{
var firstFailure = false;
if (!string.IsNullOrEmpty(translationError))
{
lock (_submitTraceGate)
{
firstFailure = _tracedShaderFailures.Add(
(pixelShaderAddress, translationError));
}
}
if (!firstFailure &&
!ShouldTraceHotPath(ref _shaderTranslationMissTraceCount))
{
return;
}
if ((!hasPixelShader || !hasPsInputEna || !hasPsInputAddr) &&
TryMarkMissingPixelShaderBindingsTrace())
{
TraceAgcShader(
$"agc.shader_register_candidates " +
DescribeShaderRegisterCandidates(ctx, state.ShRegisters));
}
var shaderDecode = string.Empty;
if (hasExportShader && hasPixelShader)
{
var shouldDescribe = false;
ulong exportShaderHeader;
ulong pixelShaderHeader;
lock (_submitTraceGate)
{
shouldDescribe = _tracedShaderDecodePairs.Add((exportShaderAddress, pixelShaderAddress));
_shaderHeadersByCode.TryGetValue(exportShaderAddress, out exportShaderHeader);
_shaderHeadersByCode.TryGetValue(pixelShaderAddress, out pixelShaderHeader);
}
if (shouldDescribe)
{
shaderDecode = $" decode={Gen5ShaderTranslator.Describe(ctx, exportShaderAddress, pixelShaderAddress)}";
TraceAgcShader(
$"agc.shader_words es=0x{exportShaderAddress:X16} " +
Gen5ShaderTranslator.DescribeWords(ctx, exportShaderAddress));
if (Gen5ShaderTranslator.TryCreateState(
ctx,
exportShaderAddress,
exportShaderHeader,
state.ShRegisters,
SelectExportUserDataRegister(state.ShRegisters),
out var exportState,
out _,
userDataScalarRegisterBase: NggUserDataScalarRegisterBase) &&
Gen5ShaderTranslator.TryCreateState(
ctx,
pixelShaderAddress,
pixelShaderHeader,
state.ShRegisters,
PsTextureUserDataRegister,
out var pixelState,
out _))
{
TraceAgcShader(
$"agc.shader_state es=0x{exportShaderAddress:X16} " +
Gen5ShaderTranslator.DescribeState(exportState));
TraceAgcShader(
$"agc.shader_state ps=0x{pixelShaderAddress:X16} " +
Gen5ShaderTranslator.DescribeState(pixelState));
if (Gen5ShaderScalarEvaluator.TryEvaluate(
ctx,
pixelState,
out var evaluation,
out var bindingError))
{
foreach (var binding in evaluation.ImageBindings)
{
TraceAgcShader(
$"agc.shader_binding ps=0x{pixelShaderAddress:X16} " +
$"pc=0x{binding.Pc:X} op={binding.Opcode} " +
$"resource={FormatShaderDwords(binding.ResourceDescriptor)} " +
$"sampler={FormatShaderDwords(binding.SamplerDescriptor)}");
}
foreach (var binding in evaluation.GlobalMemoryBindings)
{
TraceAgcShader(
$"agc.shader_global_binding ps=0x{pixelShaderAddress:X16} " +
$"saddr=s{binding.ScalarAddress} " +
$"base=0x{binding.BaseAddress:X16} bytes={binding.Data.Length} " +
$"pcs={string.Join(',', binding.InstructionPcs.Select(pc => $"0x{pc:X}"))}");
}
if (Gen5SpirvTranslator.TryCompilePixelShader(
pixelState,
evaluation,
Gen5PixelOutputKind.Float,
out var compiledPixel,
out var compileError))
{
TraceAgcShader(
$"agc.shader_spirv ps=0x{pixelShaderAddress:X16} " +
$"bytes={compiledPixel.Spirv.Length} bindings={evaluation.ImageBindings.Count} " +
$"global_buffers={evaluation.GlobalMemoryBindings.Count}");
}
else
{
TraceAgcShader(
$"agc.shader_spirv_error ps=0x{pixelShaderAddress:X16} " +
compileError.ReplaceLineEndings(" "));
}
}
else
{
TraceAgcShader(
$"agc.shader_binding_error ps=0x{pixelShaderAddress:X16} " +
bindingError);
}
}
}
}
TraceAgcShader(
$"agc.shader_translate_miss vertices={vertexCount} " +
$"es={(hasExportShader ? $"0x{exportShaderAddress:X16}" : "missing")} " +
$"ps={(hasPixelShader ? $"0x{pixelShaderAddress:X16}" : "missing")} " +
$"ps_ena={(hasPsInputEna ? $"0x{psInputEna:X8}" : "missing")} " +
$"ps_addr={(hasPsInputAddr ? $"0x{psInputAddr:X8}" : "missing")}" +
(string.IsNullOrEmpty(translationError) ? string.Empty : $" error={translationError}") +
shaderDecode);
}
private static bool TryMarkMissingPixelShaderBindingsTrace()
{
lock (_submitTraceGate)
{
if (_tracedMissingPixelShaderBindings)
{
return false;
}
_tracedMissingPixelShaderBindings = true;
return true;
}
}
private static string DescribeShaderRegisterCandidates(
CpuContext ctx,
IReadOnlyDictionary<uint, uint> registers)
{
var candidates = new List<(uint Register, ulong Address, ulong Header)>();
lock (_submitTraceGate)
{
foreach (var (register, lo) in registers)
{
if (!registers.TryGetValue(register + 1, out var hi))
{
continue;
}
var address = ((ulong)hi << 40) | ((ulong)lo << 8);
if (address != 0 &&
_shaderHeadersByCode.TryGetValue(address, out var header))
{
candidates.Add((register, address, header));
}
}
}
if (candidates.Count == 0)
{
return "none";
}
return string.Join(
',',
candidates
.OrderBy(candidate => candidate.Register)
.Take(16)
.Select(candidate =>
{
var type = ctx.TryReadByte(
candidate.Header + ShaderTypeOffset,
out var shaderType)
? shaderType.ToString()
: "?";
return
$"sh[0x{candidate.Register:X}/0x{candidate.Register + 1:X}]=" +
$"0x{candidate.Address:X16}:type{type}";
}));
}
private static bool TryGetShaderAddress(
IReadOnlyDictionary<uint, uint> registers,
uint loRegister,
uint hiRegister,
out ulong address)
{
address = 0;
if (!registers.TryGetValue(loRegister, out var lo) ||
!registers.TryGetValue(hiRegister, out var hi))
{
return false;
}
address = ((ulong)hi << 40) | ((ulong)lo << 8);
return address != 0;
}
private static bool TryReadTextureDescriptor(
CpuContext ctx,
ulong packetAddress,
uint packetLength,
out TextureDescriptor descriptor)
{
descriptor = default;
if (packetLength < 10 ||
!ctx.TryReadUInt32(packetAddress + 4, out var startRegister))
{
return false;
}
var valueCount = packetLength - 2;
if (startRegister > PsTextureUserDataRegister ||
startRegister + valueCount < PsTextureUserDataRegister + 8)
{
return false;
}
var descriptorAddress =
packetAddress +
8 +
((ulong)(PsTextureUserDataRegister - startRegister) * sizeof(uint));
Span<uint> fields = stackalloc uint[4];
for (var i = 0; i < fields.Length; i++)
{
if (!ctx.TryReadUInt32(descriptorAddress + ((ulong)i * sizeof(uint)), out fields[i]))
{
return false;
}
}
return TryDecodeTextureDescriptor(fields.ToArray(), out descriptor);
}
private static bool TryDecodeTextureDescriptor(
IReadOnlyList<uint> fields,
out TextureDescriptor descriptor)
{
descriptor = default;
if (fields.Count < 4)
{
return false;
}
// GFX10/RDNA2 T# layout: WIDTH is split across word1[31:30] (lo 2 bits)
// and word2[11:0] (hi 12 bits); FORMAT is the combined 9-bit field at
// word1[28:20]. Verified against Kyty's decode of the same game
// descriptors (fmt=56=8_8_8_8_UNORM, extent 1280x720, sw_mode 27).
// GNM T# exposes a 38-bit baseaddr256 field, but RPCSX and the
// Demon's Souls descriptors both show that only the low 32 bits are
// part of the guest GPU VA. The upper baseaddr bits carry resource
// metadata and produce bogus addresses such as 0x00003804... if used.
var address = ((ulong)(uint)((((ulong)fields[1] << 32) | fields[0]) & 0x3F_FFFF_FFFFUL)) << 8;
var width = (((fields[1] >> 30) & 0x3u) | ((fields[2] & 0xFFFu) << 2)) + 1;
var height = ((fields[2] >> 14) & 0x3FFFu) + 1;
var format = (fields[1] >> 20) & 0x1FFu;
var numberType = (fields[1] >> 26) & 0xFu;
var tileMode = (fields[3] >> 20) & 0x1Fu;
var type = (fields[3] >> 28) & 0xFu;
var baseLevel = (fields[3] >> 12) & 0xFu;
var lastLevel = (fields[3] >> 16) & 0xFu;
// The 128-bit RDNA2 2D resource derives pitch[12:0] from width;
// the optional extension word only supplies pitch[13].
var pitch = width;
if (fields.Count >= 5)
{
pitch = ((((width - 1) & 0x1FFFu) | (((fields[4] >> 13) & 1u) << 13)) + 1);
}
var dstSelect = fields[3] & 0xFFFu;
if (address == 0 || width == 0 || height == 0)
{
return false;
}
descriptor = new TextureDescriptor(
address,
width,
height,
format,
numberType,
tileMode,
type,
baseLevel,
lastLevel,
pitch,
dstSelect);
return true;
}
private static TextureDescriptor CreateFallbackTextureDescriptor(IReadOnlyList<uint> fields)
{
var format = Gen5TextureFormatR8G8B8A8Unorm;
var numberType = 0u;
var tileMode = 0u;
if (fields.Count >= 4)
{
format = (fields[1] >> 20) & 0x1FFu;
numberType = (fields[1] >> 26) & 0xFu;
tileMode = (fields[3] >> 20) & 0x1Fu;
if (format == 0)
{
format = Gen5TextureFormatR8G8B8A8Unorm;
}
}
return new TextureDescriptor(
Address: 0,
Width: 1,
Height: 1,
Format: format,
NumberType: numberType,
TileMode: tileMode,
Type: Gen5TextureType2D,
BaseLevel: 0,
LastLevel: 0,
Pitch: 1,
DstSelect: 0xFAC);
}
private static bool TrySoftwarePresent(
CpuContext ctx,
TextureDescriptor source,
int videoOutHandle,
int displayBufferIndex)
{
if (source.Format != Gen5TextureFormatR8G8B8A8Unorm ||
source.TileMode != 0 ||
source.Type != Gen5TextureType2D ||
source.Width > 8192 ||
source.Height > 8192 ||
!VideoOutExports.TryGetDisplayBufferInfo(videoOutHandle, displayBufferIndex, out var destination) ||
destination.Address == 0 ||
destination.Width == 0 ||
destination.Height == 0 ||
destination.Width > 8192 ||
destination.Height > 8192 ||
destination.TilingMode != 0 ||
destination.PixelFormat is not (
VideoOutPixelFormatA8R8G8B8Srgb or
VideoOutPixelFormatA8B8G8R8Srgb or
VideoOutPixelFormatB8G8R8A8Unorm or
VideoOutPixelFormatR8G8B8A8Unorm))
{
return false;
}
var sourceByteCount = checked((ulong)source.Width * source.Height * 4);
if (sourceByteCount > 256UL * 1024UL * 1024UL)
{
return false;
}
var sourceBytes = new byte[(int)sourceByteCount];
if (!ctx.Memory.TryRead(source.Address, sourceBytes))
{
return false;
}
var fingerprint = ComputeFingerprint(sourceBytes);
var fingerprintKey = (source.Address, destination.Address);
lock (_softwarePresenterGate)
{
if (_softwarePresenterFingerprints.TryGetValue(fingerprintKey, out var previousFingerprint) &&
previousFingerprint == fingerprint)
{
return true;
}
}
var destinationPitch = destination.PitchInPixel == 0
? destination.Width
: destination.PitchInPixel;
if (destinationPitch < destination.Width)
{
return false;
}
var destinationRow = new byte[checked((int)destinationPitch * 4)];
var rgbaDestination = destination.PixelFormat is
VideoOutPixelFormatA8B8G8R8Srgb or
VideoOutPixelFormatR8G8B8A8Unorm;
for (uint y = 0; y < destination.Height; y++)
{
var sourceY = (uint)(((ulong)y * source.Height) / destination.Height);
for (uint x = 0; x < destination.Width; x++)
{
var sourceX = (uint)(((ulong)x * source.Width) / destination.Width);
var sourceOffset = checked((int)(((ulong)sourceY * source.Width + sourceX) * 4));
var destinationOffset = checked((int)x * 4);
if (rgbaDestination)
{
destinationRow[destinationOffset + 0] = sourceBytes[sourceOffset + 0];
destinationRow[destinationOffset + 1] = sourceBytes[sourceOffset + 1];
destinationRow[destinationOffset + 2] = sourceBytes[sourceOffset + 2];
}
else
{
destinationRow[destinationOffset + 0] = sourceBytes[sourceOffset + 2];
destinationRow[destinationOffset + 1] = sourceBytes[sourceOffset + 1];
destinationRow[destinationOffset + 2] = sourceBytes[sourceOffset + 0];
}
destinationRow[destinationOffset + 3] = sourceBytes[sourceOffset + 3];
}
var destinationAddress = destination.Address + ((ulong)y * destinationPitch * 4);
if (!ctx.Memory.TryWrite(destinationAddress, destinationRow))
{
return false;
}
}
lock (_softwarePresenterGate)
{
_softwarePresenterFingerprints[fingerprintKey] = fingerprint;
}
VideoOutExports.SubmitHostRgbaFrame(sourceBytes, source.Width, source.Height);
TraceAgc(
$"agc.software_presenter src=0x{source.Address:X16} {source.Width}x{source.Height} fmt={source.Format}/num{source.NumberType} " +
$"dst=0x{destination.Address:X16} {destination.Width}x{destination.Height} fingerprint=0x{fingerprint:X16}");
return true;
}
private static ulong ComputeFingerprint(ReadOnlySpan<byte> bytes)
{
const ulong fnvOffsetBasis = 14695981039346656037UL;
const ulong fnvPrime = 1099511628211UL;
var fingerprint = fnvOffsetBasis;
foreach (var value in bytes)
{
fingerprint = (fingerprint ^ value) * fnvPrime;
}
return fingerprint;
}
private static void TraceSubmittedPacket(
CpuContext ctx,
ulong packetAddress,
uint dwordOffset,
uint header,
uint length,
uint op,
uint register)
{
TraceAgc(
$"agc.dcb.packet dw={dwordOffset} addr=0x{packetAddress:X16} header=0x{header:X8} len={length} op=0x{op:X2} reg=0x{register:X2}");
var payloadCount = Math.Min(length - 1, 32u);
for (uint i = 0; i < payloadCount; i++)
{
if (!ctx.TryReadUInt32(packetAddress + ((ulong)(i + 1) * sizeof(uint)), out var value))
{
return;
}
TraceAgc($"agc.dcb.payload dw={dwordOffset + i + 1} value=0x{value:X8}");
}
if (op != ItNop ||
register is not (RCxRegsIndirect or RShRegsIndirect or RUcRegsIndirect) ||
length < 4 ||
!ctx.TryReadUInt32(packetAddress + 4, out var registerCount) ||
!ctx.TryReadUInt64(packetAddress + 8, out var registersAddress))
{
return;
}
var registerSpace = register == RCxRegsIndirect ? "cx" : register == RShRegsIndirect ? "sh" : "uc";
var tracedCount = Math.Min(registerCount, 256u);
TraceAgc($"agc.dcb.indirect space={registerSpace} regs=0x{registersAddress:X16} count={registerCount}");
for (uint i = 0; i < tracedCount; i++)
{
var entryAddress = registersAddress + ((ulong)i * 8);
if (!ctx.TryReadUInt32(entryAddress, out var registerOffset) ||
!ctx.TryReadUInt32(entryAddress + 4, out var value))
{
TraceAgc($"agc.dcb.indirect_read_failed space={registerSpace} index={i} addr=0x{entryAddress:X16}");
return;
}
TraceAgc($"agc.dcb.reg space={registerSpace} index={i} offset=0x{registerOffset:X4} value=0x{value:X8}");
}
if (tracedCount != registerCount)
{
TraceAgc($"agc.dcb.indirect_truncated space={registerSpace} traced={tracedCount} total={registerCount}");
}
}
private static bool PatchShaderProgramRegisters(CpuContext ctx, ulong headerAddress, ulong codeAddress)
{
if (!ctx.TryReadUInt64(headerAddress + ShaderShRegistersOffset, out var shRegistersAddress) ||
!ctx.TryReadByte(headerAddress + ShaderTypeOffset, out var shaderType) ||
!ctx.TryReadByte(headerAddress + ShaderNumShRegistersOffset, out var registerCount))
{
return false;
}
if (shRegistersAddress == 0 || registerCount < 2)
{
return false;
}
if (!ctx.TryReadUInt32(shRegistersAddress, out var loRegister) ||
!ctx.TryReadUInt32(shRegistersAddress + 8, out var hiRegister))
{
return false;
}
var expectedLo = shaderType switch
{
0 => ComputePgmLo,
1 => SpiShaderPgmLoPs,
2 or 6 => SpiShaderPgmLoEs,
4 => SpiShaderPgmLoGs,
7 => SpiShaderPgmLoLs,
_ => 0u,
};
var expectedHi = shaderType switch
{
0 => ComputePgmHi,
1 => SpiShaderPgmHiPs,
2 or 6 => SpiShaderPgmHiEs,
4 => SpiShaderPgmHiGs,
7 => SpiShaderPgmHiLs,
_ => 0u,
};
if (expectedLo == 0 || loRegister != expectedLo || hiRegister != expectedHi)
{
TraceCreateShader(0, headerAddress, codeAddress, $"unexpected-registers type={shaderType} lo=0x{loRegister:X8} hi=0x{hiRegister:X8}");
return false;
}
var loValue = (uint)((codeAddress >> 8) & 0xFFFF_FFFFUL);
var hiValue = (uint)((codeAddress >> 40) & 0xFFUL);
return ctx.TryWriteUInt32(shRegistersAddress + sizeof(uint), loValue) &&
ctx.TryWriteUInt32(shRegistersAddress + 8 + sizeof(uint), hiValue);
}
private static bool IsEsGeometryShaderType(byte shaderType) =>
shaderType is 2 or 4 or 6;
private static int SetIndirectPatchAddress(CpuContext ctx, string registerSpace)
{
var commandAddress = ctx[CpuRegister.Rdi];
var registersAddress = ctx[CpuRegister.Rsi];
if (commandAddress == 0 || registersAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!ctx.TryWriteUInt32(commandAddress + 8, (uint)(registersAddress & 0xFFFF_FFFFUL)) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)(registersAddress >> 32)))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.patch_{registerSpace}_addr cmd=0x{commandAddress:X16} regs=0x{registersAddress:X16}");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int AddIndirectPatchRegisters(CpuContext ctx, string registerSpace)
{
var commandAddress = ctx[CpuRegister.Rdi];
var registerCount = (uint)ctx[CpuRegister.Rsi];
if (commandAddress == 0)
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!ctx.TryReadUInt32(commandAddress + 4, out var currentCount) ||
!ctx.TryWriteUInt32(commandAddress + 4, currentCount + registerCount))
{
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.patch_{registerSpace}_add cmd=0x{commandAddress:X16} add={registerCount} total={currentCount + registerCount}");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int DcbSetRegistersIndirect(CpuContext ctx, uint packetRegister, string registerSpace)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var registersAddress = ctx[CpuRegister.Rsi];
var registerCount = (uint)ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 4, out var commandAddress) ||
!ctx.TryWriteUInt32(commandAddress, Pm4(4, ItNop, packetRegister)) ||
!ctx.TryWriteUInt32(commandAddress + 4, registerCount) ||
!ctx.TryWriteUInt32(commandAddress + 8, (uint)(registersAddress & 0xFFFF_FFFFUL)) ||
!ctx.TryWriteUInt32(commandAddress + 12, (uint)(registersAddress >> 32)))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_set_{registerSpace}_indirect buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} regs=0x{registersAddress:X16} count={registerCount}");
return ReturnPointer(ctx, commandAddress);
}
private static bool TryAllocateCommandDwords(CpuContext ctx, ulong commandBufferAddress, uint sizeDwords, out ulong commandAddress)
{
commandAddress = 0;
if (sizeDwords == 0 ||
!ctx.TryReadUInt64(commandBufferAddress + CommandBufferCursorUpOffset, out var cursorUp) ||
!ctx.TryReadUInt64(commandBufferAddress + CommandBufferCursorDownOffset, out var cursorDown) ||
!ctx.TryReadUInt64(commandBufferAddress + CommandBufferCallbackOffset, out var callback) ||
!ctx.TryReadUInt32(commandBufferAddress + CommandBufferReservedDwOffset, out var reservedDwords))
{
return false;
}
var availableDwords = cursorDown >= cursorUp
? Math.Min((cursorDown - cursorUp) / sizeof(uint), uint.MaxValue)
: 0;
var remainingDwords = (uint)Math.Max(availableDwords, reservedDwords) - reservedDwords;
if (sizeDwords > remainingDwords)
{
TraceAgc($"agc.cmd_alloc_full buf=0x{commandBufferAddress:X16} need={sizeDwords} remaining={remainingDwords} callback=0x{callback:X16}");
return false;
}
var nextCursor = cursorUp + ((ulong)sizeDwords * sizeof(uint));
if (!ctx.TryWriteUInt64(commandBufferAddress + CommandBufferCursorUpOffset, nextCursor))
{
return false;
}
commandAddress = cursorUp;
return true;
}
private static bool CopyShaderRegister(CpuContext ctx, ulong sourceAddress, ulong destinationAddress)
{
if (!ctx.TryReadUInt32(sourceAddress, out var offset) ||
!ctx.TryReadUInt32(sourceAddress + sizeof(uint), out var value))
{
return false;
}
return ctx.TryWriteUInt32(destinationAddress, offset) &&
ctx.TryWriteUInt32(destinationAddress + sizeof(uint), value);
}
private static bool RelocatePointerField(CpuContext ctx, ulong fieldAddress)
{
if (!ctx.TryReadUInt64(fieldAddress, out var relativeAddress))
{
return false;
}
if (relativeAddress == 0)
{
return true;
}
return ctx.TryWriteUInt64(fieldAddress, fieldAddress + relativeAddress);
}
private static int ReturnRegisterDefaults(CpuContext ctx, bool internalDefaults)
{
var version = (uint)ctx[CpuRegister.Rdi];
if (!IsSupportedRegisterDefaultsVersion(version))
{
return ReturnPointer(ctx, 0);
}
if (!TryGetRegisterDefaultsAllocation(ctx, out var allocation))
{
return ReturnPointer(ctx, 0);
}
var address = internalDefaults ? allocation.Internal : allocation.Primary;
TraceAgc($"agc.get_register_defaults internal={internalDefaults} version={version} address=0x{address:X16}");
return ReturnPointer(ctx, address);
}
private static bool IsSupportedRegisterDefaultsVersion(uint version)
{
return version is
RegisterDefaultsVersion7 or
RegisterDefaultsVersion8 or
RegisterDefaultsVersion10 or
RegisterDefaultsVersion13;
}
private static bool TryGetRegisterDefaultsAllocation(
CpuContext ctx,
out RegisterDefaultsAllocation allocation)
{
lock (_registerDefaultsGate)
{
if (_registerDefaultsAllocations.TryGetValue(ctx.Memory, out allocation!))
{
return true;
}
if (!TryBuildRegisterDefaults(
ctx,
PrimaryRegisterDefaults,
cxTableLength: 78,
shTableLength: 29,
ucTableLength: 20,
out var primaryAddress) ||
!TryBuildRegisterDefaults(
ctx,
InternalRegisterDefaults,
cxTableLength: 4,
shTableLength: 15,
ucTableLength: 3,
out var internalAddress))
{
allocation = null!;
return false;
}
allocation = new RegisterDefaultsAllocation(primaryAddress, internalAddress);
_registerDefaultsAllocations.Add(ctx.Memory, allocation);
return true;
}
}
private static bool TryBuildRegisterDefaults(
CpuContext ctx,
RegisterDefaultGroup[] groups,
int cxTableLength,
int shTableLength,
int ucTableLength,
out ulong address)
{
var cxTableOffset = AlignUp(RegisterDefaultsSize, sizeof(ulong));
var shTableOffset = cxTableOffset + (cxTableLength * sizeof(ulong));
var ucTableOffset = shTableOffset + (shTableLength * sizeof(ulong));
var typesOffset = AlignUp(ucTableOffset + (ucTableLength * sizeof(ulong)), sizeof(uint));
var registerBlocksOffset = AlignUp(typesOffset + (groups.Length * 3 * sizeof(uint)), sizeof(ulong));
var blobLength = registerBlocksOffset + (groups.Length * RegisterDefaultBlockSize);
if (!KernelMemoryCompatExports.TryAllocateHleData(ctx, (ulong)blobLength, 0x1000, out address))
{
return false;
}
var blob = new byte[blobLength];
WriteBlobUInt64(blob, 0x00, address + (ulong)cxTableOffset);
WriteBlobUInt64(blob, 0x08, address + (ulong)shTableOffset);
WriteBlobUInt64(blob, 0x10, address + (ulong)ucTableOffset);
WriteBlobUInt64(blob, 0x30, address + (ulong)typesOffset);
WriteBlobUInt32(blob, 0x38, (uint)groups.Length);
for (var groupIndex = 0; groupIndex < groups.Length; groupIndex++)
{
var group = groups[groupIndex];
if (group.Registers.Length > 16)
{
return false;
}
var tableOffset = group.Space switch
{
0 => cxTableOffset,
1 => shTableOffset,
2 => ucTableOffset,
_ => -1,
};
var tableLength = group.Space switch
{
0 => cxTableLength,
1 => shTableLength,
2 => ucTableLength,
_ => 0,
};
if (tableOffset < 0 || group.Index >= tableLength)
{
return false;
}
var registerBlockOffset = registerBlocksOffset + (groupIndex * RegisterDefaultBlockSize);
WriteBlobUInt64(
blob,
tableOffset + ((int)group.Index * sizeof(ulong)),
address + (ulong)registerBlockOffset);
var typeEntryOffset = typesOffset + (groupIndex * 3 * sizeof(uint));
WriteBlobUInt32(blob, typeEntryOffset, group.Type);
WriteBlobUInt32(blob, typeEntryOffset + sizeof(uint), (group.Index * 4) + group.Space);
for (var registerIndex = 0; registerIndex < group.Registers.Length; registerIndex++)
{
var register = group.Registers[registerIndex];
var registerOffset = registerBlockOffset + (registerIndex * 2 * sizeof(uint));
WriteBlobUInt32(blob, registerOffset, register.Offset);
WriteBlobUInt32(blob, registerOffset + sizeof(uint), register.Value);
}
}
return ctx.Memory.TryWrite(address, blob);
}
private static int AlignUp(int value, int alignment) =>
(value + alignment - 1) & -alignment;
private static void WriteBlobUInt32(Span<byte> blob, int offset, uint value) =>
BinaryPrimitives.WriteUInt32LittleEndian(blob[offset..], value);
private static void WriteBlobUInt64(Span<byte> blob, int offset, ulong value) =>
BinaryPrimitives.WriteUInt64LittleEndian(blob[offset..], value);
private static int ReturnPointer(CpuContext ctx, ulong pointer)
{
ctx[CpuRegister.Rax] = pointer;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static uint Pm4(uint lengthDwords, uint op, uint register) =>
0xC0000000u |
((((ushort)lengthDwords - 2u) & 0x3FFFu) << 16) |
((op & 0xFFu) << 8) |
((register & 0x3Fu) << 2);
private static uint Pm4Length(uint header) =>
((header >> 16) & 0x3FFFu) + 2u;
private static bool TryReadGuestCString(
CpuContext ctx,
ulong address,
int maximumLength,
out byte[] bytes)
{
if (address == 0)
{
bytes = [];
return true;
}
var values = new List<byte>(Math.Min(maximumLength, 128));
for (var index = 0; index < maximumLength; index++)
{
if (!ctx.TryReadByte(address + (ulong)index, out var value))
{
bytes = [];
return false;
}
if (value == 0)
{
bytes = [.. values];
return true;
}
values.Add(value);
}
bytes = [];
return false;
}
private static bool TryGetPacketIdentity(
CpuContext ctx,
ulong commandAddress,
out uint op,
out uint register)
{
op = 0;
register = 0;
if (commandAddress == 0 || !ctx.TryReadUInt32(commandAddress, out var header))
{
return false;
}
op = (header >> 8) & 0xFFu;
register = (header >> 2) & 0x3Fu;
return true;
}
private static bool TryCopyGuestMemory(
CpuContext ctx,
ulong sourceAddress,
ulong destinationAddress,
uint byteCount)
{
if (sourceAddress == destinationAddress)
{
return true;
}
var buffer = new byte[Math.Min(byteCount, 64u * 1024u)];
ulong offset = 0;
while (offset < byteCount)
{
var chunkLength = (int)Math.Min((ulong)buffer.Length, byteCount - offset);
var chunk = buffer.AsSpan(0, chunkLength);
if (!ctx.Memory.TryRead(sourceAddress + offset, chunk) ||
!ctx.Memory.TryWrite(destinationAddress + offset, chunk))
{
return false;
}
offset += (uint)chunkLength;
}
return true;
}
private static bool TryFillGuestMemory(
CpuContext ctx,
uint value,
ulong destinationAddress,
uint byteCount)
{
var buffer = new byte[Math.Min(byteCount, 64u * 1024u)];
Span<byte> encoded = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(encoded, value);
for (var offset = 0; offset < buffer.Length; offset += sizeof(uint))
{
var remaining = Math.Min(sizeof(uint), buffer.Length - offset);
encoded[..remaining].CopyTo(buffer.AsSpan(offset, remaining));
}
ulong destinationOffset = 0;
while (destinationOffset < byteCount)
{
var chunkLength = (int)Math.Min(
(ulong)buffer.Length,
byteCount - destinationOffset);
if (!ctx.Memory.TryWrite(
destinationAddress + destinationOffset,
buffer.AsSpan(0, chunkLength)))
{
return false;
}
destinationOffset += (uint)chunkLength;
}
return true;
}
private static bool ShouldTraceHotPath(ref long counter)
{
var count = Interlocked.Increment(ref counter);
return count <= 8 || count % 100_000 == 0;
}
private static void TraceAgc(string message)
{
if (!_traceAgc)
{
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] {message}");
}
private static void TraceAgcShader(string message)
{
if (!_traceAgcShader)
{
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] {message}");
}
private static string FormatShaderDwords(IReadOnlyList<uint> values) =>
values.Count == 0
? "none"
: string.Join(',', values.Select(static value => $"{value:X8}"));
private static string FormatTextureDescriptor(TextureDescriptor descriptor) =>
$"addr=0x{descriptor.Address:X16} {descriptor.Width}x{descriptor.Height} " +
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
$"type={descriptor.Type} levels={descriptor.BaseLevel}-{descriptor.LastLevel} " +
$"pitch={descriptor.Pitch} dst=0x{descriptor.DstSelect:X3}";
private static void DumpSpirv(
string stage,
ulong shaderAddress,
ulong stateFingerprint,
byte[] spirv,
Gen5ShaderProgram program)
{
if (spirv.Length == 0 ||
!string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_SPIRV"),
"1",
StringComparison.Ordinal))
{
return;
}
var directory = Path.Combine(AppContext.BaseDirectory, "shader-dumps");
Directory.CreateDirectory(directory);
var name = $"{shaderAddress:X16}-{stateFingerprint:X16}.{stage}";
File.WriteAllBytes(Path.Combine(directory, $"{name}.spv"), spirv);
var lines = new List<string>(program.Instructions.Count + 2)
{
$"address=0x{program.Address:X16}",
"pc words opcode destinations <- sources control",
};
foreach (var instruction in program.Instructions)
{
lines.Add(
$"0x{instruction.Pc:X4} " +
$"{string.Join('_', instruction.Words.Select(static word => $"{word:X8}"))} " +
$"{instruction.Opcode} " +
$"{string.Join(',', instruction.Destinations)} <- " +
$"{string.Join(',', instruction.Sources)} " +
$"{instruction.Control}");
}
File.WriteAllLines(Path.Combine(directory, $"{name}.ir.txt"), lines);
}
private static void TraceCreateShader(ulong destinationAddress, ulong headerAddress, ulong codeAddress, string detail)
{
var isOk = string.Equals(detail, "ok", StringComparison.Ordinal);
if (isOk &&
(!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) ||
!ShouldTraceHotPath(ref _createShaderTraceCount)))
{
return;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] agc.create_shader dst=0x{destinationAddress:X16} header=0x{headerAddress:X16} code=0x{codeAddress:X16} {detail}");
}
}