sharpemu/tests/SharpEmu.Libs.Tests/Gpu/GpuCommands/GpuCommandInterpreterMemory...

275 lines
13 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Agc;
using SharpEmu.Libs.Gpu.GpuCommands;
using SharpEmu.Libs.Gpu.Images;
using Xunit;
namespace SharpEmu.Libs.Tests.Gpu.GpuCommands;
public sealed class GpuCommandInterpreterMemoryTests
{
private const uint Nop = PacketOpcode.Nop;
private const ulong Label = StreamRunner.LabelAddress;
private const ulong Data = StreamRunner.DataAddress;
private static uint[] WriteData(uint control, ulong destination, params uint[] values)
{
var payload = new uint[3 + values.Length];
payload[0] = control;
payload[1] = StreamRunner.Low(destination);
payload[2] = StreamRunner.High(destination);
values.CopyTo(payload, 3);
return StreamRunner.Packet(PacketOpcode.WriteData, payload);
}
private static uint[] DmaData(uint sourceSelect, uint destinationSelect, ulong source, ulong destination, uint bytes, uint engine = 0) =>
StreamRunner.Packet(
PacketOpcode.DmaData,
engine | ((destinationSelect & 3) << 20) | ((sourceSelect & 3) << 29),
StreamRunner.Low(source), StreamRunner.High(source),
StreamRunner.Low(destination), StreamRunner.High(destination),
bytes | ((sourceSelect & 4) << 24) | ((destinationSelect & 4) << 25) | ((sourceSelect & 8) << 25) | ((destinationSelect & 8) << 26));
private static uint[] CopyData(uint rawSource, uint rawDestination, uint engine, bool is64Bit, ulong source, ulong destination) =>
StreamRunner.Packet(
PacketOpcode.CopyData,
rawSource | (rawDestination << 8) | (is64Bit ? 1u << 16 : 0) | (engine << 30),
StreamRunner.Low(source), StreamRunner.High(source),
StreamRunner.Low(destination), StreamRunner.High(destination));
[Fact]
public void WriteData_CopiesOrWritesOneAddress()
{
var runner = new StreamRunner();
runner.Run(WriteData(5u << 8, Label, 1, 2, 3));
Assert.Equal(1u, runner.Host.ReadDword(Label));
Assert.Equal(3u, runner.Host.ReadDword(Label + 8));
runner.Run(WriteData((5u << 8) | (1u << 16), Label, 7, 8));
Assert.Equal(8u, runner.Host.ReadDword(Label));
Assert.Equal(2u, runner.Host.ReadDword(Label + 4));
Assert.Contains("write-data destination selector", runner.RunExpectingFatal(WriteData(0, Label, 1)).Message);
Assert.Empty(runner.Host.Calls);
}
[Fact]
public void WrappedWriteData_UsesTheByteControlAndSkipsOtherDestinations()
{
var runner = new StreamRunner();
runner.Host.WriteDword(Label, 0);
runner.Run(StreamRunner.CustomPacket(Nop, PacketCustomCode.WriteData, 5u, StreamRunner.Low(Label), StreamRunner.High(Label), 0xAB, 0xCD));
Assert.Equal(0xABu, runner.Host.ReadDword(Label));
Assert.Equal(0xCDu, runner.Host.ReadDword(Label + 4));
runner.Run(StreamRunner.CustomPacket(Nop, PacketCustomCode.WriteData, 5u | (1u << 16), StreamRunner.Low(Label), StreamRunner.High(Label), 1, 2));
Assert.Equal(2u, runner.Host.ReadDword(Label));
runner.Run(StreamRunner.CustomPacket(Nop, PacketCustomCode.WriteData, 3u, StreamRunner.Low(Label), StreamRunner.High(Label), 9));
Assert.Equal(2u, runner.Host.ReadDword(Label));
}
[Fact]
public void DmaData_FillsCopiesAndSkips()
{
var runner = new StreamRunner();
runner.Run(DmaData(2, 0, 0x1234, Data, 16));
Assert.Equal($"fill {Data:X} 10 00001234 gds=False", Assert.Single(runner.Host.Calls));
Assert.Equal(0x1234u, runner.Host.ReadDword(Data + 12));
runner.Host.Calls.Clear();
runner.Run(DmaData(0, 1, Data, 0x40, 8));
Assert.Equal($"copy 40 {Data:X} 8 dstGds=True srcGds=False", Assert.Single(runner.Host.Calls));
runner.Host.Calls.Clear();
runner.Run(DmaData(3, 2, Data, Label, 8), DmaData(0, 0, Data, 0x3022C, 8), DmaData(2, 0, 0, Data, 0));
Assert.Empty(runner.Host.Calls);
Assert.Contains("GDS-to-GDS", runner.RunExpectingFatal(DmaData(1, 1, 0, 0x10, 8)).Message);
Assert.Contains("nowhere source", runner.RunExpectingFatal(DmaData(0, 2, Data, Label, 8)).Message);
}
[Fact]
public void WrappedDmaData_BothLayouts()
{
var runner = new StreamRunner();
runner.Run(StreamRunner.CustomPacket(Nop, PacketCustomCode.DmaData, 0u | (2u << 16), 0, 8, StreamRunner.Low(Data), StreamRunner.High(Data), 0x77, 0));
Assert.Equal($"fill {Data:X} 8 00000077 gds=False", Assert.Single(runner.Host.Calls));
runner.Host.Calls.Clear();
runner.Run(StreamRunner.CustomPacket(Nop, PacketCustomCode.DmaData, StreamRunner.Low(Label), StreamRunner.High(Label), StreamRunner.Low(Data), StreamRunner.High(Data), 8, 3u | (0u << 8)));
Assert.Equal($"copy {Label:X} {Data:X} 8 dstGds=False srcGds=False", Assert.Single(runner.Host.Calls));
Assert.Equal(0x77u, runner.Host.ReadDword(Label));
}
[Theory]
[InlineData(0u, 2u, 0u)]
[InlineData(1u, 2u, 0x12345678u)]
[InlineData(0u, 3u, 0x87654321u)]
[InlineData(1u, 3u, 0u)]
public void DmaBuilder_UsesGuestArgumentsForFillAndCopy(uint engine, uint sourceSelector, uint value)
{
var runner = new StreamRunner();
var context = new CpuContext(runner.Host.Memory, Generation.Gen5);
const ulong commandBufferAddress = StreamRunner.TableAddress;
const ulong stackAddress = StreamRunner.TableAddress + 0x100;
runner.Host.WriteQword(commandBufferAddress + 0x10, StreamRunner.CommandAddress);
runner.Host.WriteQword(commandBufferAddress + 0x18, StreamRunner.CommandAddress + 0x100);
runner.Host.WriteDword(Data, value);
runner.Host.WriteDword(Label, 0xFFFFFFFF);
context[CpuRegister.Rdi] = commandBufferAddress;
context[CpuRegister.Rsi] = engine;
context[CpuRegister.Rdx] = 3;
context[CpuRegister.Rcx] = 0;
context[CpuRegister.R8] = Label;
context[CpuRegister.R9] = sourceSelector;
context[CpuRegister.Rsp] = stackAddress;
runner.Host.WriteQword(stackAddress + 8, 1);
runner.Host.WriteQword(stackAddress + 16, sourceSelector == 2 ? value : Data);
runner.Host.WriteQword(stackAddress + 24, 4);
runner.Host.WriteQword(stackAddress + 32, 1);
runner.Host.WriteQword(stackAddress + 40, 1);
runner.Host.WriteQword(stackAddress + 48, 1);
AgcExports.DcbDmaData(context);
Assert.Equal(StreamRunner.CommandAddress, context[CpuRegister.Rax]);
Assert.Equal(StreamRunner.CommandAddress + 32, runner.Host.ReadQword(commandBufferAddress + 0x10));
Assert.Equal(3u | (sourceSelector << 16) | (1u << 24), runner.Host.ReadDword(StreamRunner.CommandAddress + 4));
Assert.Equal(engine | 0x01010100u, runner.Host.ReadDword(StreamRunner.CommandAddress + 8));
runner.Interpreter.Process(new PacketCursorStack(), StreamRunner.CommandAddress, 8);
Assert.Equal(value, runner.Host.ReadDword(Label));
Assert.Equal(
sourceSelector == 2 ? $"fill {Label:X} 4 {value:X8} gds=False" : $"copy {Label:X} {Data:X} 4 dstGds=False srcGds=False",
Assert.Single(runner.Host.Calls));
}
[Fact]
public void CopyDataBuilder_EncodesGuestArguments()
{
var runner = new StreamRunner();
var context = new CpuContext(runner.Host.Memory, Generation.Gen5);
const ulong commandBufferAddress = StreamRunner.TableAddress;
const ulong stackAddress = StreamRunner.TableAddress + 0x100;
const ulong immediateValue = 0x1122_3344_5566_7788;
runner.Host.WriteQword(commandBufferAddress + 0x10, StreamRunner.CommandAddress);
runner.Host.WriteQword(commandBufferAddress + 0x18, StreamRunner.CommandAddress + 0x100);
context[CpuRegister.Rdi] = commandBufferAddress;
context[CpuRegister.Rsi] = 2;
context[CpuRegister.Rdx] = 3;
context[CpuRegister.Rcx] = Label;
context[CpuRegister.R8] = 10;
context[CpuRegister.R9] = 2;
context[CpuRegister.Rsp] = stackAddress;
runner.Host.WriteQword(stackAddress + 8, immediateValue);
runner.Host.WriteQword(stackAddress + 16, 1);
runner.Host.WriteQword(stackAddress + 24, 1);
AgcExports.DcbCopyData(context);
var expectedControl =
((10u >> 1) & 0xFu) |
(((2u >> 1) & 0xFu) << 8) |
((2u & 0x3u) << 13) |
(1u << 16) |
(1u << 20) |
((3u & 0x3u) << 25) |
((10u & 0x1u) << 30);
Assert.Equal(StreamRunner.CommandAddress, context[CpuRegister.Rax]);
Assert.Equal(StreamRunner.CommandAddress + 24, runner.Host.ReadQword(commandBufferAddress + 0x10));
Assert.Equal(PacketHeader.Make(6, PacketOpcode.CopyData), runner.Host.ReadDword(StreamRunner.CommandAddress));
Assert.Equal(expectedControl, runner.Host.ReadDword(StreamRunner.CommandAddress + 4));
Assert.Equal(immediateValue, runner.Host.ReadQword(StreamRunner.CommandAddress + 8));
Assert.Equal(Label, runner.Host.ReadQword(StreamRunner.CommandAddress + 16));
runner.Interpreter.Process(new PacketCursorStack(), StreamRunner.CommandAddress, 6);
Assert.Equal(immediateValue, runner.Host.ReadQword(Label));
}
[Fact]
public void CopyData_MapsSelectorsToTransfers()
{
var runner = new StreamRunner();
runner.Host.WriteQword(Data, 0x1122_3344_5566_7788);
runner.Run(CopyData(2, 2, 0, false, Data, Label));
Assert.Equal($"copy {Label:X} {Data:X} 4 dstGds=False srcGds=False", Assert.Single(runner.Host.Calls));
runner.Host.Calls.Clear();
runner.Run(CopyData(5, 2, 0, false, 0x5A5A, Label));
Assert.Equal($"fill {Label:X} 4 00005A5A gds=False", Assert.Single(runner.Host.Calls));
runner.Host.Calls.Clear();
runner.Run(CopyData(5, 2, 0, true, 0x0102_0304_0506_0708, Label));
Assert.Empty(runner.Host.Calls);
Assert.Equal(0x0102_0304_0506_0708UL, runner.Host.ReadQword(Label));
runner.Run(CopyData(4, 1, 1, true, 0, Label));
Assert.NotEqual(0x0102_0304_0506_0708UL, runner.Host.ReadQword(Label));
Assert.Contains("copy-data source selector", runner.RunExpectingFatal(CopyData(0, 2, 0, false, Data, Label)).Message);
Assert.Contains("reference-clock copy-data", runner.RunExpectingFatal(CopyData(4, 3, 1, false, 0, Label)).Message);
}
[Fact]
public void CopyData_AtomicReturnAndComputeEncoding()
{
var runner = new StreamRunner();
Assert.Contains("atomic return value is not available", runner.RunExpectingFatal(CopyData(6, 2, 0, false, 0, Label)).Message);
var atomic = StreamRunner.Packet(PacketOpcode.AtomicMemory, 0x0F, StreamRunner.Low(Data), StreamRunner.High(Data), 5, 0, 0, 0, 0);
runner.Host.WriteDword(Data, 40);
runner.Run(atomic, CopyData(6, 2, 0, false, 0, Label));
Assert.Equal(40u, runner.Host.ReadDword(Label));
Assert.Equal(45u, runner.Host.ReadDword(Data));
var compute = new StreamRunner(queueId: 1);
compute.Host.WriteDword(Data, 0xBEEF);
compute.Run(CopyData(2, 2, 0, false, Data, Label));
Assert.Equal($"copy {Label:X} {Data:X} 4 dstGds=False srcGds=False", Assert.Single(compute.Host.Calls));
Assert.Contains("copy-data source selector", compute.RunExpectingFatal(CopyData(4, 2, 0, false, Data, Label)).Message);
}
[Fact]
public void GetLodStats_ZeroesTheBufferAndMarksItValid()
{
var runner = new StreamRunner();
runner.Host.WriteQword(Label, ulong.MaxValue);
runner.Host.WriteQword(Label + 8, ulong.MaxValue);
runner.Run(StreamRunner.Packet(PacketOpcode.GetLodStats, 16, StreamRunner.Low(Label), StreamRunner.High(Label), 0));
Assert.Equal(1UL, runner.Host.ReadQword(Label));
Assert.Equal(0UL, runner.Host.ReadQword(Label + 8));
}
[Fact]
public void GetLodStats_PointsAFullReportAtItsCounters()
{
var runner = new StreamRunner();
runner.Run(StreamRunner.Packet(PacketOpcode.GetLodStats, MipStatistics.ReportBytes, StreamRunner.Low(Data), StreamRunner.High(Data), 0));
Assert.Equal(MipStatistics.FirstCounterOffset, runner.Host.ReadDword(Data));
Assert.Equal(255u, runner.Host.ReadDword(Data + MipStatistics.FirstCounterOffset + 255 * 8) >> 24);
}
[Fact]
public void ReadOfUnmappedMemory_IsFatalWithTheAddress()
{
var runner = new StreamRunner();
var fatal = runner.RunExpectingFatal(StreamRunner.Packet(PacketOpcode.WaitRegisterMemory, 0x13, 0x10, 0, 1, 1, 0));
Assert.Contains("cannot read guest memory: address=0x0000000000000010", fatal.Message);
}
}