sharpemu/tests/SharpEmu.ShaderCompiler.Tests/Gen5TypedBufferLoadDecodeTe...

137 lines
5.1 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
using Xunit;
namespace SharpEmu.ShaderCompiler.Tests;
// Typed buffer instructions keep their own unified format; formatted untyped ones do not carry one.
public sealed class Gen5TypedBufferLoadDecodeTests
{
private const ulong ShaderAddress = 0x1000;
private const uint Format32x4Float = 77;
[Theory]
[InlineData(0u, false, "TBufferLoadFormatX", 1u)]
[InlineData(1u, false, "TBufferLoadFormatXy", 2u)]
[InlineData(2u, false, "TBufferLoadFormatXyz", 3u)]
[InlineData(3u, false, "TBufferLoadFormatXyzw", 4u)]
[InlineData(4u, false, "TBufferStoreFormatX", 1u)]
[InlineData(7u, false, "TBufferStoreFormatXyzw", 4u)]
[InlineData(0u, true, "TBufferLoadFormatD16X", 1u)]
[InlineData(1u, true, "TBufferLoadFormatD16Xy", 1u)]
[InlineData(2u, true, "TBufferLoadFormatD16Xyz", 2u)]
[InlineData(3u, true, "TBufferLoadFormatD16Xyzw", 2u)]
[InlineData(7u, true, "TBufferStoreFormatD16Xyzw", 2u)]
public void TypedBufferInstructions_DecodeTheirOpcodeAndFormat(
uint opcodeLow,
bool opcodeHigh,
string expectedOpcode,
uint expectedDwordCount)
{
var program = Decode(
[
TypedWord0(Format32x4Float, opcodeLow, offset: 0x30, indexEnabled: true),
TypedWord1(opcodeHigh, scalarResource: 8, vectorData: 4),
0xBF810000,
]);
var instruction = program.Instructions[0];
Assert.Equal(Gen5ShaderEncoding.Mtbuf, instruction.Encoding);
Assert.Equal(expectedOpcode, instruction.Opcode);
var control = Assert.IsType<Gen5BufferMemoryControl>(instruction.Control);
Assert.True(control.Typed);
Assert.Equal(Format32x4Float, control.TypedFormat);
Assert.Equal(expectedDwordCount, control.DwordCount);
Assert.Equal(0x30, control.OffsetBytes);
Assert.True(control.IndexEnabled);
Assert.Equal(8u, control.ScalarResource);
Assert.Equal(4u, control.VectorData);
}
[Fact]
public void FormattedUntypedInstructions_CarryNoInstructionFormat()
{
// buffer_load_format_xyzw v[4:7], v0, s[8:11], 0 idxen
var program = Decode(
[
0xE0002000 | (3u << 18),
0x80020400,
0xBF810000,
]);
var control = Assert.IsType<Gen5BufferMemoryControl>(program.Instructions[0].Control);
Assert.Equal("BufferLoadFormatXyzw", program.Instructions[0].Opcode);
Assert.False(control.Typed);
Assert.Equal(0u, control.TypedFormat);
}
private static Gen5ShaderProgram Decode(uint[] words)
{
var context = WriteProgram(new TestCpuMemory(ShaderAddress, 0x100), words);
Assert.True(Gen5ShaderTranslator.TryDecodeProgram(context, ShaderAddress, out var program, out var error), error);
return program;
}
private static CpuContext WriteProgram(TestCpuMemory memory, uint[] words)
{
Span<byte> shader = stackalloc byte[words.Length * sizeof(uint)];
for (var index = 0; index < words.Length; index++)
{
BinaryPrimitives.WriteUInt32LittleEndian(shader[(index * sizeof(uint))..], words[index]);
}
Assert.True(memory.TryWrite(ShaderAddress, shader));
return new CpuContext(memory, Generation.Gen5);
}
// MTBUF word 0: offset[11:0], offen[12], idxen[13], op[18:16], format[25:19], encoding 0x3A.
private static uint TypedWord0(uint format, uint opcodeLow, uint offset, bool indexEnabled) =>
0xE800_0000u | (format << 19) | (opcodeLow << 16) | (indexEnabled ? 1u << 13 : 0) | offset;
// MTBUF word 1: vaddr[7:0], vdata[15:8], srsrc[20:16], op[21], soffset[31:24] (0x80 = constant 0).
private static uint TypedWord1(bool opcodeHigh, uint scalarResource, uint vectorData) =>
(0x80u << 24) | (opcodeHigh ? 1u << 21 : 0) | ((scalarResource / 4) << 16) | (vectorData << 8);
private sealed class TestCpuMemory(ulong baseAddress, int size) : ICpuMemory
{
private readonly byte[] _storage = new byte[size];
public bool TryRead(ulong virtualAddress, Span<byte> destination)
{
if (!TryResolve(virtualAddress, destination.Length, out var offset))
{
return false;
}
_storage.AsSpan(offset, destination.Length).CopyTo(destination);
return true;
}
public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
{
if (!TryResolve(virtualAddress, source.Length, out var offset))
{
return false;
}
source.CopyTo(_storage.AsSpan(offset, source.Length));
return true;
}
private bool TryResolve(ulong virtualAddress, int length, out int offset)
{
offset = 0;
if (virtualAddress < baseAddress || virtualAddress - baseAddress + (ulong)length > (ulong)_storage.Length)
{
return false;
}
offset = (int)(virtualAddress - baseAddress);
return true;
}
}
}