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

481 lines
20 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
namespace SharpEmu.Libs.Agc;
public static partial class AgcExports
{
// This partial constructs AGC draw-command packets.
[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);
return DcbSetIndexSizePacket(
ctx,
commandBufferAddress,
indexSize,
cachePolicy,
perInstanceObjectIdSupport: 0);
}
[SysAbiExport(
ExportName = "sceAgcDcbSetIndexSizeGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetIndexSizeGetSize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 3u * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
private static int DcbSetIndexSizePacket(
CpuContext ctx,
ulong commandBufferAddress,
uint indexSize,
uint cachePolicy,
uint perInstanceObjectIdSupport)
{
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 3, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(3, ItSetUconfigRegIndex, 0)) ||
!TryWriteUInt32(ctx, commandAddress + 4, 0x2000_0000u | VgtIndexType) ||
!TryWriteUInt32(
ctx,
commandAddress + 8,
0x400u |
(indexSize & 0x3u) |
((cachePolicy & 0x3u) << 6) |
((perInstanceObjectIdSupport & 0x1u) << 14)))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_set_index_size buf=0x{commandBufferAddress:X16} " +
$"cmd=0x{commandAddress:X16} size={indexSize & 0x3u} " +
$"cache={cachePolicy & 0x3u} instance_id={perInstanceObjectIdSupport & 0x1u}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "8N2tmT3jmC8",
ExportName = "sceAgcDcbSetIndexCount",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetIndexCount(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var indexCount = (uint)ctx[CpuRegister.Rsi];
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(2, ItNop, RIndexCount)) ||
!TryWriteUInt32(ctx, commandAddress + 4, indexCount))
{
return ReturnPointer(ctx, 0);
}
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "mljzuGDZRQ4",
ExportName = "sceAgcDcbSetIndexCountGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbSetIndexCountGetSize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 7u * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
[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) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(2, ItNumInstances, 0)) ||
!TryWriteUInt32(ctx, 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 = ctx[CpuRegister.Rcx];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
NoteDrawModifier("sceAgcDcbDrawIndex", modifier);
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var baseCommand) ||
!TryWriteUInt32(ctx, baseCommand, Pm4(3, ItIndexBase, 0)) ||
!TryWriteUInt32(ctx, baseCommand + 4, (uint)indexAddress) ||
!TryWriteUInt32(ctx, baseCommand + 8, (uint)(indexAddress >> 32)) ||
!TryWriteUInt32(ctx, baseCommand + 12, Pm4(2, ItIndexBufferSize, 0)) ||
!TryWriteUInt32(ctx, baseCommand + 16, indexCount))
{
return ReturnPointer(ctx, 0);
}
// DRAW_INDEX_2 is six dwords: header, maximum index count, the
// 64-bit index-buffer base, the draw count and the initiator. The
// former five-dword packet omitted both the real base and the count
// field, so every call made by Unity looked like a zero-count draw to
// the submitted-command parser and the complete scene was discarded.
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 6, out var drawCommand) ||
!TryWriteUInt32(ctx, drawCommand, Pm4(6, ItDrawIndex2, 0)) ||
!TryWriteUInt32(ctx, drawCommand + 4, indexCount) ||
!TryWriteUInt32(ctx, drawCommand + 8, (uint)indexAddress) ||
!TryWriteUInt32(ctx, drawCommand + 12, (uint)(indexAddress >> 32)) ||
!TryWriteUInt32(ctx, drawCommand + 16, indexCount) ||
!TryWriteUInt32(ctx, drawCommand + 20, 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 = "1q1titRBL6o",
ExportName = "sceAgcDcbDrawIndirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndirect(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var dataOffset = (uint)ctx[CpuRegister.Rsi];
var drawModifier = ctx[CpuRegister.Rdx];
if (commandBufferAddress == 0)
{
Interlocked.Increment(ref _indirectDrawEmitRejectCount);
return ReturnPointer(ctx, 0);
}
var modifierBits = (uint)drawModifier;
var shaderStage = modifierBits >> 29;
var scalarRegisterBase = shaderStage is 3 or 5 ? 0x10Cu : 0x8Cu;
var firstVertexRegister = (modifierBits & 1u) != 0
? scalarRegisterBase + ((modifierBits >> 9) & 0x1Fu)
: 0x280u;
var firstInstanceRegister = (modifierBits & 4u) != 0
? scalarRegisterBase + ((modifierBits >> 19) & 0x1Fu)
: 0x280u;
var drawInitiator = (drawModifier & (1UL << 32)) != 0
? 2u
: ((modifierBits >> 3) & 0x20u) | 2u;
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var drawCommand) ||
!TryWriteUInt32(ctx, drawCommand, Pm4(5, ItDrawIndirect, 0)) ||
!TryWriteUInt32(ctx, drawCommand + 4, dataOffset) ||
!TryWriteUInt32(ctx, drawCommand + 8, firstVertexRegister) ||
!TryWriteUInt32(ctx, drawCommand + 12, firstInstanceRegister) ||
!TryWriteUInt32(ctx, drawCommand + 16, drawInitiator))
{
var rejects = Interlocked.Increment(ref _indirectDrawEmitRejectCount);
if (rejects <= 8 || rejects % 250 == 0)
{
Console.Error.WriteLine(
$"[LOADER][WARN] agc.emit_indirect_reject#{rejects} " +
$"buf=0x{commandBufferAddress:X16} reason=alloc_or_write");
}
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_draw_indirect buf=0x{commandBufferAddress:X16} " +
$"draw=0x{drawCommand:X16} offset=0x{dataOffset:X} modifier=0x{drawModifier:X}");
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)
{
return ReturnPointer(ctx, 0);
}
NoteDrawModifier("sceAgcDcbDrawIndexAuto", modifier);
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 7, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(7, ItNop, RDrawIndexAuto)) ||
!TryWriteUInt32(ctx, commandAddress + 4, indexCount) ||
!TryWriteUInt32(ctx, commandAddress + 8, 0) ||
!TryWriteUInt32(ctx, commandAddress + 12, 0) ||
!TryWriteUInt32(ctx, commandAddress + 16, 0) ||
!TryWriteUInt32(ctx, commandAddress + 20, 0) ||
!TryWriteUInt32(ctx, 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 = "t1vNu082-jM",
ExportName = "sceAgcDcbDrawIndexIndirect",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndexIndirect(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var dataOffset = (uint)ctx[CpuRegister.Rsi];
var modifier = ctx[CpuRegister.Rdx];
var modifierBits = (uint)modifier;
var shaderStage = modifierBits >> 29;
var scalarRegisterBase = shaderStage is 3 or 5 ? 0x10Cu : 0x8Cu;
ulong registerLocations = (modifierBits & 1u) != 0
? scalarRegisterBase + ((modifierBits >> 9) & 0x1Fu) : 0x280u;
var firstInstanceRegister = (modifierBits & 4u) != 0
? scalarRegisterBase + ((modifierBits >> 19) & 0x1Fu) : 0x280u;
registerLocations |= (ulong)firstInstanceRegister << 32;
if ((modifierBits & 2u) != 0)
{
registerLocations |= ((ulong)(scalarRegisterBase + ((modifierBits >> 14) & 0x1Fu)) << 16) | (1UL << 59);
}
// The API modifier selects register locations; it is not a packet initiator.
var drawInitiator = (modifier & (1UL << 32)) != 0
? 2u : ((modifierBits >> 3) & 0x20u) | 2u;
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(5, ItDrawIndexIndirect, 0)) ||
!TryWriteUInt32(ctx, commandAddress + 4, dataOffset) ||
!ctx.TryWriteUInt64(commandAddress + 8, registerLocations) ||
!TryWriteUInt32(ctx, commandAddress + 16, drawInitiator))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_draw_index_indirect buf=0x{commandBufferAddress:X16} " +
$"cmd=0x{commandAddress:X16} offset=0x{dataOffset:X8} modifier=0x{modifier:X8}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "ypVBz4uPKcQ",
ExportName = "sceAgcDcbDrawIndexIndirectMulti",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndexIndirectMulti(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var dataOffset = (uint)ctx[CpuRegister.Rsi];
var countFromMemory = (uint)ctx[CpuRegister.Rdx];
var drawCount = (uint)ctx[CpuRegister.Rcx];
var countAddress = ctx[CpuRegister.R8];
var stride = (uint)ctx[CpuRegister.R9];
if (countFromMemory > 1 ||
!TryReadUInt64(ctx, ctx[CpuRegister.Rsp] + sizeof(ulong), out var modifier))
return ReturnPointer(ctx, 0);
var modifierBits = (uint)modifier;
var shaderStage = modifierBits >> 29;
var scalarRegisterBase = shaderStage is 3 or 5 ? 0x10Cu : 0x8Cu;
ulong registerLocations = (modifierBits & 1u) != 0
? scalarRegisterBase + ((modifierBits >> 9) & 0x1Fu) : 0x280u;
var firstInstanceRegister = (modifierBits & 4u) != 0
? scalarRegisterBase + ((modifierBits >> 19) & 0x1Fu) : 0x280u;
registerLocations |= (ulong)firstInstanceRegister << 32;
if ((modifierBits & 2u) != 0)
registerLocations |= ((ulong)(scalarRegisterBase + ((modifierBits >> 14) & 0x1Fu)) << 16) | (1UL << 59);
var drawInitiator = (modifier & (1UL << 32)) != 0
? 2u : ((modifierBits >> 3) & 0x20u) | 2u;
if (commandBufferAddress == 0 ||
!TryAllocateCommandDwords(ctx, commandBufferAddress, 10, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(10, ItDrawIndexIndirectMulti, 0)) ||
!TryWriteUInt32(ctx, commandAddress + 4, dataOffset) ||
!ctx.TryWriteUInt64(commandAddress + 8, registerLocations) ||
!TryWriteUInt32(ctx, commandAddress + 16, countFromMemory << 30) ||
!TryWriteUInt32(ctx, commandAddress + 20, drawCount) ||
!ctx.TryWriteUInt64(commandAddress + 24, countAddress & ~3UL) ||
!TryWriteUInt32(ctx, commandAddress + 32, stride) ||
!TryWriteUInt32(ctx, commandAddress + 36, drawInitiator))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_draw_index_indirect_multi buf=0x{commandBufferAddress:X16} " +
$"cmd=0x{commandAddress:X16} offset=0x{dataOffset:X8} draws={drawCount} " +
$"stride={stride} modifier=0x{modifier:X8}");
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "mStuvI0zOtc",
ExportName = "sceAgcDcbDrawIndexIndirectGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndexIndirectGetSize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 5u * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
[SysAbiExport(
Nid = "r98I08t+LOg",
ExportName = "sceAgcDcbDrawIndexIndirectMultiGetSize",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndexIndirectMultiGetSize(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 10u * sizeof(uint);
return (int)ctx[CpuRegister.Rax];
}
[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) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(3, ItIndexBase, 0)) ||
!TryWriteUInt32(ctx, commandAddress + 4, (uint)(indexBufferAddress & 0xFFFF_FFFFUL)) ||
!TryWriteUInt32(ctx, commandAddress + 8, (uint)(indexBufferAddress >> 32)) ||
!TryWriteUInt32(ctx, commandAddress + 12, Pm4(2, ItIndexBufferSize, 0)) ||
!TryWriteUInt32(ctx, 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 drawModifier = ctx[CpuRegister.Rcx];
if (commandBufferAddress == 0)
{
return ReturnPointer(ctx, 0);
}
// The API modifier is not a packet initiator. Bit 32 disables its bit-8 selection.
var drawInitiator = (drawModifier & (1UL << 32)) != 0
? 0u
: ((uint)drawModifier >> 3) & 0x20u;
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var commandAddress) ||
!TryWriteUInt32(ctx, commandAddress, Pm4(5, ItDrawIndexOffset2, 0)) ||
!TryWriteUInt32(ctx, commandAddress + 4, Math.Max(indexCount, 1u)) ||
!TryWriteUInt32(ctx, commandAddress + 8, indexOffset) ||
!TryWriteUInt32(ctx, commandAddress + 12, indexCount) ||
!TryWriteUInt32(ctx, commandAddress + 16, drawInitiator))
{
return ReturnPointer(ctx, 0);
}
TraceAgc($"agc.dcb_draw_index_offset buf=0x{commandBufferAddress:X16} cmd=0x{commandAddress:X16} offset={indexOffset} count={indexCount} flags=0x{drawModifier:X8}");
return ReturnPointer(ctx, commandAddress);
}
// Synthetic label for an uncatalogued NID (the Unknown* convention); the NID is authoritative.
#pragma warning disable SHEM006
[SysAbiExport(
Nid = "-KRzWekV120",
ExportName = "sceAgcDriverUnknown_KRzWekV120",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverUnknownKRzWekV120(CpuContext ctx)
{
return DcbSetIndexSizePacket(
ctx,
ctx[CpuRegister.Rdi],
(uint)ctx[CpuRegister.Rsi],
(uint)ctx[CpuRegister.Rdx],
(uint)ctx[CpuRegister.Rcx]);
}
#pragma warning restore SHEM006
private static long _indirectDrawEmitRejectCount;
private static readonly ConcurrentDictionary<(string, ulong), byte> _reportedDrawModifiers = new();
// The modifier names the shader stage that receives the draw and the user registers the
// command processor fills for indirect draws. A direct draw emits the same packet for every
// modifier; rejecting the unfamiliar ones dropped whole passes, such as Unreal's volume
// rasterization through the geometry stage, without a trace.
private static void NoteDrawModifier(string export, ulong modifier)
{
if (modifier != 0x4000_0000 && _reportedDrawModifiers.TryAdd((export, modifier), 0))
{
Console.Error.WriteLine(
$"[LOADER][INFO] agc.draw_modifier {export} modifier=0x{modifier:X} stage={(uint)modifier >> 29} flags=0x{(uint)modifier & 7u:X}");
}
}
}