[Agc] Emit direct draws for every draw modifier
sceAgcDcbDrawIndex and sceAgcDcbDrawIndexAuto returned without writing a packet unless the modifier was exactly 0x40000000. The modifier names the shader stage and user registers of the bound pipeline, so any other pipeline lost its draws without a trace. Unreal bakes its color-grading LUT through a geometry-stage pipeline, which left the LUT unwritten and tinted every 3D frame of Poppy Playtime blue. Both draws now emit their packets for any modifier. Modifiers other than the vertex-stage default are logged once each. (cherry picked from commit 8d46a6068b7d1a999662eeac1ee17f9e6d87c2eb)
This commit is contained in:
parent
d5de44b6fb
commit
f8c85b6168
|
|
@ -1,6 +1,7 @@
|
|||
// 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;
|
||||
|
||||
|
|
@ -133,13 +134,15 @@ public static partial class AgcExports
|
|||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var indexCount = (uint)ctx[CpuRegister.Rsi];
|
||||
var indexAddress = ctx[CpuRegister.Rdx];
|
||||
var modifier = (uint)ctx[CpuRegister.Rcx];
|
||||
var modifier = ctx[CpuRegister.Rcx];
|
||||
|
||||
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
|
||||
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) ||
|
||||
|
|
@ -239,11 +242,13 @@ public static partial class AgcExports
|
|||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var indexCount = (uint)ctx[CpuRegister.Rsi];
|
||||
var modifier = ctx[CpuRegister.Rdx];
|
||||
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
|
||||
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) ||
|
||||
|
|
@ -457,4 +462,19 @@ public static partial class AgcExports
|
|||
#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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Agc;
|
||||
using SharpEmu.Libs.Gpu.GpuCommands;
|
||||
using SharpEmu.Libs.Tests.Gpu.GpuCommands;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Agc;
|
||||
|
||||
// Direct draws carry a modifier naming the shader stage and the user registers of the pipeline.
|
||||
// Every modifier must still produce the draw: Unreal's volume passes use a geometry-stage modifier.
|
||||
public sealed class AgcDirectDrawModifierTests
|
||||
{
|
||||
private const ulong BaseAddress = 0x2_1000_0000;
|
||||
private const ulong CommandBufferAddress = BaseAddress + 0x80;
|
||||
private const ulong CommandAddress = BaseAddress + 0x200;
|
||||
|
||||
public static TheoryData<ulong> Modifiers => new() { 0x4000_0000UL, 0x6000_0000UL, 0xA000_0000UL, 0x4008_0205UL, 0x1_6000_0000UL };
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Modifiers))]
|
||||
public void DrawIndexAutoEmitsTheDrawForEveryModifier(ulong modifier)
|
||||
{
|
||||
var memory = CreateCommandBuffer();
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
context[CpuRegister.Rdi] = CommandBufferAddress;
|
||||
context[CpuRegister.Rsi] = 6;
|
||||
context[CpuRegister.Rdx] = modifier;
|
||||
|
||||
AgcExports.DcbDrawIndexAuto(context);
|
||||
|
||||
Assert.Equal(CommandAddress, context[CpuRegister.Rax]);
|
||||
var runner = new StreamRunner();
|
||||
Assert.Equal(SubmissionProgress.Complete, runner.Run(ReadDwords(memory, CommandAddress, 7)));
|
||||
var draw = Assert.Single(runner.Host.AutoDraws);
|
||||
Assert.Equal(6u, draw.VertexCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Modifiers))]
|
||||
public void DrawIndexEmitsTheDrawForEveryModifier(ulong modifier)
|
||||
{
|
||||
var memory = CreateCommandBuffer();
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
context[CpuRegister.Rdi] = CommandBufferAddress;
|
||||
context[CpuRegister.Rsi] = 36;
|
||||
context[CpuRegister.Rdx] = StreamRunner.DataAddress;
|
||||
context[CpuRegister.Rcx] = modifier;
|
||||
|
||||
AgcExports.DcbDrawIndex(context);
|
||||
|
||||
Assert.NotEqual(0UL, context[CpuRegister.Rax]);
|
||||
var runner = new StreamRunner();
|
||||
Assert.Equal(SubmissionProgress.Complete, runner.Run(ReadDwords(memory, CommandAddress, 11)));
|
||||
var draw = Assert.Single(runner.Host.IndexedDraws);
|
||||
Assert.Equal(36u, draw.IndexCount);
|
||||
Assert.Equal(StreamRunner.DataAddress, draw.IndexAddress);
|
||||
}
|
||||
|
||||
private static FakeCpuMemory CreateCommandBuffer()
|
||||
{
|
||||
var memory = new FakeCpuMemory(BaseAddress, 0x1000);
|
||||
WriteUInt64(memory, CommandBufferAddress + 0x10, CommandAddress);
|
||||
WriteUInt64(memory, CommandBufferAddress + 0x18, CommandAddress + 0x100);
|
||||
return memory;
|
||||
}
|
||||
|
||||
private static uint[] ReadDwords(FakeCpuMemory memory, ulong address, int count)
|
||||
{
|
||||
var bytes = new byte[count * sizeof(uint)];
|
||||
Assert.True(memory.TryRead(address, bytes));
|
||||
var dwords = new uint[count];
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
dwords[index] = BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(index * sizeof(uint)));
|
||||
}
|
||||
|
||||
return dwords;
|
||||
}
|
||||
|
||||
private static void WriteUInt64(FakeCpuMemory memory, ulong address, ulong value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||
Assert.True(memory.TryWrite(address, buffer));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue