184 lines
7.5 KiB
C#
184 lines
7.5 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;
|
|
|
|
public sealed class AgcInterpolantMappingTests
|
|
{
|
|
private const ulong BaseAddress = 0x1_0000_0000;
|
|
private const ulong RegistersAddress = BaseAddress + 0x100;
|
|
private const ulong GeometryShaderAddress = BaseAddress + 0x400;
|
|
private const ulong PixelShaderAddress = BaseAddress + 0x500;
|
|
private const ulong GeometrySemanticsAddress = BaseAddress + 0x800;
|
|
private const ulong PixelSemanticsAddress = BaseAddress + 0x900;
|
|
private const uint SpiPsInputCntl0 = 0x191;
|
|
|
|
[Fact]
|
|
public void CreateInterpolantMapping_MatchesOutputsBySemanticAndKeepsIdentitySlots()
|
|
{
|
|
var memory = new FakeCpuMemory(BaseAddress, 0x2000);
|
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
|
|
|
WriteUInt64(memory, GeometryShaderAddress + 0x38, GeometrySemanticsAddress);
|
|
WriteUInt16(memory, GeometryShaderAddress + 0x56, 3);
|
|
WriteUInt32(memory, GeometrySemanticsAddress + 0, Semantic(7, hardwareMapping: 5));
|
|
WriteUInt32(memory, GeometrySemanticsAddress + 4, Semantic(9, hardwareMapping: 12));
|
|
WriteUInt32(memory, GeometrySemanticsAddress + 8, Semantic(10, hardwareMapping: 4, isF16: 1));
|
|
|
|
WriteUInt64(memory, PixelShaderAddress + 0x30, PixelSemanticsAddress);
|
|
WriteUInt32(memory, PixelShaderAddress + 0x50, 5);
|
|
WriteUInt32(memory, PixelSemanticsAddress + 0, Semantic(7));
|
|
WriteUInt32(memory, PixelSemanticsAddress + 4, Semantic(8, defaultValue: 2));
|
|
WriteUInt32(memory, PixelSemanticsAddress + 8, Semantic(9, flatShaded: true, custom: true, defaultValue: 1));
|
|
WriteUInt32(memory, PixelSemanticsAddress + 12, Semantic(10, isF16: 1, defaultValue: 3, defaultValueHigh: 2));
|
|
WriteUInt32(memory, PixelSemanticsAddress + 16, Semantic(11, isF16: 2, defaultValueHigh: 3));
|
|
|
|
ctx[CpuRegister.Rdi] = RegistersAddress;
|
|
ctx[CpuRegister.Rsi] = GeometryShaderAddress;
|
|
ctx[CpuRegister.Rdx] = PixelShaderAddress;
|
|
|
|
Assert.Equal(
|
|
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
|
AgcExports.CreateInterpolantMapping(ctx));
|
|
|
|
// Values captured from the per-word implementation before the reads and the register
|
|
// block write were batched.
|
|
uint[] expectedValues = [0x0000_0005, 0x0000_0220, 0x0000_052C, 0x0158_0304, 0x0278_0020];
|
|
for (uint index = 0; index < 32; index++)
|
|
{
|
|
Assert.Equal(SpiPsInputCntl0 + index, ReadUInt32(memory, RegistersAddress + (index * 8)));
|
|
var expected = index < expectedValues.Length ? expectedValues[index] : index;
|
|
Assert.Equal(expected, ReadUInt32(memory, RegistersAddress + (index * 8) + 4));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateInterpolantMapping2_WritesIdentityMappingWithoutPixelInputs()
|
|
{
|
|
var memory = new FakeCpuMemory(BaseAddress, 0x2000);
|
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
|
ctx[CpuRegister.Rdi] = RegistersAddress;
|
|
|
|
Assert.Equal(
|
|
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
|
AgcExports.CreateInterpolantMapping2(ctx));
|
|
|
|
for (uint index = 0; index < 32; index++)
|
|
{
|
|
Assert.Equal(SpiPsInputCntl0 + index, ReadUInt32(memory, RegistersAddress + (index * 8)));
|
|
Assert.Equal(index, ReadUInt32(memory, RegistersAddress + (index * 8) + 4));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateInterpolantMapping2_PacksNativeSemanticModes()
|
|
{
|
|
var memory = new FakeCpuMemory(BaseAddress, 0x2000);
|
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
|
|
|
WriteUInt64(memory, GeometryShaderAddress + 0x38, GeometrySemanticsAddress);
|
|
WriteUInt16(memory, GeometryShaderAddress + 0x56, 3);
|
|
WriteUInt32(memory, GeometrySemanticsAddress + 0, Semantic(7, hardwareMapping: 5));
|
|
WriteUInt32(memory, GeometrySemanticsAddress + 4, Semantic(9, hardwareMapping: 12));
|
|
WriteUInt32(memory, GeometrySemanticsAddress + 8, Semantic(10, hardwareMapping: 4, isF16: 1));
|
|
|
|
WriteUInt64(memory, PixelShaderAddress + 0x30, PixelSemanticsAddress);
|
|
WriteUInt32(memory, PixelShaderAddress + 0x50, 6);
|
|
WriteUInt32(memory, PixelSemanticsAddress + 0, Semantic(7));
|
|
WriteUInt32(memory, PixelSemanticsAddress + 4, Semantic(8, defaultValue: 2));
|
|
WriteUInt32(
|
|
memory,
|
|
PixelSemanticsAddress + 8,
|
|
Semantic(9, flatShaded: true, custom: true, defaultValue: 1));
|
|
WriteUInt32(
|
|
memory,
|
|
PixelSemanticsAddress + 12,
|
|
Semantic(10, isF16: 1, defaultValue: 3, defaultValueHigh: 2));
|
|
WriteUInt32(
|
|
memory,
|
|
PixelSemanticsAddress + 16,
|
|
Semantic(10, isF16: 2, defaultValueHigh: 3));
|
|
WriteUInt32(
|
|
memory,
|
|
PixelSemanticsAddress + 20,
|
|
Semantic(11, isF16: 1, defaultValue: 2, defaultValueHigh: 1));
|
|
|
|
ctx[CpuRegister.Rdi] = RegistersAddress;
|
|
ctx[CpuRegister.Rsi] = GeometryShaderAddress;
|
|
ctx[CpuRegister.Rdx] = PixelShaderAddress;
|
|
|
|
Assert.Equal(
|
|
(int)OrbisGen2Result.ORBIS_GEN2_OK,
|
|
AgcExports.CreateInterpolantMapping2(ctx));
|
|
|
|
uint[] expectedValues =
|
|
[
|
|
0x0000_0005,
|
|
0x0000_0220,
|
|
0x0000_052C,
|
|
0x0158_0304,
|
|
0x0268_0324,
|
|
0x0138_0220,
|
|
];
|
|
for (uint index = 0; index < (uint)expectedValues.Length; index++)
|
|
{
|
|
Assert.Equal(SpiPsInputCntl0 + index, ReadUInt32(memory, RegistersAddress + (index * 8)));
|
|
Assert.Equal(expectedValues[index], ReadUInt32(memory, RegistersAddress + (index * 8) + 4));
|
|
}
|
|
|
|
Assert.Equal(SpiPsInputCntl0 + 6, ReadUInt32(memory, RegistersAddress + 48));
|
|
Assert.Equal(6u, ReadUInt32(memory, RegistersAddress + 52));
|
|
}
|
|
|
|
private static uint Semantic(
|
|
uint semantic,
|
|
uint hardwareMapping = 0,
|
|
uint isF16 = 0,
|
|
bool flatShaded = false,
|
|
bool custom = false,
|
|
uint defaultValue = 0,
|
|
uint defaultValueHigh = 0)
|
|
{
|
|
return semantic |
|
|
(hardwareMapping << 8) |
|
|
(isF16 << 20) |
|
|
(flatShaded ? 1u << 22 : 0) |
|
|
(custom ? 1u << 24 : 0) |
|
|
(defaultValue << 28) |
|
|
(defaultValueHigh << 30);
|
|
}
|
|
|
|
private static void WriteUInt16(FakeCpuMemory memory, ulong address, ushort value)
|
|
{
|
|
Span<byte> bytes = stackalloc byte[sizeof(ushort)];
|
|
BinaryPrimitives.WriteUInt16LittleEndian(bytes, value);
|
|
Assert.True(memory.TryWrite(address, bytes));
|
|
}
|
|
|
|
private static void WriteUInt32(FakeCpuMemory memory, ulong address, uint value)
|
|
{
|
|
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
|
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
|
|
Assert.True(memory.TryWrite(address, bytes));
|
|
}
|
|
|
|
private static void WriteUInt64(FakeCpuMemory memory, ulong address, ulong value)
|
|
{
|
|
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
|
|
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
|
|
Assert.True(memory.TryWrite(address, bytes));
|
|
}
|
|
|
|
private static uint ReadUInt32(FakeCpuMemory memory, ulong address)
|
|
{
|
|
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
|
Assert.True(memory.TryRead(address, bytes));
|
|
return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
|
|
}
|
|
}
|