sharpemu/tests/SharpEmu.ShaderCompiler.Tests/Gen5DataShareWrite64Tests.cs

111 lines
5.9 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.ShaderCompiler.Metal;
using SharpEmu.ShaderCompiler.Vulkan;
using Xunit;
using static SharpEmu.ShaderCompiler.Tests.Resources.ResourceTestProgram;
namespace SharpEmu.ShaderCompiler.Tests;
public sealed class Gen5DataShareWrite64Tests
{
public const uint InitialLowerWord = 0xDEADBEEF;
public const uint InitialUpperWord = 0xFEEDABCD;
public static IEnumerable<object[]> PairedWriteCases()
{
foreach (var largeStride in new[] { false, true })
foreach (var global in new[] { false, true })
{
uint highestOffset = largeStride ? 63u : 255u;
(uint First, uint Second)[] offsets = [(0, 8), (8, 0), (3, 3),
(highestOffset, 1), (1, highestOffset), (highestOffset, highestOffset)];
foreach (var writeEnabled in new[] { false, true })
foreach (var (first, second) in offsets)
yield return [global, first, second, writeEnabled, 20u, largeStride];
yield return [global, 0u, 8u, true, 10u, largeStride];
yield return [global, 0u, 8u, true, 11u, largeStride];
}
}
public static uint SourceWord(uint register) => register switch
{
10 => 0xA1234567,
11 => 0xB2345678,
12 => 0xC3456789,
20 => 0x11223344,
21 => 0x55667788,
_ => throw new ArgumentOutOfRangeException(nameof(register)),
};
public static uint[] ExpectedWords(uint firstOffset, uint secondOffset, bool writeEnabled, uint secondSource) => !writeEnabled
? [InitialLowerWord, InitialUpperWord, InitialLowerWord, InitialUpperWord]
: [SourceWord(10), SourceWord(11), SourceWord(firstOffset == secondOffset ? 10 : secondSource),
SourceWord(firstOffset == secondOffset ? 11 : secondSource + 1)];
[Theory]
[MemberData(nameof(PairedWriteCases))]
public void PairedWriteDecodesIndependentSourcePairsAndCompilesOnBothBackends(
bool global, uint firstOffset, uint secondOffset, bool writeEnabled, uint secondSource, bool largeStride)
{
var program = CreateReadbackProgram(global, firstOffset, secondOffset, writeEnabled, secondSource, largeStride);
var opcode = largeStride ? "DsWrite2St64B64" : "DsWrite2B64";
var write = Assert.Single(program.Instructions, instruction => instruction.Opcode == opcode);
Assert.Equal([Gen5Operand.Vector(3), Gen5Operand.Vector(10), Gen5Operand.Vector(11),
Gen5Operand.Vector(secondSource), Gen5Operand.Vector(secondSource + 1)], write.Sources);
Assert.Empty(write.Destinations);
var control = Assert.IsType<Gen5DataShareControl>(write.Control);
Assert.Equal(firstOffset, control.Offset0);
Assert.Equal(secondOffset, control.Offset1);
Assert.Equal(global, control.Gds);
var (plan, resources, layout) = Prepare(program);
var request = new ShaderCompileRequest(plan, resources, layout) { LocalSizeX = 1, ThreadCountX = 1 };
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out _, out var spirvError), spirvError);
Assert.True(Gen5MslTranslator.TryCompileProgram(request, out _, out var metalError), metalError);
}
public static Gen5ShaderProgram CreateReadbackProgram(bool global, uint firstOffset, uint secondOffset, bool writeEnabled,
uint secondSource, bool largeStride)
{
uint[] words = [(largeStride ? 0xD93C0000u : 0xD9380000u) | (global ? 1u << 17 : 0) | firstOffset | (secondOffset << 8),
3u | (10u << 8) | (secondSource << 16), 0xBF810000];
var bytes = new byte[words.Length * sizeof(uint)];
for (var index = 0; index < words.Length; index++)
BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(index * sizeof(uint)), words[index]);
Assert.True(Gen5ShaderTranslator.TryDecodeProgram(new CpuContext(new InstructionMemory(bytes), Generation.Gen5), 0x1000,
out var decoded, out var error), error);
var offsetStride = largeStride ? 512u : 8u;
var firstByteOffset = firstOffset * offsetStride;
var secondByteOffset = secondOffset * offsetStride;
return Program(
MoveVector(0, 3, 8), MoveVector(4, 30, InitialLowerWord), MoveVector(8, 31, InitialUpperWord),
DataShare(12, "DsWriteB64", global, [Gen5Operand.Vector(3), Gen5Operand.Vector(30), Gen5Operand.Vector(31)], [],
firstByteOffset & 0xFF, firstByteOffset >> 8),
DataShare(20, "DsWriteB64", global, [Gen5Operand.Vector(3), Gen5Operand.Vector(30), Gen5Operand.Vector(31)], [],
secondByteOffset & 0xFF, secondByteOffset >> 8),
MoveVector(28, 10, SourceWord(10)), MoveVector(32, 11, SourceWord(11)), MoveVector(36, 12, SourceWord(12)),
MoveVector(40, 20, SourceWord(20)), MoveVector(44, 21, SourceWord(21)),
MoveScalar(48, 126, writeEnabled ? 1u : 0u), decoded.Instructions[0] with { Pc = 52 }, MoveScalar(60, 126, 1),
DataShare(64, "DsReadB64", global, [Gen5Operand.Vector(3)], [4, 5], firstByteOffset & 0xFF, firstByteOffset >> 8),
DataShare(72, "DsReadB64", global, [Gen5Operand.Vector(3)], [6, 7], secondByteOffset & 0xFF, secondByteOffset >> 8),
BufferAccess(80, "BufferStoreDwordx4", 4, dwords: 4, vectorData: 4), EndProgram(88));
}
private sealed class InstructionMemory(byte[] bytes) : ICpuMemory
{
public bool TryRead(ulong address, Span<byte> destination)
{
if (address < 0x1000 || destination.Length > bytes.Length ||
address - 0x1000 > (ulong)(bytes.Length - destination.Length)) return false;
bytes.AsSpan((int)(address - 0x1000), destination.Length).CopyTo(destination);
return true;
}
public bool TryWrite(ulong address, ReadOnlySpan<byte> source) => false;
}
}