sharpemu/tests/SharpEmu.Libs.Tests/Gpu/Images/GuestImageCacheTests.Owners...

525 lines
26 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.HLE.GpuMemory;
using SharpEmu.HLE.Host;
using SharpEmu.Libs.Gpu.Buffers;
using SharpEmu.Libs.Gpu.Images;
using SharpEmu.Libs.Tests.Gpu.Buffers;
using Silk.NET.Vulkan;
using Xunit;
using static SharpEmu.Libs.Tests.Gpu.Images.ImageCacheTestSupport;
namespace SharpEmu.Libs.Tests.Gpu.Images;
// Ownership hand-overs between the guest, the buffer store and the image store.
public sealed partial class GuestImageCacheTests
{
[Fact]
public void BufferMirror_ReadsTheImageAndKeepsItsOwnership()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
harness.Write(address + 0x5000, Bytes(0xa1b2c3d4u));
var mirror = Color32(address + 0x5000);
var mirrorId = harness.Acquire(ref mirror);
harness.MarkGpuWritten(mirrorId);
Assert.True(harness.WriteFault(address + 0x5000));
harness.Write(address + 0x5000, Bytes(0x0f1e2d3cu));
var (mirrorBuffer, mirrorOffset) = harness.Worker.Run(() => harness.Cache.ObtainBuffer(address + 0x5000, 4, isWritten: false, isTexelBuffer: true));
Assert.False(harness.Image(mirrorId).IsBufferModified);
Assert.Equal(Bytes(0x0f1e2d3cu), harness.ReadBufferBytes(mirrorBuffer, mirrorOffset, 4));
var refresh = mirror;
var refreshId = harness.Acquire(ref refresh);
Assert.Equal(mirrorId, refreshId);
Assert.False(harness.Image(refreshId).IsBufferModified);
harness.MarkGpuWritten(refreshId);
harness.Write(address + 0x9000, Bytes(0x3f234567u));
var exact = Color32(address + 0x9000);
var exactId = harness.Acquire(ref exact);
harness.MarkGpuWritten(exactId);
var (exactBuffer, _) = harness.Worker.Run(() => harness.Cache.ObtainBuffer(address + 0x9000, 4, isWritten: false, isTexelBuffer: true));
Assert.NotNull(exactBuffer);
Assert.False(harness.Image(exactId).IsBufferModified);
Assert.True(harness.Image(exactId).IsGpuModified);
var exactFloat = exact;
exactFloat.Description.PixelFormat = Format.R32Sfloat;
exactFloat.Description.GuestFormat = GuestPixelFormat.Bits32Float;
exactFloat.View = exactFloat.View with { Format = Format.R32Sfloat };
var exactFloatId = harness.Acquire(ref exactFloat, exactFormat: true);
Assert.NotEqual(exactId, exactFloatId);
Assert.True(harness.Images.Contains(exactId));
Assert.True(harness.Image(exactId).IsGpuModified);
Assert.False(harness.Image(exactFloatId).IsBufferModified);
Assert.False(harness.Image(exactFloatId).IsGpuModified);
Assert.Equal(Bytes(0x3f234567u), harness.ReadImageBytes(harness.Image(exactFloatId)));
harness.Shutdown();
}
[Fact]
public void BufferWriteThenImageClear_TransfersOwnershipToTheImage()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
harness.Write(address + 0x6000, Bytes(0x01010101u));
var clear = LinearRequest(address + 0x6000, 4, Format.R8G8B8A8Unorm, GuestPixelFormat.Bits8_8_8_8UNorm, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 4, 1);
var clearId = harness.Find(ref clear);
harness.Worker.Run(() =>
{
var (buffer, _) = harness.Cache.ObtainBuffer(address + 0x6000, 4, isWritten: true, isTexelBuffer: true);
Assert.NotNull(buffer);
harness.Images.InvalidateMemoryFromGpu(address + 0x6000, 4);
});
Assert.True(harness.Image(clearId).IsBufferModified);
Assert.True(harness.Worker.Run(() => harness.Images.TryClearImageFromBuffer(address + 0x6000, 4, 0xaabbccddu)));
Assert.False(harness.Image(clearId).IsBufferModified);
Assert.True(harness.Image(clearId).IsGpuModified);
Assert.True(harness.Image(clearId).IsWatched);
var reread = clear;
Assert.Equal(clearId, harness.Find(ref reread));
Assert.Equal(new byte[] { 0xdd, 0xcc, 0xbb, 0xaa }, harness.ReadImageBytes(harness.Image(clearId)));
harness.Shutdown();
}
[Fact]
public void FullColorClear_SkipsSourceBufferAndRetainsWriteObservation()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
ImageClearBacking? backing = null;
using var harness = new CacheHarness(_vulkan, backing: memory => backing = new ImageClearBacking(memory));
var address = harness.MapBacked(0x10000, ReadWrite);
harness.Write(address, Bytes(0x01020304u));
var request = Color32(address);
var imageIdentifier = harness.Find(ref request);
var bufferCount = harness.Cache.BufferCount;
var readCount = backing!.ReadCount;
Assert.True(harness.Image(imageIdentifier).IsCpuDirty);
Assert.True(harness.Worker.Run(() => harness.Images.TryClearImageFromBuffer(address, 4, 0xaabbccddu)));
Assert.Equal(bufferCount, harness.Cache.BufferCount);
Assert.Equal(readCount, backing.ReadCount);
Assert.False(harness.Image(imageIdentifier).IsCpuDirty);
Assert.True(harness.Image(imageIdentifier).IsGpuModified);
Assert.True(harness.Image(imageIdentifier).IsWatched);
Assert.Equal(Bytes(0xaabbccddu), harness.ReadImageBytes(harness.Image(imageIdentifier)));
Assert.True(harness.WriteFault(address));
Assert.True(harness.Image(imageIdentifier).IsCpuDirty);
harness.Shutdown();
}
private sealed class ImageClearBacking(IGuestBackedSpace inner) : IGuestBackedSpace
{
public int ReadCount { get; private set; }
public bool TryHoldRange(ulong address, ulong size) => inner.TryHoldRange(address, size);
public bool TryHoldRangeAtOrAbove(ulong searchStart, ulong size, ulong alignment, out ulong address) =>
inner.TryHoldRangeAtOrAbove(searchStart, size, alignment, out address);
public bool TryMapBacked(ulong address, ulong size, ulong backingOffset, GuestPageProtection protection, out HostViewFailure failure) =>
inner.TryMapBacked(address, size, backingOffset, protection, out failure);
public bool TryUnmapBacked(ulong address, ulong size) => inner.TryUnmapBacked(address, size);
public bool TryClearBacking(ulong offset, ulong size) => inner.TryClearBacking(offset, size);
public bool IsBackedView(ulong address) => inner.IsBackedView(address);
public bool IsBackedRange(ulong address, ulong size) => inner.IsBackedRange(address, size);
public bool TryWriteBacking(ulong address, ReadOnlySpan<byte> data) => inner.TryWriteBacking(address, data);
public bool TryReadBacking(ulong address, Span<byte> data)
{
ReadCount++;
return inner.TryReadBacking(address, data);
}
}
[Theory]
[InlineData(Format.R16G16Sfloat, GuestPixelFormat.Bits16_16Float, 4u, 0x3c00bc00u)]
[InlineData(Format.R16G16B16A16Sfloat, GuestPixelFormat.Bits16_16_16_16Float, 8u, 0x3c00bc00u)]
[InlineData(Format.R16G16B16A16Sfloat, GuestPixelFormat.Bits16_16_16_16Float, 8u, 0x80000001u)]
[InlineData(Format.R32G32Uint, GuestPixelFormat.Bits32_32UInt, 8u, 0xdeadbeefu)]
[InlineData(Format.R32G32B32A32Uint, GuestPixelFormat.Bits32_32_32_32UInt, 16u, 0xdeadbeefu)]
public void FullColorBufferFillPreservesRawWords(Format format, GuestPixelFormat guestFormat, uint bytesPerTexel, uint word)
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
var request = LinearRequest(address, bytesPerTexel, format, guestFormat, GuestImageType.Color2D,
new Extent3D(1, 1, 1), 1, bytesPerTexel, 1);
var image = harness.Find(ref request);
Assert.True(harness.Worker.Run(() => harness.Images.TryClearImageFromBuffer(address, bytesPerTexel, word)));
Assert.Equal(Bytes(Enumerable.Repeat(word, (int)bytesPerTexel / 4).ToArray()), harness.ReadImageBytes(harness.Image(image)));
Assert.True(harness.Image(image).IsGpuModified);
harness.Shutdown();
}
[Fact]
public void HalfFloatNaNBufferFillFallsBackWithoutChangingImageOwnership()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
var request = LinearRequest(address, 8, Format.R16G16B16A16Sfloat, GuestPixelFormat.Bits16_16_16_16Float,
GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 8, 1);
var image = harness.Find(ref request);
Assert.True(harness.Image(image).IsCpuDirty);
Assert.False(harness.Worker.Run(() => harness.Images.TryClearImageFromBuffer(address, 8, 0x7e003c00u)));
Assert.True(harness.Image(image).IsCpuDirty);
Assert.False(harness.Image(image).IsGpuModified);
harness.Shutdown();
}
[Fact]
public void HalfFloatBufferFillStillRequiresAnExactRange()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
var request = LinearRequest(address, 16, Format.R16G16B16A16Sfloat, GuestPixelFormat.Bits16_16_16_16Float,
GuestImageType.Color2D, new Extent3D(2, 1, 1), 1, 8, 1);
var image = harness.Find(ref request);
Assert.False(harness.Worker.Run(() => harness.Images.TryClearImageFromBuffer(address + 8, 8, 0x3c00bc00u)));
Assert.False(harness.Worker.Run(() => harness.Images.TryClearImageFromBuffer(address, 20, 0x3c00bc00u)));
Assert.True(harness.Image(image).IsCpuDirty);
Assert.False(harness.Image(image).IsGpuModified);
harness.Shutdown();
}
[Fact]
public void PartialPage_BufferAndImageShareOneTrackerPage()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
var imageAddress = address + 0xa000;
var bufferAddress = address + 0xa010;
var cleanAddress = address + 0xa020;
harness.Write(cleanAddress, Bytes(0xabcdef01u));
harness.Worker.Run(() =>
{
var (buffer, _) = harness.Cache.ObtainBuffer(imageAddress, 4, isWritten: true);
Assert.NotNull(buffer);
harness.Cache.FillBuffer(imageAddress, 4, 0x31415926u, isGds: false);
harness.Cache.FillBuffer(bufferAddress, 4, 0x27182818u, isGds: false);
});
var partial = Color32(imageAddress);
var partialId = harness.Find(ref partial);
Assert.False(harness.Image(partialId).IsGpuModified);
harness.Worker.Run(() =>
{
var (mirror, _) = harness.Cache.ObtainBuffer(imageAddress, 4, isWritten: false, isTexelBuffer: true);
Assert.NotNull(mirror);
});
Assert.False(harness.Image(partialId).IsGpuModified);
harness.Worker.Run(() => Assert.NotNull(harness.Cache.ObtainBufferForImage(cleanAddress, 4).Buffer));
Assert.True(harness.ReadFault(bufferAddress));
Assert.Equal(0x31415926u, harness.ReadUInt32(imageAddress));
Assert.Equal(0x27182818u, harness.ReadUInt32(bufferAddress));
Assert.True(harness.WriteFault(cleanAddress));
harness.Write(cleanAddress, Bytes(0x13579bdfu));
var (source, sourceOffset) = harness.Worker.Run(() => harness.Cache.ObtainBufferForImage(cleanAddress, 4));
Assert.NotNull(source);
Assert.Equal(Bytes(0x13579bdfu), harness.ReadBufferBytes(source, sourceOffset, 4));
harness.Shutdown();
}
[Fact]
public void SamePageImages_WatchAndInvalidateIndependently()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
harness.Write(address + 0x8000, Bytes(0x01020304u));
harness.Write(address + 0x8010, Bytes(0x11121314u));
var first = Color32(address + 0x8000);
var second = Color32(address + 0x8010);
var firstId = harness.Acquire(ref first);
var secondId = harness.Acquire(ref second);
harness.MarkGpuWritten(firstId);
harness.MarkGpuWritten(secondId);
Assert.True(harness.Image(firstId).IsWatched);
Assert.True(harness.Image(secondId).IsWatched);
Assert.Equal(HostPageProtection.ReadOnly, harness.Protection(address + 0x8000));
Assert.Equal(0x01020304u, harness.ReadUInt32(address + 0x8000));
Assert.Equal(0x11121314u, harness.ReadUInt32(address + 0x8010));
Assert.True(harness.Image(firstId).IsGpuModified);
Assert.True(harness.Image(secondId).IsGpuModified);
Assert.True(harness.WriteFault(address + 0x8080));
Assert.True(harness.Image(firstId).IsGpuModified);
Assert.True(harness.Image(secondId).IsGpuModified);
Assert.False(harness.Image(firstId).IsWatched);
Assert.False(harness.Image(secondId).IsWatched);
Assert.True(harness.Image(firstId).IsMaybeCpuDirty);
Assert.True(harness.Image(secondId).IsMaybeCpuDirty);
Assert.Equal(HostPageProtection.ReadWrite, harness.Protection(address + 0x8000));
var retrackedFirst = harness.Acquire(ref first);
var retrackedSecond = harness.Acquire(ref second);
var (mirror, _) = harness.Worker.Run(() => harness.Cache.ObtainBuffer(address + 0x8000, 4, isWritten: false, isTexelBuffer: true));
Assert.Equal(firstId, retrackedFirst);
Assert.Equal(secondId, retrackedSecond);
Assert.NotNull(mirror);
Assert.True(harness.Image(firstId).IsWatched);
Assert.True(harness.Image(secondId).IsWatched);
Assert.False(harness.Image(firstId).IsCpuDirty);
Assert.False(harness.Image(secondId).IsCpuDirty);
Assert.True(harness.Image(firstId).IsGpuModified);
Assert.True(harness.Image(secondId).IsGpuModified);
Assert.True(harness.WriteFault(address + 0x8010));
Assert.True(harness.Image(secondId).IsGpuModified);
Assert.True(harness.Image(secondId).IsDefinitelyCpuDirty);
harness.Write(address + 0x8010, Bytes(0xa5a6a7a8u));
var refreshedSecond = harness.Acquire(ref second);
Assert.Equal(secondId, refreshedSecond);
Assert.False(harness.Image(secondId).IsDefinitelyCpuDirty);
Assert.True(harness.Image(secondId).IsGpuModified);
Assert.Equal(Bytes(0xa5a6a7a8u), harness.ReadImageBytes(harness.Image(secondId)));
harness.Shutdown();
}
[Fact]
public void ExactDisjointReplacement_PublishesWithoutDirtyingNeighbors()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x40000, ReadWrite);
var imageAddress = address + 0x26000;
var bufferAddress = address + 0x26010;
harness.Write(imageAddress, Bytes(0x31415926u));
var publish = Color32(imageAddress);
var publishId = harness.Acquire(ref publish);
harness.MarkGpuWritten(publishId);
harness.Worker.Run(() => harness.Cache.FillBuffer(bufferAddress, 4, 0x27182818u, isGds: false));
var replacement = publish;
replacement.Description.PixelFormat = Format.R32Sfloat;
replacement.Description.GuestFormat = GuestPixelFormat.Bits32Float;
replacement.View = replacement.View with { Format = Format.R32Sfloat };
var replacementId = harness.Find(ref replacement, exactFormat: true);
Assert.True(replacementId.IsValid);
Assert.NotEqual(publishId, replacementId);
Assert.False(harness.Cache.HasGpuDirtyBytes(bufferAddress, 4));
Assert.Equal(0x31415926u, harness.ReadUInt32(imageAddress));
Assert.Equal(0x27182818u, harness.ReadUInt32(bufferAddress));
harness.Shutdown();
}
[Fact]
public void StandaloneMultisampleTarget_KeepsWriteOnlyOwnership()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x20000, ReadWrite);
foreach (var (offset, samples) in new[] { (0x10000UL, 2u), (0x11000UL, 4u) })
{
harness.Write(address + offset, new byte[samples * 2]);
var request = LinearRequest(address + offset, samples * 2, Format.D16Unorm, GuestPixelFormat.Bits16UNorm, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 2, samples);
request = AsDepthTarget(request, Format.D16Unorm);
var imageIdentifier = harness.Acquire(ref request);
harness.MarkGpuWritten(imageIdentifier);
Assert.True(harness.Image(imageIdentifier).IsGpuModified);
Assert.Equal(0, BitConverter.ToUInt16(harness.Read(address + offset, 2)));
Assert.True(harness.Image(imageIdentifier).IsGpuModified);
Assert.True(harness.WriteFault(address + offset));
Assert.True(harness.Image(imageIdentifier).IsGpuModified);
Assert.True(harness.Image(imageIdentifier).IsDefinitelyCpuDirty);
}
harness.Shutdown();
}
[Fact]
public void UnformattedBufferAlias_KeepsImageOwnership()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
var request = Color32(address + 0x5000);
var imageIdentifier = harness.Acquire(ref request);
harness.MarkGpuWritten(imageIdentifier);
var (buffer, _) = harness.Worker.Run(() => harness.Cache.ObtainBuffer(address + 0x5000, 4, isWritten: false));
Assert.NotNull(buffer);
Assert.True(harness.Image(imageIdentifier).IsGpuModified);
Assert.False(harness.Image(imageIdentifier).IsBufferModified);
harness.Shutdown();
}
[Fact]
public void Replacement_WatchesTheWholeRangeOfTheNewImage()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x20000, ReadWrite);
var start = address + 0x18000;
harness.Write(start, new byte[0x2000]);
harness.Write(start, Bytes(0xcafebabeu));
harness.Write(start + 0x2000 - 4, Bytes(0x0badf00du));
var source = Color32(start);
var sourceId = harness.Acquire(ref source);
harness.MarkGpuWritten(sourceId);
var replacement = LinearRequest(start, 0x2000, Format.R32Uint, GuestPixelFormat.Bits32UInt, GuestImageType.Color2D, new Extent3D(1, 1, 1), 2, 4, 1);
var replacementId = harness.Find(ref replacement);
Assert.NotEqual(sourceId, replacementId);
Assert.True(harness.Image(replacementId).IsGpuModified);
Assert.True(harness.WriteFault(start + 0x1000));
Assert.True(harness.Image(replacementId).IsGpuModified);
Assert.True(harness.Image(replacementId).IsDefinitelyCpuDirty);
Assert.Equal(0xcafebabeu, harness.ReadUInt32(start));
Assert.Equal(0x0badf00du, harness.ReadUInt32(start + 0x2000 - 4));
harness.Shutdown();
}
[Fact]
public void MixedPageSource_UploadsBothOwnershipDomains()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x40000, ReadWrite);
var source = address + 0x20000;
const uint width = 1025;
const ulong size = 0x1004;
harness.Worker.Run(() => Assert.NotNull(harness.Cache.ObtainBuffer(source, size, isWritten: true).Buffer));
Assert.True(harness.WriteFault(source));
harness.Write(source, Bytes(0x1234abcdu));
harness.Worker.Run(() => harness.Cache.FillBuffer(source + 0x1000, 4, 0x9876fedcu, isGds: false));
var request = Color32(source, width);
var imageIdentifier = harness.Acquire(ref request);
Assert.False(harness.Image(imageIdentifier).IsGpuModified);
var bytes = harness.ReadImageBytes(harness.Image(imageIdentifier));
Assert.Equal(0x1234abcdu, BitConverter.ToUInt32(bytes, 0));
Assert.Equal(0x9876fedcu, BitConverter.ToUInt32(bytes, 0x1000));
harness.Shutdown();
}
[Fact]
public void BridgedBufferOwners_UploadBothDomains()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x200000, ReadWrite);
var gpuAddress = address + 0x101000;
var cpuAddress = address + 0x105000;
const ulong texel = 16;
var sourceSize = cpuAddress - gpuAddress + texel;
var width = (uint)(sourceSize / texel);
harness.Write(cpuAddress, Bytes(0x56473829u, 0x56473829u, 0x56473829u, 0x56473829u));
harness.Worker.Run(() =>
{
Assert.NotNull(harness.Cache.ObtainBuffer(cpuAddress, texel, isWritten: true).Buffer);
harness.Cache.FillBuffer(cpuAddress, texel, 0, isGds: false);
});
Assert.True(harness.WriteFault(cpuAddress));
harness.Write(cpuAddress, Bytes(0x56473829u, 0x56473829u, 0x56473829u, 0x56473829u));
harness.Write(gpuAddress, Bytes(0xa5a5a5a5u, 0xa5a5a5a5u, 0xa5a5a5a5u, 0xa5a5a5a5u));
var (cpuOwner, gpuOwner) = harness.Worker.Run(() =>
{
var (gpu, _) = harness.Cache.ObtainBuffer(gpuAddress, texel, isWritten: true);
harness.Cache.FillBuffer(gpuAddress, texel, 0x10293847u, isGds: false);
return (harness.Cache.ObtainBuffer(cpuAddress, texel, isWritten: false).Buffer, gpu);
});
Assert.NotSame(cpuOwner, gpuOwner);
Assert.True(harness.Cache.HasGpuDirtyBytes(gpuAddress, texel));
Assert.False(harness.Cache.HasGpuDirtyBytes(cpuAddress, texel));
var request = LinearRequest(gpuAddress, sourceSize, Format.R32G32B32A32Uint, GuestPixelFormat.Bits32_32_32_32UInt, GuestImageType.Color2D, new Extent3D(width, 1, 1), 1, (uint)texel, 1);
var imageIdentifier = harness.Acquire(ref request);
var bytes = harness.ReadImageBytes(harness.Image(imageIdentifier));
Assert.Equal(0x10293847u, BitConverter.ToUInt32(bytes, 0));
Assert.Equal(0x56473829u, BitConverter.ToUInt32(bytes, bytes.Length - 4));
harness.Shutdown();
}
[Fact]
public void ByteImageMirror_KeepsOwnership()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x40000, ReadWrite);
harness.Write(address + 0x24000, [0xac, 0x5a, 0xbd, 0xce]);
var request = LinearRequest(address + 0x24001, 1, Format.R8Unorm, GuestPixelFormat.Bits8UNorm, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 1, 1);
var imageIdentifier = harness.Acquire(ref request);
harness.MarkGpuWritten(imageIdentifier);
var (mirror, _) = harness.Worker.Run(() => harness.Cache.ObtainBuffer(address + 0x24001, 1, isWritten: false, isTexelBuffer: true));
Assert.NotNull(mirror);
Assert.False(harness.Image(imageIdentifier).IsBufferModified);
Assert.True(harness.Image(imageIdentifier).IsGpuModified);
harness.Shutdown();
}
[Fact]
public void Bgra16Target_UploadsTheSwappedHalves()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
harness.Write(address + 0xb000, Bytes((ushort)0x3c00, (ushort)0x4000, (ushort)0x4200, (ushort)0x4400));
var request = LinearRequest(address + 0xb000, 8, Format.R16G16B16A16Sfloat, GuestPixelFormat.Bits16_16_16_16Float, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 8, 1);
request = AsColorTarget(request);
request.Description.Bgra16 = true;
var imageIdentifier = harness.Acquire(ref request);
Assert.Equal(Bytes(0x40004200u, 0x44003c00u), harness.ReadImageBytes(harness.Image(imageIdentifier)));
harness.Shutdown();
}
[Fact]
public void SamePageDirtySibling_BlocksTheStaleImageSource()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x400000, ReadWrite);
var imageAddress = address + 0x334100;
var siblingAddress = address + 0x334200;
harness.Worker.Run(() =>
{
Assert.NotNull(harness.Cache.ObtainBuffer(siblingAddress, 4, isWritten: true).Buffer);
harness.Cache.FillBuffer(siblingAddress, 4, 0xc001d00du, isGds: false);
});
var exact = Color32(imageAddress);
var exactId = harness.Acquire(ref exact);
harness.MarkGpuWritten(exactId);
Assert.True(harness.Cache.HasGpuDirtyBytes(siblingAddress, 4));
Assert.False(harness.Cache.HasGpuDirtyBytes(imageAddress, 4));
Assert.True(harness.Image(exactId).IsGpuModified);
harness.Worker.Run(() =>
{
var (buffer, offset) = harness.Cache.ObtainBuffer(imageAddress, 4, isWritten: true);
buffer.Fill(offset, 4, 0xc001d00du);
using var download = new GpuBuffer(harness.Vulkan.DeviceInfo, harness.Scheduler, GpuBufferUsage.DeviceLocal, imageAddress, GpuBuffer.AllFlags, 4);
Assert.False(harness.Images.TrySynchronizeBufferFromImage(download, imageAddress, 4));
harness.Scheduler.Finish();
});
Assert.True(harness.Cache.TrySynchronizeCpuRead(imageAddress, 4));
Assert.True(harness.Cache.TrySynchronizeCpuRead(siblingAddress, 4));
harness.Shutdown();
}
[Fact]
public void PartialUnmap_DeletesTheImage()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
var request = LinearRequest(address, 0x2000, Format.R32Uint, GuestPixelFormat.Bits32UInt, GuestImageType.Color2D, new Extent3D(2048, 1, 1), 1, 4, 1);
var imageIdentifier = harness.Find(ref request);
Assert.True(imageIdentifier.IsValid);
harness.Worker.Run(() => harness.ImageStore.Unregister(address, 0x1000));
Assert.False(harness.Images.Contains(imageIdentifier));
Assert.Empty(harness.ImagesInRange(address, 0x2000));
harness.Shutdown();
}
}