491 lines
23 KiB
C#
491 lines
23 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using SharpEmu.HLE.GpuMemory;
|
|
using SharpEmu.Libs.Gpu.Images;
|
|
using SharpEmu.Libs.Tests.Gpu.Buffers;
|
|
using SharpEmu.Libs.Tests.Gpu.Scheduling;
|
|
using Silk.NET.Vulkan;
|
|
using Xunit;
|
|
using static SharpEmu.Libs.Tests.Gpu.Images.ImageCacheTestSupport;
|
|
|
|
namespace SharpEmu.Libs.Tests.Gpu.Images;
|
|
|
|
// Surface metadata registration, the pending DCC state and compressed surfaces.
|
|
public sealed partial class GuestImageCacheTests
|
|
{
|
|
[Fact]
|
|
public void HtileEntries_ReportRegisteredAndClearedState()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x10000, ReadWrite);
|
|
ulong[] entries = [address + 0x1f00, address + 0x2000, address + 0x2100];
|
|
foreach (var entry in entries)
|
|
{
|
|
harness.Images.RegisterHtileMetadataForTest(entry);
|
|
Assert.True(harness.Images.IsMetadata(entry));
|
|
Assert.False(harness.Images.IsMetadataCleared(entry, 0));
|
|
}
|
|
|
|
Assert.True(harness.Images.ClearMetadata(entries[2]));
|
|
Assert.True(harness.Images.IsMetadataCleared(entries[2], 0, out var fill));
|
|
Assert.Equal(0xffffffffu, fill);
|
|
Assert.False(harness.Images.IsMetadataCleared(entries[0], 0));
|
|
Assert.False(harness.Images.IsMetadataCleared(entries[2], 32));
|
|
Assert.False(harness.Images.SetMetadataSlice(entries[2], 32, true));
|
|
Assert.False(harness.Images.IsMetadata(address + 0x3000));
|
|
Assert.False(harness.Images.ClearMetadata(address + 0x3000));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void PendingDccAndHtile_StayApart()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x20000, ReadWrite);
|
|
var metadataA = address + 0x13000;
|
|
var metadataB = address + 0x13100;
|
|
|
|
ImageRequest MetadataDepth(ulong data, ulong metadata)
|
|
{
|
|
var request = LinearRequest(data, 4, Format.D32Sfloat, GuestPixelFormat.Bits32Float, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 4, 1);
|
|
request = AsDepthTarget(request, Format.D32Sfloat);
|
|
request.Description.Metadata.Kind = MetadataKind.Htile;
|
|
request.Description.Metadata.Range = new GuestSpan(metadata, 0x80);
|
|
return request;
|
|
}
|
|
|
|
var depthA = MetadataDepth(address + 0x12000, metadataA);
|
|
var depthB = MetadataDepth(address + 0x12100, metadataB);
|
|
depthA.Description.HtileClearMask = 0;
|
|
var depthAId = harness.Find(ref depthA);
|
|
var depthBId = harness.Find(ref depthB);
|
|
Assert.False(harness.Images.TryAbsorbDccFill(metadataA, 0x80, 0));
|
|
Assert.False(harness.Images.IsMetadata(metadataA));
|
|
|
|
var localA = depthA;
|
|
var localB = depthB;
|
|
harness.Worker.Run(() =>
|
|
{
|
|
Assert.NotEqual(0UL, harness.Images.AcquireDepthTargetView(depthAId, localA).Handle);
|
|
Assert.NotEqual(0UL, harness.Images.AcquireDepthTargetView(depthBId, localB).Handle);
|
|
});
|
|
Assert.False(harness.Images.IsMetadataCleared(metadataA, 0));
|
|
Assert.True(harness.Images.ClearMetadata(metadataA));
|
|
Assert.True(harness.Images.ClearMetadata(metadataB));
|
|
|
|
var alias = LinearRequest(metadataA, 4, Format.R32Sfloat, GuestPixelFormat.Bits32Float, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 4, 1);
|
|
alias = AsColorTarget(alias);
|
|
var aliasId = harness.Acquire(ref alias, exactFormat: true);
|
|
Assert.True(aliasId.IsValid);
|
|
Assert.True(harness.Image(depthAId).IsGpuModified);
|
|
Assert.True(harness.Image(depthBId).IsGpuModified);
|
|
Assert.True(harness.Images.IsMetadata(metadataA));
|
|
Assert.True(harness.Images.IsMetadataCleared(metadataA, 0));
|
|
Assert.True(harness.Images.IsMetadata(metadataB));
|
|
Assert.True(harness.Images.IsMetadataCleared(metadataB, 0));
|
|
|
|
Assert.True(harness.Images.SetMetadataSlice(metadataA, 0, false));
|
|
Assert.False(harness.Images.TryAbsorbDccFill(metadataA, 0x80, 0));
|
|
Assert.False(harness.Images.IsMetadataCleared(metadataA, 0));
|
|
Assert.True(harness.Images.SetMetadataSlice(metadataB, 0, true));
|
|
Assert.True(harness.Images.IsMetadataCleared(metadataB, 0));
|
|
Assert.True(harness.Images.SetMetadataSlice(metadataB, 0, false));
|
|
Assert.False(harness.Images.IsMetadataCleared(metadataB, 0));
|
|
harness.Worker.Run(() => harness.Cache.FillBuffer(metadataB, 4, 0, isGds: false));
|
|
Assert.True(harness.Images.IsMetadataCleared(metadataB, 0));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void DccFills_RemainGuestProducersUntilDescriptorDiscovery()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x20000, ReadWrite);
|
|
var metadata = address + 0x10000;
|
|
harness.Write(metadata, Bytes(0x11223344u));
|
|
Assert.False(harness.Images.TryAbsorbDccFill(metadata, 0x1000, 0x20202020u));
|
|
Assert.False(harness.Images.IsMetadata(metadata));
|
|
Assert.False(harness.Images.TryAbsorbDccFill(metadata, 0x1000, 0x40404040u));
|
|
|
|
harness.Write(address + 0xc000, Bytes(0x55667788u));
|
|
var target = LinearRequest(address + 0xc000, 4, Format.R8G8B8A8Srgb, GuestPixelFormat.Bits8_8_8_8Srgb, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 4, 1);
|
|
target = AsColorTarget(target);
|
|
target.Description.Metadata.Kind = MetadataKind.Dcc;
|
|
target.Description.Metadata.Range = new GuestSpan(metadata, 0x1000);
|
|
target.Description.Metadata.Compression = DisplayCompression.Dcc256_256_0;
|
|
var targetId = harness.Acquire(ref target);
|
|
Assert.False(harness.Images.IsMetadata(metadata));
|
|
Assert.False(harness.Images.IsMetadataCleared(metadata, 0));
|
|
Assert.Equal(0x11223344u, harness.ReadUInt32(metadata));
|
|
Assert.False(harness.Images.TryAbsorbDccFill(metadata, 0x1000, 0x01010101u));
|
|
Assert.False(harness.Images.TryAbsorbDccFill(metadata, 0x1000, 0xc0c0c0c0u));
|
|
Assert.False(harness.Images.IsMetadataCleared(metadata, 5));
|
|
Assert.Equal(0x11223344u, harness.ReadUInt32(metadata));
|
|
Assert.False(harness.Images.ClearMetadata(metadata));
|
|
|
|
// A compressed target rejects readback and read claims but keeps GPU ownership.
|
|
Assert.Equal(0x55667788u, harness.ReadUInt32(address + 0xc000));
|
|
Assert.True(harness.Image(targetId).IsGpuModified);
|
|
Assert.False(harness.Worker.Run(() => harness.Images.TryDownloadForTest(targetId)));
|
|
Assert.True(harness.Image(targetId).IsGpuModified);
|
|
Assert.False(harness.Image(targetId).IsBufferModified);
|
|
var video = target;
|
|
video.Role = ImageRole.DisplaySurface;
|
|
video.View = video.View with { Usage = ImageUsageFlags.SampledBit };
|
|
var videoId = harness.Find(ref video);
|
|
var videoImage = harness.Image(videoId);
|
|
Assert.Equal(targetId, videoId);
|
|
Assert.False(videoImage.Binding.IsBound);
|
|
Assert.False(videoImage.Binding.IsTarget);
|
|
Assert.False(videoImage.Binding.NeedsRebind);
|
|
Assert.False(videoImage.Uses.VideoOut);
|
|
|
|
var depth = LinearRequest(address + 0xe000, 4, Format.D32Sfloat, GuestPixelFormat.Bits32Float, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 4, 1);
|
|
depth = AsDepthTarget(depth, Format.D32Sfloat);
|
|
depth.Description.Metadata.Kind = MetadataKind.Htile;
|
|
depth.Description.Metadata.Range = new GuestSpan(metadata, 0x100);
|
|
var depthId = harness.Find(ref depth);
|
|
var depthLocal = depth;
|
|
harness.Worker.Run(() => Assert.NotEqual(
|
|
0UL,
|
|
harness.Images.AcquireDepthTargetView(depthId, depthLocal).Handle));
|
|
Assert.True(harness.Images.IsMetadata(metadata));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void DccVolumeOverThirtyTwoSlices_MaterializesAndConsumesEveryUniformSlice()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x80000, ReadWrite);
|
|
var imageAddress = address + 0x1000;
|
|
var metadataAddress = address + 0x10000;
|
|
const uint depth = 64;
|
|
const ulong metadataSliceSize = 0x1000;
|
|
var metadataBytes = new byte[checked((int)(depth * metadataSliceSize))];
|
|
Array.Fill(metadataBytes, (byte)0xc0);
|
|
harness.Write(metadataAddress, metadataBytes);
|
|
|
|
const uint width = 4;
|
|
const uint height = 4;
|
|
var imageSize = (ulong)width * height * depth * 4;
|
|
var target = LinearRequest(
|
|
imageAddress,
|
|
imageSize,
|
|
Format.R8G8B8A8Unorm,
|
|
GuestPixelFormat.Bits8_8_8_8UNorm,
|
|
GuestImageType.Color3D,
|
|
new Extent3D(width, height, depth),
|
|
1,
|
|
4,
|
|
1);
|
|
target = AsColorTarget(target);
|
|
target.View = target.View with
|
|
{
|
|
Type = ImageViewType.Type2DArray,
|
|
BaseLayer = 0,
|
|
LayerCount = depth,
|
|
};
|
|
target.Description.Metadata.Kind = MetadataKind.Dcc;
|
|
target.Description.Metadata.Range = new GuestSpan(metadataAddress, depth * metadataSliceSize);
|
|
target.Description.Metadata.DccAlphaMsb = true;
|
|
|
|
Assert.False(harness.Images.TryAbsorbDccFill(
|
|
metadataAddress,
|
|
depth * metadataSliceSize,
|
|
0xc0c0c0c0u));
|
|
var targetId = harness.Find(ref target);
|
|
harness.Finish();
|
|
|
|
Assert.True(targetId.IsValid);
|
|
Assert.Equal(uint.MaxValue, harness.ReadUInt32(metadataAddress));
|
|
Assert.Equal(uint.MaxValue, harness.ReadUInt32(metadataAddress + 31 * metadataSliceSize));
|
|
Assert.Equal(uint.MaxValue, harness.ReadUInt32(metadataAddress + 32 * metadataSliceSize));
|
|
Assert.Equal(uint.MaxValue, harness.ReadUInt32(metadataAddress + (depth - 1) * metadataSliceSize));
|
|
Assert.False(harness.Images.IsMetadataCleared(metadataAddress, 0));
|
|
Assert.False(harness.Images.IsMetadataCleared(metadataAddress, 31));
|
|
Assert.False(harness.Images.IsMetadataCleared(metadataAddress, 32));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void SelflossShapeDccVolume_HandlesOneHundredTwentyEightSlicesIndependently()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x140000, ReadWrite);
|
|
var imageAddress = address + 0x1000;
|
|
var metadataAddress = address + 0x20000;
|
|
const uint depth = 128;
|
|
const ulong metadataSliceSize = 0x2000;
|
|
var metadataBytes = new byte[checked((int)(depth * metadataSliceSize))];
|
|
Array.Fill(metadataBytes, (byte)0xc0);
|
|
const uint mixedSlice = 73;
|
|
Array.Fill(
|
|
metadataBytes,
|
|
(byte)0x55,
|
|
checked((int)(mixedSlice * metadataSliceSize + 1)),
|
|
checked((int)metadataSliceSize - 1));
|
|
harness.Write(metadataAddress, metadataBytes);
|
|
|
|
const uint width = 4;
|
|
const uint height = 4;
|
|
var imageSize = (ulong)width * height * depth * 8;
|
|
var texture = LinearRequest(
|
|
imageAddress,
|
|
imageSize,
|
|
Format.R16G16B16A16Sfloat,
|
|
GuestPixelFormat.Bits16_16_16_16Float,
|
|
GuestImageType.Color3D,
|
|
new Extent3D(width, height, depth),
|
|
1,
|
|
8,
|
|
1);
|
|
texture.Description.Metadata.Kind = MetadataKind.Dcc;
|
|
texture.Description.Metadata.Range = new GuestSpan(
|
|
metadataAddress,
|
|
depth * metadataSliceSize);
|
|
texture.Description.Metadata.DccAlphaMsb = true;
|
|
|
|
var textureId = harness.Find(ref texture);
|
|
harness.Finish();
|
|
|
|
Assert.True(textureId.IsValid);
|
|
Assert.Equal(uint.MaxValue, harness.ReadUInt32(metadataAddress));
|
|
Assert.Equal(
|
|
0x555555c0u,
|
|
harness.ReadUInt32(metadataAddress + mixedSlice * metadataSliceSize));
|
|
Assert.Equal(
|
|
uint.MaxValue,
|
|
harness.ReadUInt32(metadataAddress + (depth - 1) * metadataSliceSize));
|
|
Assert.False(harness.Images.IsMetadata(metadataAddress));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void DccMetadataScan_RejectsAMismatchBeyondTheFirstPooledChunk()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x50000, ReadWrite);
|
|
var imageAddress = address + 0x1000;
|
|
var metadataAddress = address + 0x10000;
|
|
const uint layers = 2;
|
|
const ulong metadataSliceSize = 0x11000;
|
|
var metadataBytes = new byte[checked((int)(layers * metadataSliceSize))];
|
|
Array.Fill(metadataBytes, (byte)0xc0);
|
|
metadataBytes[^1] = 0x55;
|
|
harness.Write(metadataAddress, metadataBytes);
|
|
|
|
const uint width = 4;
|
|
const uint height = 4;
|
|
var texture = LinearRequest(
|
|
imageAddress,
|
|
(ulong)width * height * layers * 4,
|
|
Format.R8G8B8A8Unorm,
|
|
GuestPixelFormat.Bits8_8_8_8UNorm,
|
|
GuestImageType.Color2D,
|
|
new Extent3D(width, height, 1),
|
|
layers,
|
|
4,
|
|
1);
|
|
texture.Description.Metadata.Kind = MetadataKind.Dcc;
|
|
texture.Description.Metadata.Range = new GuestSpan(
|
|
metadataAddress,
|
|
layers * metadataSliceSize);
|
|
texture.Description.Metadata.DccAlphaMsb = true;
|
|
|
|
var textureId = harness.Find(ref texture);
|
|
harness.Finish();
|
|
|
|
Assert.True(textureId.IsValid);
|
|
Assert.Equal(uint.MaxValue, harness.ReadUInt32(metadataAddress));
|
|
Assert.Equal(0xc0c0c0c0u, harness.ReadUInt32(metadataAddress + metadataSliceSize));
|
|
Assert.Equal(0x55, harness.Read(metadataAddress + 2 * metadataSliceSize - 1, 1)[0]);
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void GuestDccMetadataFilledOnTheGpu_IsDownloadedWhenTheTargetIsSampled(bool storage)
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
const ulong SliceSize = 0x1000;
|
|
var address = harness.MapBacked(0x20000, ReadWrite);
|
|
var metadata = address + 0x10000;
|
|
var sampled = LinearRequest(address + 0xc000, 4, Format.R8G8B8A8Unorm, GuestPixelFormat.Bits8_8_8_8UNorm, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 4, 1);
|
|
var target = AsColorTarget(sampled);
|
|
var targetId = harness.Find(ref target);
|
|
var image = harness.Image(targetId);
|
|
image.Description.TileMode = GuestTileMode.RenderTarget;
|
|
image.Description.Metadata.Kind = MetadataKind.Dcc;
|
|
image.Description.Metadata.Range = new GuestSpan(metadata, SliceSize);
|
|
Assert.Equal(SliceSize, image.Description.DccSliceSize);
|
|
harness.Write(metadata, Enumerable.Repeat((byte)0x55, (int)SliceSize).ToArray());
|
|
harness.Worker.Run(() =>
|
|
{
|
|
Assert.NotNull(harness.Cache.ObtainBuffer(metadata, SliceSize, isWritten: true).Buffer);
|
|
harness.Cache.FillBuffer(metadata, SliceSize, 0, isGds: false);
|
|
});
|
|
Assert.True(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
|
Assert.False(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out _));
|
|
|
|
sampled.Description.TileMode = GuestTileMode.RenderTarget;
|
|
if (storage)
|
|
{
|
|
sampled = AsStorage(sampled);
|
|
}
|
|
|
|
Assert.Equal(targetId, harness.Find(ref sampled));
|
|
Assert.False(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
|
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out var code));
|
|
Assert.Equal(0x00, code);
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void DccDescriptor_SynchronizesMetadataOnAnImageFoundWithoutIt(bool native)
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
const ulong SliceSize = 0x1000;
|
|
var address = harness.MapBacked(0x20000, ReadWrite);
|
|
var metadata = address + 0x10000;
|
|
var plain = LinearRequest(address + 0xc000, 4, Format.R8G8B8A8Unorm, GuestPixelFormat.Bits8_8_8_8UNorm, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 4, 1);
|
|
var imageId = harness.Find(ref plain);
|
|
var image = harness.Image(imageId);
|
|
image.Description.TileMode = GuestTileMode.RenderTarget;
|
|
Assert.Equal(MetadataKind.None, image.Description.Metadata.Kind);
|
|
harness.Write(metadata, Enumerable.Repeat((byte)0x55, (int)SliceSize).ToArray());
|
|
harness.Worker.Run(() =>
|
|
{
|
|
Assert.NotNull(harness.Cache.ObtainBuffer(metadata, SliceSize, isWritten: true).Buffer);
|
|
harness.Cache.FillBuffer(metadata, SliceSize, 0, isGds: false);
|
|
});
|
|
Assert.True(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
|
|
|
var compressed = plain;
|
|
compressed.Description.TileMode = GuestTileMode.RenderTarget;
|
|
compressed.Description.Metadata.Kind = MetadataKind.Dcc;
|
|
compressed.Description.Metadata.Range = new GuestSpan(metadata, native ? SliceSize : 0);
|
|
Assert.Equal(imageId, harness.Find(ref compressed));
|
|
Assert.Equal(MetadataKind.Dcc, image.Description.Metadata.Kind);
|
|
Assert.Equal(metadata, image.Description.Metadata.Range.Address);
|
|
Assert.Equal(SliceSize, image.Description.DccSliceSize);
|
|
Assert.False(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
|
harness.Finish();
|
|
Assert.Equal(native ? uint.MaxValue : 0u, harness.ReadUInt32(metadata));
|
|
Assert.Equal(!native, harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out _));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void GuestDccClear_ReadsOnlyUniformClearSlices()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
const ulong SliceSize = 0x2000;
|
|
var metadata = harness.MapBacked(0x10000, ReadWrite);
|
|
harness.Write(metadata, Enumerable.Repeat((byte)0x80, (int)SliceSize).ToArray());
|
|
harness.Write(metadata + SliceSize, Enumerable.Repeat((byte)0x00, (int)SliceSize).ToArray());
|
|
var mixed = Enumerable.Repeat((byte)0x40, (int)SliceSize).ToArray();
|
|
mixed[^1] = 0xff;
|
|
harness.Write(metadata + 2 * SliceSize, mixed);
|
|
harness.Write(metadata + 3 * SliceSize, Enumerable.Repeat((byte)0xff, (int)SliceSize).ToArray());
|
|
|
|
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out var slice, out var code));
|
|
Assert.Equal(metadata, slice);
|
|
Assert.Equal(0x80, code);
|
|
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 1, out slice, out code));
|
|
Assert.Equal(metadata + SliceSize, slice);
|
|
Assert.Equal(0x00, code);
|
|
Assert.False(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 2, out _, out _));
|
|
Assert.False(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 3, out _, out _));
|
|
Assert.False(harness.Images.TryReadGuestDccClear(metadata, 0, 0, out _, out _));
|
|
|
|
harness.Worker.Run(() => harness.Cache.FillBuffer(metadata, SliceSize, uint.MaxValue, isGds: false));
|
|
Assert.False(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out _));
|
|
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 1, out _, out _));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void GpuDccFill_IsReadableWithoutSynchronization()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
const ulong SliceSize = 0x2000;
|
|
var memory = harness.MapBacked(0x20000, ReadWrite);
|
|
var metadata = memory + 0x4000;
|
|
var plain = memory + 0x10000;
|
|
harness.Write(metadata, Enumerable.Repeat((byte)0xff, (int)SliceSize).ToArray());
|
|
harness.Write(plain, Enumerable.Repeat((byte)0xff, (int)SliceSize).ToArray());
|
|
harness.Images.RegisterDccMetadataForTest(metadata, SliceSize);
|
|
harness.Worker.Run(() =>
|
|
{
|
|
_ = harness.Cache.ObtainBuffer(metadata, SliceSize, isWritten: true);
|
|
_ = harness.Cache.ObtainBuffer(plain, SliceSize, isWritten: true);
|
|
});
|
|
Assert.True(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
|
Assert.True(harness.Cache.HasGpuDirtyBytes(plain, SliceSize));
|
|
|
|
harness.Worker.Run(() =>
|
|
{
|
|
harness.Cache.FillBuffer(metadata, SliceSize, 0x00000000, isGds: false);
|
|
harness.Cache.FillBuffer(plain, SliceSize, 0x00000000, isGds: false);
|
|
});
|
|
|
|
Assert.False(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
|
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out var code));
|
|
Assert.Equal(0x00, code);
|
|
Assert.True(harness.Cache.HasGpuDirtyBytes(plain, SliceSize));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void CleanDccFill_WritesBothCopiesWithoutMarkingTheGpuCopyNewer()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
const ulong SliceSize = 0x2000;
|
|
var metadata = harness.MapBacked(0x10000, ReadWrite) + 0x4000;
|
|
harness.Write(metadata, Enumerable.Repeat((byte)0xff, (int)SliceSize).ToArray());
|
|
harness.Images.RegisterDccMetadataForTest(metadata, SliceSize);
|
|
_ = harness.Worker.Run(() => harness.Cache.ObtainBuffer(metadata, SliceSize, isWritten: false));
|
|
|
|
harness.Worker.Run(() => harness.Cache.FillDccMetadata(metadata, SliceSize, 0x20202020));
|
|
|
|
Assert.False(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
|
Assert.False(harness.Cache.HasGpuDirtyPages(metadata, SliceSize));
|
|
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out var code));
|
|
Assert.Equal(0x20, code);
|
|
harness.Finish();
|
|
var (after, afterOffset) = harness.Worker.Run(() => harness.Cache.ObtainBuffer(metadata, SliceSize, isWritten: false));
|
|
Assert.Equal(Enumerable.Repeat((byte)0x20, 16), harness.ReadBack(after, afterOffset + SliceSize - 16, 16));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void Unregister_DropsMetadataInTheRange()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x10000, ReadWrite);
|
|
harness.Images.RegisterHtileMetadataForTest(address + 0x1000);
|
|
harness.Images.RegisterHtileMetadataForTest(address + 0x3000);
|
|
harness.Worker.Run(() => harness.ImageStore.Unregister(address, 0x2000));
|
|
Assert.False(harness.Images.IsMetadata(address + 0x1000));
|
|
Assert.True(harness.Images.IsMetadata(address + 0x3000));
|
|
harness.Shutdown();
|
|
}
|
|
}
|