166 lines
7.4 KiB
C#
166 lines
7.4 KiB
C#
// 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 Xunit;
|
|
|
|
namespace SharpEmu.Libs.Tests.Agc;
|
|
|
|
// A retained snapshot is a candidate: the draw uses it only when the state it was captured
|
|
// under is the state the interpreter reached, which the prepass cannot know in advance.
|
|
[Collection(AgcCommandBufferChainCollection.Name)]
|
|
public sealed class AgcGeometrySnapshotValidationTests
|
|
{
|
|
private const ulong BaseAddress = 0x1_0000_0000;
|
|
private const int MemorySize = 0x1_0000;
|
|
private const ulong CommandAddress = BaseAddress + 0x1000;
|
|
private const ulong PredicateAddress = BaseAddress + 0x8000;
|
|
private const ulong IndexAddress = BaseAddress + 0x9000;
|
|
private const ulong PatchedIndexAddress = BaseAddress + 0xA000;
|
|
|
|
private const uint ItConditionalExecute = 0x22;
|
|
private const uint ItDrawIndex2 = 0x27;
|
|
private const uint ItIndexType = 0x2A;
|
|
private const uint ItWriteData = 0x37;
|
|
private const uint ItSetShaderRegister = 0x76;
|
|
private const uint UserDataRegister = 0x8C;
|
|
private const uint UserDataValue = 0x1234;
|
|
private const uint IndexCount = 6;
|
|
|
|
private static uint[] SetShaderRegister(uint offset, uint value) => [Pm4Header(3, ItSetShaderRegister), offset, value];
|
|
|
|
private static uint[] IndexType(uint type) => [Pm4Header(2, ItIndexType), type];
|
|
|
|
private static uint[] DrawIndex2(ulong indexAddress) =>
|
|
[Pm4Header(6, ItDrawIndex2), IndexCount, unchecked((uint)indexAddress), (uint)(indexAddress >> 32), IndexCount, 0];
|
|
|
|
private static uint[] ConditionalExecute(ulong predicateAddress, uint skippedDwords) =>
|
|
[Pm4Header(5, ItConditionalExecute), unchecked((uint)predicateAddress), (uint)(predicateAddress >> 32), 0, skippedDwords];
|
|
|
|
private static uint[] WriteData(ulong destination, uint value) =>
|
|
[Pm4Header(5, ItWriteData), 2u << 8, unchecked((uint)destination), (uint)(destination >> 32), value];
|
|
|
|
private static uint Pm4Header(uint dwords, uint opcode) =>
|
|
0xC000_0000u | ((dwords - 2) << 16) | (opcode << 8);
|
|
|
|
private static (FakeCpuMemory Memory, uint Dwords, ulong DrawPacketAddress) WriteStream(params uint[][] packets)
|
|
{
|
|
var memory = new FakeCpuMemory(BaseAddress, MemorySize);
|
|
var words = packets.SelectMany(static packet => packet).ToArray();
|
|
var drawOffset = 0u;
|
|
foreach (var packet in packets)
|
|
{
|
|
if (packet[0] == Pm4Header(6, ItDrawIndex2))
|
|
{
|
|
break;
|
|
}
|
|
|
|
drawOffset += (uint)packet.Length;
|
|
}
|
|
|
|
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
|
for (var index = 0; index < words.Length; index++)
|
|
{
|
|
BinaryPrimitives.WriteUInt32LittleEndian(bytes, words[index]);
|
|
Assert.True(memory.TryWrite(CommandAddress + ((ulong)index * sizeof(uint)), bytes));
|
|
}
|
|
|
|
return (memory, (uint)words.Length, CommandAddress + ((ulong)drawOffset * sizeof(uint)));
|
|
}
|
|
|
|
private static object CapturedAsThePrepassWould(ulong drawPacketAddress, ulong indexAddress) =>
|
|
AgcExports.CreateGeometrySnapshotsForTests(
|
|
drawPacketAddress,
|
|
indexSize: 0,
|
|
indexAddress,
|
|
IndexCount,
|
|
instanceCount: 1,
|
|
[(UserDataRegister, UserDataValue)]);
|
|
|
|
[Fact]
|
|
public void MatchingState_UsesTheSnapshot()
|
|
{
|
|
var (memory, dwords, drawPacket) = WriteStream(
|
|
SetShaderRegister(UserDataRegister, UserDataValue),
|
|
IndexType(0),
|
|
DrawIndex2(IndexAddress));
|
|
|
|
AgcExports.GetHeadlessCommandStreamForTests(memory).Submit(0, CommandAddress, dwords, 1, CapturedAsThePrepassWould(drawPacket, IndexAddress));
|
|
|
|
Assert.Equal(AgcExports.GeometrySnapshotDecision.Accepted, AgcExports.LastGeometrySnapshotDecisionForTests);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void CaptureRegisters_MatchInterpreterAcrossFrameBoundary(bool endFrame)
|
|
{
|
|
var (memory, dwords, _) = WriteStream(
|
|
SetShaderRegister(0x48, 1), SetShaderRegister(0x49, 2),
|
|
SetShaderRegister(0x4A, 3), SetShaderRegister(0x4B, 4));
|
|
var context = new CpuContext(memory, Generation.Gen5);
|
|
const ulong submissionAddress = BaseAddress + 0x40;
|
|
context[CpuRegister.Rdi] = submissionAddress;
|
|
Assert.True(context.TryWriteUInt64(submissionAddress, CommandAddress));
|
|
Span<byte> lengthBytes = stackalloc byte[4];
|
|
BinaryPrimitives.WriteUInt32LittleEndian(lengthBytes, dwords);
|
|
Assert.True(memory.TryWrite(submissionAddress + 8, lengthBytes));
|
|
Assert.Equal(0, AgcExports.DriverSubmitDcb(context));
|
|
if (endFrame)
|
|
{
|
|
Assert.Equal(0, AgcExports.SuspendPoint(context));
|
|
}
|
|
|
|
var nextPacket = SetShaderRegister(0xC8, 5);
|
|
for (var index = 0; index < nextPacket.Length; index++)
|
|
{
|
|
BinaryPrimitives.WriteUInt32LittleEndian(lengthBytes, nextPacket[index]);
|
|
Assert.True(memory.TryWrite(CommandAddress + (ulong)(index * 4), lengthBytes));
|
|
}
|
|
|
|
BinaryPrimitives.WriteUInt32LittleEndian(lengthBytes, (uint)nextPacket.Length);
|
|
Assert.True(memory.TryWrite(submissionAddress + 8, lengthBytes));
|
|
Assert.Equal(0, AgcExports.DriverSubmitDcb(context));
|
|
var interpreterRegisters = AgcExports.GetHeadlessCommandStreamForTests(memory).Queue.GetInterpreter(0).Registers.Shader;
|
|
var capturedRegisters = AgcExports.GetGeometryCaptureRegistersForTests(memory);
|
|
Assert.Equal(endFrame ? 1 : 5, interpreterRegisters.Count);
|
|
Assert.Equal(interpreterRegisters.OrderBy(static entry => entry.Key), capturedRegisters.OrderBy(static entry => entry.Key));
|
|
}
|
|
|
|
// The prepass applied the register write; the interpreter skipped it through the predicate.
|
|
[Fact]
|
|
public void RegisterWriteSkippedByConditionalExecute_RejectsTheSnapshot()
|
|
{
|
|
var (memory, dwords, drawPacket) = WriteStream(
|
|
ConditionalExecute(PredicateAddress, 3),
|
|
SetShaderRegister(UserDataRegister, UserDataValue),
|
|
IndexType(0),
|
|
DrawIndex2(IndexAddress));
|
|
Span<byte> zero = stackalloc byte[sizeof(uint)];
|
|
Assert.True(memory.TryWrite(PredicateAddress, zero));
|
|
|
|
AgcExports.GetHeadlessCommandStreamForTests(memory).Submit(0, CommandAddress, dwords, 1, CapturedAsThePrepassWould(drawPacket, IndexAddress));
|
|
|
|
Assert.Equal(AgcExports.GeometrySnapshotDecision.Rejected, AgcExports.LastGeometrySnapshotDecisionForTests);
|
|
}
|
|
|
|
// The stream patches the draw's index address before the draw executes.
|
|
[Fact]
|
|
public void IndexAddressPatchedAfterCapture_RejectsTheSnapshot()
|
|
{
|
|
var (memory, dwords, drawPacket) = WriteStream(
|
|
SetShaderRegister(UserDataRegister, UserDataValue),
|
|
IndexType(0),
|
|
WriteData(CommandAddress + ((3 + 2 + 5 + 2) * sizeof(uint)), unchecked((uint)PatchedIndexAddress)),
|
|
DrawIndex2(IndexAddress));
|
|
|
|
AgcExports.GetHeadlessCommandStreamForTests(memory).Submit(0, CommandAddress, dwords, 1, CapturedAsThePrepassWould(drawPacket, IndexAddress));
|
|
|
|
Assert.Equal(AgcExports.GeometrySnapshotDecision.Rejected, AgcExports.LastGeometrySnapshotDecisionForTests);
|
|
Assert.True(AgcExports.TryGetGraphicsIndexStateForTests(new CpuContext(memory, Generation.Gen5), out var address, out _, out _));
|
|
Assert.Equal(PatchedIndexAddress, address);
|
|
}
|
|
}
|