sharpemu/tests/SharpEmu.Libs.Tests/Agc/AgcDrawIndex2Tests.cs

264 lines
12 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 SharpEmu.Libs.Gpu.GpuCommands;
using SharpEmu.Libs.Tests.Gpu.GpuCommands;
using Xunit;
namespace SharpEmu.Libs.Tests.Agc;
public sealed class AgcDrawIndex2Tests
{
private const ulong BaseAddress = 0x2_1000_0000;
private const ulong SubmitPacketAddress = BaseAddress + 0x40;
private const ulong CommandAddress = BaseAddress + 0x200;
private const uint ItIndexBase = 0x26;
private const uint ItDrawIndex2 = 0x27;
[Theory]
[InlineData(0u, 0u, 0UL, 0x280u, 0x280u, 2u, 3)]
[InlineData(1u, 0u, 0UL, 0x280u, 0x280u, 2u, 0)]
[InlineData(1u, 1u, 0x60180707UL, 0x010C010Fu, 0x0800010Fu, 0x22u, 1)]
[InlineData(1u, 5u, 0x160180707UL, 0x010C010Fu, 0x0800010Fu, 2u, 3)]
public void IndexedMultiDrawPreservesCountAddressStrideAndModifier(
uint countFromMemory, uint memoryCount, ulong modifier, uint vertexLocations,
uint instanceLocation, uint initiator, int expectedDraws)
{
var memory = new FakeCpuMemory(BaseAddress, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
var commandBufferAddress = BaseAddress + 0x80;
var stackAddress = BaseAddress + 0x500;
WriteUInt64(memory, commandBufferAddress + 0x10, CommandAddress);
WriteUInt64(memory, commandBufferAddress + 0x18, CommandAddress + 40);
WriteUInt64(memory, stackAddress + 8, modifier);
context[CpuRegister.Rdi] = commandBufferAddress;
context[CpuRegister.Rsi] = 16;
context[CpuRegister.Rdx] = countFromMemory;
context[CpuRegister.Rcx] = 3;
context[CpuRegister.R8] = StreamRunner.LabelAddress;
context[CpuRegister.R9] = 32;
context[CpuRegister.Rsp] = stackAddress;
AgcExports.DcbDrawIndexIndirectMulti(context);
Assert.Equal(CommandAddress, context[CpuRegister.Rax]);
Span<byte> bytes = stackalloc byte[40];
Assert.True(memory.TryRead(CommandAddress, bytes));
var packet = new uint[10];
for (var index = 0; index < packet.Length; index++)
packet[index] = BinaryPrimitives.ReadUInt32LittleEndian(bytes[(index * 4)..]);
Assert.Equal(new uint[] { PacketHeader.Make(10, PacketOpcode.DrawIndexIndirectMulti),
16, vertexLocations, instanceLocation, countFromMemory << 30, 3,
StreamRunner.Low(StreamRunner.LabelAddress), StreamRunner.High(StreamRunner.LabelAddress), 32, initiator }, packet);
AgcExports.DcbDrawIndexIndirectMultiGetSize(context);
Assert.Equal(40UL, context[CpuRegister.Rax]);
Span<byte> cursor = stackalloc byte[8];
Assert.True(memory.TryRead(commandBufferAddress + 0x10, cursor));
Assert.Equal(CommandAddress + 40, BinaryPrimitives.ReadUInt64LittleEndian(cursor));
var runner = new StreamRunner();
runner.Host.WriteDword(StreamRunner.LabelAddress, memoryCount);
for (var draw = 0u; draw < 3; draw++)
runner.Host.WriteWords(StreamRunner.DataAddress + 16 + draw * 32, new uint[] { 7 + draw, 2, 0, 4, 5 });
Assert.Equal(SubmissionProgress.Complete, runner.Run(
StreamRunner.Packet(PacketOpcode.SetBase, 1, StreamRunner.Low(StreamRunner.DataAddress), StreamRunner.High(StreamRunner.DataAddress)),
StreamRunner.Packet(PacketOpcode.IndexBase, StreamRunner.Low(StreamRunner.DataAddress + 0x200), StreamRunner.High(StreamRunner.DataAddress + 0x200)), packet));
Assert.Equal(expectedDraws, runner.Host.IndexedDraws.Count);
for (var draw = 0; draw < expectedDraws; draw++)
{
Assert.Equal((uint)(7 + draw), runner.Host.IndexedDraws[draw].IndexCount);
Assert.Equal(2u, runner.Host.IndexedDraws[draw].InstanceCount);
}
}
[Theory]
[InlineData(0UL, 0x280u, 0x280u, 2u)]
[InlineData(0x40000100UL, 0x280u, 0x280u, 0x22u)]
[InlineData(0x140000100UL, 0x280u, 0x280u, 2u)]
[InlineData(0x60180705UL, 0x10Fu, 0x10Fu, 0x22u)]
[InlineData(0xA0180605UL, 0x10Fu, 0x10Fu, 2u)]
[InlineData(0x40180605UL, 0x8Fu, 0x8Fu, 2u)]
public void DrawIndirectEncodesTheModifierBeforeInterpretation(
ulong drawModifier, uint firstVertexRegister, uint firstInstanceRegister, uint drawInitiator)
{
var memory = new FakeCpuMemory(BaseAddress, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
var commandBufferAddress = BaseAddress + 0x80;
WriteUInt64(memory, commandBufferAddress + 0x10, CommandAddress);
WriteUInt64(memory, commandBufferAddress + 0x18, CommandAddress + 0x100);
context[CpuRegister.Rdi] = commandBufferAddress;
context[CpuRegister.Rsi] = 16;
context[CpuRegister.Rdx] = drawModifier;
AgcExports.DcbDrawIndirect(context);
Assert.Equal(CommandAddress, context[CpuRegister.Rax]);
Span<byte> packetBytes = stackalloc byte[20];
Assert.True(memory.TryRead(CommandAddress, packetBytes));
var packet = new uint[5];
for (var index = 0; index < packet.Length; index++)
{
packet[index] = BinaryPrimitives.ReadUInt32LittleEndian(packetBytes[(index * sizeof(uint))..]);
}
Assert.Equal(PacketHeader.Make(5, PacketOpcode.DrawIndirect), packet[0]);
Assert.Equal(16u, packet[1]);
Assert.Equal(firstVertexRegister, packet[2]);
Assert.Equal(firstInstanceRegister, packet[3]);
Assert.Equal(drawInitiator, packet[4]);
var runner = new StreamRunner();
runner.Host.WriteWords(StreamRunner.DataAddress + 16, new uint[] { 9, 3, 1, 2 });
Assert.Equal(SubmissionProgress.Complete, runner.Run(
StreamRunner.Packet(PacketOpcode.SetBase, 1,
StreamRunner.Low(StreamRunner.DataAddress), StreamRunner.High(StreamRunner.DataAddress)), packet));
var draw = Assert.Single(runner.Host.AutoDraws);
Assert.Equal((9u, 3u, 1u, 2u), (draw.VertexCount, draw.InstanceCount, draw.FirstVertex, draw.FirstInstance));
}
[Theory]
[InlineData(0x40000000UL, 0x280u, 0x280u, 2u)]
[InlineData(0x40000100UL, 0x280u, 0x280u, 0x22u)]
[InlineData(0x140000100UL, 0x280u, 0x280u, 2u)]
[InlineData(0x60180707UL, 0x010C010Fu, 0x0800010Fu, 0x22u)]
public void IndexedIndirectDrawEncodesTheModifierBeforeInterpretation(
ulong modifier, uint vertexLocations, uint instanceLocation, uint initiator)
{
var memory = new FakeCpuMemory(BaseAddress, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
var commandBufferAddress = BaseAddress + 0x80;
WriteUInt64(memory, commandBufferAddress + 0x10, CommandAddress);
WriteUInt64(memory, commandBufferAddress + 0x18, CommandAddress + 20);
context[CpuRegister.Rdi] = commandBufferAddress;
context[CpuRegister.Rsi] = 0x1F4;
context[CpuRegister.Rdx] = modifier;
AgcExports.DcbDrawIndexIndirect(context);
Assert.Equal(CommandAddress, context[CpuRegister.Rax]);
Span<byte> packetBytes = stackalloc byte[20];
Assert.True(memory.TryRead(CommandAddress, packetBytes));
var packet = new uint[5];
for (var index = 0; index < packet.Length; index++)
{
packet[index] = BinaryPrimitives.ReadUInt32LittleEndian(packetBytes[(index * sizeof(uint))..]);
}
Assert.Equal(new uint[] { PacketHeader.Make(5, PacketOpcode.DrawIndexIndirect),
0x1F4, vertexLocations, instanceLocation, initiator }, packet);
var runner = new StreamRunner();
runner.Host.WriteWords(StreamRunner.DataAddress + 0x1F4, new uint[] { 7, 2, 3, 4, 5 });
Assert.Equal(SubmissionProgress.Complete, runner.Run(
StreamRunner.Packet(PacketOpcode.SetBase, 1, StreamRunner.Low(StreamRunner.DataAddress), StreamRunner.High(StreamRunner.DataAddress)),
StreamRunner.Packet(PacketOpcode.IndexBase, StreamRunner.Low(StreamRunner.DataAddress), StreamRunner.High(StreamRunner.DataAddress)), packet));
var draw = Assert.Single(runner.Host.IndexedDraws);
Assert.Equal(7u, draw.IndexCount);
Assert.Equal(2u, draw.InstanceCount);
Assert.Equal(StreamRunner.DataAddress + 6, draw.IndexAddress);
Assert.Equal(4, draw.BaseVertex);
Assert.Equal(5u, draw.FirstInstance);
}
[Theory]
[InlineData(0x40000000UL, 3u, 0u)]
[InlineData(0x40000100UL, 3u, 0x20u)]
[InlineData(0x140000100UL, 3u, 0u)]
[InlineData(0UL, 0u, 0u)]
public void DrawIndexOffsetEncodesTheModifierBeforeInterpretation(ulong drawModifier, uint indexCount, uint expectedInitiator)
{
var memory = new FakeCpuMemory(BaseAddress, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
var commandBufferAddress = BaseAddress + 0x80;
WriteUInt64(memory, commandBufferAddress + 0x10, CommandAddress);
WriteUInt64(memory, commandBufferAddress + 0x18, CommandAddress + 0x100);
context[CpuRegister.Rdi] = commandBufferAddress;
context[CpuRegister.Rsi] = 2;
context[CpuRegister.Rdx] = indexCount;
context[CpuRegister.Rcx] = drawModifier;
AgcExports.DcbDrawIndexOffset(context);
Assert.Equal(CommandAddress, context[CpuRegister.Rax]);
Span<byte> packetBytes = stackalloc byte[20];
Assert.True(memory.TryRead(CommandAddress, packetBytes));
var packet = new uint[5];
for (var index = 0; index < packet.Length; index++)
{
packet[index] = BinaryPrimitives.ReadUInt32LittleEndian(packetBytes[(index * sizeof(uint))..]);
}
Assert.Equal(PacketHeader.Make(5, PacketOpcode.DrawIndexOffset2), packet[0]);
Assert.Equal(Math.Max(indexCount, 1u), packet[1]);
Assert.Equal(2u, packet[2]);
Assert.Equal(indexCount, packet[3]);
Assert.Equal(expectedInitiator, packet[4]);
var runner = new StreamRunner();
Assert.Equal(SubmissionProgress.Complete, runner.Run(
StreamRunner.Packet(PacketOpcode.IndexBase, StreamRunner.Low(StreamRunner.DataAddress), StreamRunner.High(StreamRunner.DataAddress)), packet));
if (indexCount != 0)
{
var draw = Assert.Single(runner.Host.IndexedDraws);
Assert.Equal(indexCount, draw.IndexCount);
Assert.Equal(StreamRunner.DataAddress + 4, draw.IndexAddress);
}
}
[Fact]
public void DrawIndex2UsesItsEmbeddedIndexBuffer()
{
const ulong staleAddress = 0x0000_0006_00AA_0000;
const ulong embeddedAddress = 0x0000_0006_0080_AC50;
const uint maximumIndexCount = 0x200;
const uint drawCount = 6;
var memory = new FakeCpuMemory(BaseAddress, 0x1000);
var ctx = new CpuContext(memory, Generation.Gen5);
WriteDwords(
memory,
CommandAddress,
Pm4Header(3, ItIndexBase),
unchecked((uint)staleAddress),
(uint)(staleAddress >> 32),
Pm4Header(6, ItDrawIndex2),
maximumIndexCount,
unchecked((uint)embeddedAddress),
(uint)(embeddedAddress >> 32),
drawCount,
0);
WriteUInt64(memory, SubmitPacketAddress, CommandAddress);
WriteUInt32(memory, SubmitPacketAddress + 8, 9);
ctx[CpuRegister.Rdi] = SubmitPacketAddress;
AgcExports.DriverSubmitDcb(ctx);
Assert.True(AgcExports.TryGetGraphicsIndexStateForTests(
ctx,
out var address,
out var count,
out var offset));
Assert.Equal(embeddedAddress, address);
Assert.Equal(drawCount, count);
Assert.Equal(0u, offset);
}
private static uint Pm4Header(uint dwords, uint opcode) =>
0xC000_0000u | ((dwords - 2) << 16) | (opcode << 8);
private static void WriteDwords(FakeCpuMemory memory, ulong address, params uint[] values)
{
for (var index = 0; index < values.Length; index++)
{
WriteUInt32(memory, address + ((ulong)index * sizeof(uint)), values[index]);
}
}
private static void WriteUInt32(FakeCpuMemory memory, ulong address, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
Assert.True(memory.TryWrite(address, buffer));
}
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));
}
}