sharpemu/tests/SharpEmu.Libs.Tests/Gpu/Pipelines/BoundedFillDetectorTests.cs

238 lines
7.9 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Gpu.Pipelines;
using SharpEmu.Libs.Tests.Agc;
using SharpEmu.ShaderCompiler;
using Xunit;
namespace SharpEmu.Libs.Tests.Gpu.Pipelines;
public sealed class BoundedFillDetectorTests
{
private const ulong ShaderAddress = 0x1_0000_0000;
private static readonly uint[] ZeroRangeFromConstantBuffer =
[
0xBEFC03FF, 0x24311A8B,
0xBF960000,
0xBFA00001,
0xD7460000, 0x04010C08,
0xF4201A82, 0xFA000004,
0xBF8CC07F,
0x7DA8006A,
0xBF880007,
0x7E020280,
0xF4201A82, 0xFA000000,
0xBF8CC07F,
0x4A00006A,
0xE0102000, 0x80000100,
];
private static readonly uint[] ValueRangeFromPointer =
[
0xBEFC03FF, 0xDCFA31EA,
0xBF960000,
0xBFA00001,
0xD7460000, 0x04010C02,
0xF4040100, 0xFA000000,
0xBF8CC07F,
0xF4001A82, 0xFA000004,
0xBF8CC07F,
0x7DA8006A,
0xBF88000C,
0xF4001A82, 0xFA000000,
0xF4001AC2, 0xFA000008,
0xBF8CC07F,
0x4A00006A,
0x7E02026B,
0xF4080002, 0xFA000010,
0xBF8CC07F,
0xE0702000, 0x80000100,
];
private static readonly uint[] PatternFromUserData =
[
0xBFA00003,
0xD7460002, 0x04010C0A,
0x7DA80408,
0xBF88003F,
0x7E000C09,
0xBF070980,
0x858A807E,
0x7E005700,
0x100000FF, 0x4F800000,
0x7E060F00,
0xD5766A00, 0x02020609,
0x7D8A0280,
0x4C020080,
0x02000101,
0xD56A0001, 0x00020700,
0x4C000303,
0x4A020303,
0x02000101,
0xD56A0000, 0x00020500,
0xD5690001, 0x00020009,
0x4C060302,
0x7D860609,
0x7D8C02F9, 0x06068C02,
0x87EA6A0C,
0x50000080,
0xD5286A00, 0x003200C1,
0xD5010000, 0x002A00C1,
0xD5690000, 0x00020009,
0x4C000102,
0x7D0A0080,
0xBE88246A,
0xBF880015,
0x7D0A0081,
0xBE8A246A,
0xBF88000C,
0x7D0A0082,
0xBEEA246A,
0xBF880003,
0x7E000207,
0xE0102000, 0x80000002,
0x8AFE7E6A,
0xBF880003,
0x7E000206,
0xE0102000, 0x80000002,
0xBEFE046A,
0x8AFE7E0A,
0xBF880003,
0x7E000205,
0xE0102000, 0x80000002,
0xBEFE040A,
0x8AFE7E08,
0xBF880003,
0x7E000204,
0xE0102000, 0x80000002,
];
[Fact]
public void ARepeatingPatternFillFromUserDataIsRecognized()
{
var fill = BoundedFillDetector.Detect(Decode(PatternFromUserData));
Assert.NotNull(fill);
static FillWord User(uint register) => new(FillWordSource.UserData, register, 0, 0);
Assert.Equal(10u, fill.GroupScalarRegister);
Assert.Equal(User(8), fill.Count);
Assert.Null(fill.Start);
Assert.Equal(User(4), fill.Value);
Assert.Equal(User(9), fill.PatternLength);
Assert.Equal([User(4), User(5), User(6), User(7)], fill.Pattern!);
Assert.Equal([User(0), User(1), User(2), User(3)], fill.Destination);
Assert.True(fill.Formatted);
}
private static readonly uint[] DemonsSoulsModuloCopy =
[
0xBEFC03FF, 0xA65CD91E, 0xBF960000, 0xBFA00003, 0xD7460002, 0x04010C0C, 0xF4201A84, 0xFA000000,
0xBF8CC07F, 0x7DA8046A, 0xBF88002A, 0xF4200304, 0xFA000004, 0xBF8CC07F, 0x7E000C0C, 0xBF070C80,
0x8588807E, 0x7E005700, 0x100000FF, 0x4F800000, 0x7E060F00, 0xD5766A00, 0x0202060C, 0x7D8A0280,
0x4C020080, 0x02000101, 0xD56A0001, 0x00020700, 0x4C000303, 0x4A020303, 0x02000101, 0xD56A0000,
0x00020500, 0xD5690001, 0x0002000C, 0x4C060302, 0x7D8C02F9, 0x06068A02, 0x7D86060C, 0x87EA6A0A,
0x50000080, 0xD5286A00, 0x002A00C1, 0xD5010000, 0x002200C1, 0xD5690000, 0x0002000C, 0x4C000102,
0xE0002000, 0x80000000, 0xBF8C3F70, 0xE0102000, 0x80010002, 0xBF810000,
];
[Fact]
public void TheDemonsSoulsModuloCopyIsRecognized()
{
var program = Decode(DemonsSoulsModuloCopy);
var copy = BoundedFillDetector.DetectCopy(program);
Assert.NotNull(copy);
static FillWord User(uint register) => new(FillWordSource.UserData, register, 0, 0);
Assert.Equal(12u, copy.GroupScalarRegister);
Assert.Equal(new FillWord(FillWordSource.BufferResource, 8, 0, 0), copy.Count);
Assert.Equal(new FillWord(FillWordSource.BufferResource, 8, 0, 4), copy.Modulus);
Assert.Equal([User(0), User(1), User(2), User(3)], copy.Source);
Assert.Equal([User(4), User(5), User(6), User(7)], copy.Destination);
Assert.Null(BoundedFillDetector.Detect(program));
}
[Fact]
public void AModuloCopyStoringAtTheRemainderIsRejected()
{
var words = DemonsSoulsModuloCopy.ToArray();
words[Array.IndexOf(words, 0x80010002u)] = 0x80010000u;
Assert.Null(BoundedFillDetector.DetectCopy(Decode(words)));
}
[Fact]
public void FillsAreNotCopies()
{
Assert.Null(BoundedFillDetector.DetectCopy(Decode(PatternFromUserData)));
Assert.Null(BoundedFillDetector.DetectCopy(Decode(ZeroRangeFromConstantBuffer)));
}
[Fact]
public void APatternFillWithAnotherBranchTargetIsRejected()
{
var words = PatternFromUserData.ToArray();
words[Array.IndexOf(words, 0xBF880015u)] = 0xBF880014u;
Assert.Null(BoundedFillDetector.Detect(Decode(words)));
}
private static Gen5ShaderProgram Decode(uint[] words)
{
var memory = new FakeCpuMemory(ShaderAddress, 0x2000);
Gen5ShaderAtomicDecodeTests.WriteProgram(memory, ShaderAddress, words);
Assert.True(Gen5ShaderTranslator.TryDecodeProgram(new CpuContext(memory, Generation.Gen5), ShaderAddress, out var program, out var error), error);
return program;
}
[Fact]
public void AZeroFillBoundedByAConstantBufferIsRecognized()
{
var fill = BoundedFillDetector.Detect(Decode(ZeroRangeFromConstantBuffer));
Assert.NotNull(fill);
Assert.Equal(8u, fill.GroupScalarRegister);
Assert.Equal(new FillWord(FillWordSource.BufferResource, 4, 0, 4), fill.Count);
Assert.Equal(new FillWord(FillWordSource.BufferResource, 4, 0, 0), fill.Start);
Assert.Equal(0u, fill.ConstantValue);
Assert.Null(fill.Value);
Assert.Equal(
[0u, 1, 2, 3],
fill.Destination.Select(word => word.Source == FillWordSource.UserData ? word.Register : uint.MaxValue));
Assert.True(fill.Formatted);
}
[Fact]
public void AValueFillDescribedThroughAPointerIsRecognized()
{
var fill = BoundedFillDetector.Detect(Decode(ValueRangeFromPointer));
Assert.NotNull(fill);
Assert.Equal(2u, fill.GroupScalarRegister);
Assert.Equal(new FillWord(FillWordSource.IndirectPointer, 0, 0, 4), fill.Count);
Assert.Equal(new FillWord(FillWordSource.IndirectPointer, 0, 0, 0), fill.Start);
Assert.Null(fill.ConstantValue);
Assert.Equal(new FillWord(FillWordSource.IndirectPointer, 0, 0, 8), fill.Value);
Assert.Equal(
[16, 20, 24, 28],
fill.Destination.Select(word => word.Source == FillWordSource.IndirectPointer && word.Register == 0 ? word.Offset : -1));
Assert.False(fill.Formatted);
}
[Fact]
public void AnyOtherInstructionRejectsTheFill()
{
var words = ZeroRangeFromConstantBuffer.ToArray();
words[Array.IndexOf(words, 0x7E020280u)] = 0x7E026E80u;
Assert.Null(BoundedFillDetector.Detect(Decode(words)));
}
[Fact]
public void ABranchThatSkipsPastTheEndRejectsTheFill()
{
var words = ZeroRangeFromConstantBuffer.ToArray();
words[Array.IndexOf(words, 0xBF880007u)] = 0xBF880006u;
Assert.Null(BoundedFillDetector.Detect(Decode(words)));
}
}