109 lines
5.1 KiB
C#
109 lines
5.1 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using SharpEmu.HLE.GpuMemory;
|
|
using SharpEmu.Libs.Gpu.Buffers;
|
|
using SharpEmu.Libs.Gpu.Images;
|
|
using SharpEmu.Libs.Tests.Gpu.Buffers;
|
|
using SharpEmu.Libs.Tests.Gpu.Vulkan;
|
|
using Xunit;
|
|
using static SharpEmu.Libs.Tests.Gpu.Images.ImageCacheTestSupport;
|
|
|
|
namespace SharpEmu.Libs.Tests.Gpu.Images;
|
|
|
|
public sealed partial class GuestImageCacheTests
|
|
{
|
|
[Theory]
|
|
[InlineData(16)]
|
|
[InlineData(17)]
|
|
[InlineData(65)]
|
|
public void RangeQueriesKeepAllAliasesAndInvalidateAcrossOwnerPages(int count)
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x400000, ReadWrite);
|
|
var firstPage = (address + 0xfffff) & ~0xfffffUL;
|
|
var range = Ownership(firstPage + 0xff000, 0x2000);
|
|
var identifiers = new List<ResourceSlotIdentifier>();
|
|
for (var index = 0; index < count; index++) identifiers.Add(harness.Images.InsertImageForTest(range));
|
|
Assert.Equal(identifiers, harness.ImagesInRange(range.Data.Address, range.Data.Size));
|
|
harness.Images.QueryEpoch = uint.MaxValue;
|
|
Assert.Equal(identifiers, harness.ImagesInRange(range.Data.Address, range.Data.Size));
|
|
foreach (var identifier in identifiers) harness.Image(identifier).MarkGpuModified();
|
|
Assert.True(harness.Images.HasGpuModifiedImageBytes(range.Data.Address, range.Data.Size));
|
|
Assert.True(((IGuestImageStore)harness.Images).MarkCpuWrite(range.Data.Address, range.Data.Size));
|
|
foreach (var identifier in identifiers) Assert.True(harness.Image(identifier).IsCpuDirty);
|
|
if (count <= 16)
|
|
{
|
|
Assert.Equal(0, MeasureImageQueryAllocations(() =>
|
|
((IGuestImageStore)harness.Images).MarkCpuWrite(range.Data.Address, range.Data.Size)));
|
|
}
|
|
Assert.Equal(identifiers, harness.ImagesInRange(range.Data.Address, range.Data.Size));
|
|
foreach (var identifier in identifiers) harness.Image(identifier).ClearGpuModified();
|
|
((IGuestImageStore)harness.Images).Unregister(range.Data.Address, range.Data.Size);
|
|
Assert.Empty(harness.ImagesInRange(range.Data.Address, range.Data.Size));
|
|
harness.Shutdown();
|
|
}
|
|
|
|
[Fact]
|
|
public void DirtyImageQueryMatchesRegionQueryAndDoesNotAllocate()
|
|
{
|
|
if (!GatePrerequisites.Ready(_vulkan)) return;
|
|
using var harness = new CacheHarness(_vulkan);
|
|
var address = harness.MapBacked(0x10000, ReadWrite);
|
|
var owner = harness.Images.InsertImageForTest(Ownership(address + 0x100, 0x100));
|
|
var image = harness.Image(owner);
|
|
Assert.False(harness.Images.HasGpuModifiedImageBytes(address + 0x100, 4));
|
|
image.MarkGpuModified();
|
|
var stale = new ResourceSlotIdentifier(owner.Index, owner.Generation + 1);
|
|
harness.Images.AddPageOwner(address, stale);
|
|
foreach (var queryAddress in new[] { address, address + 0x100, address + 0x1fc, address + 0x200, address + 0x1000 })
|
|
Assert.Equal(harness.Images.QueryRegion(queryAddress, 4).GpuImageBytes,
|
|
harness.Images.HasGpuModifiedImageBytes(queryAddress, 4));
|
|
Assert.False(harness.Images.HasGpuModifiedImageBytes(address, 0));
|
|
Assert.False(harness.Images.HasGpuModifiedImageBytes(ulong.MaxValue, 4));
|
|
image.DepthOwner = owner;
|
|
Assert.False(harness.Images.HasGpuModifiedImageBytes(address + 0x100, 4));
|
|
image.DepthOwner = ResourceSlotIdentifier.Invalid;
|
|
Assert.Equal(0, MeasureImageQueryAllocations(Query));
|
|
Assert.True(harness.Images.RemovePageOwner(address, stale));
|
|
image.ClearGpuModified();
|
|
harness.Shutdown();
|
|
|
|
void Query()
|
|
{
|
|
_ = harness.Images.QueryRegion(address + 0x100, 4);
|
|
_ = harness.Images.QueryRegion(address + 0x2000, 4);
|
|
_ = harness.Images.HasGpuModifiedImageBytes(address + 0x100, 4);
|
|
_ = harness.Images.HasGpuModifiedImageBytes(address + 0x2000, 4);
|
|
_ = ((IGuestImageStore)harness.Images).MarkCpuWrite(address + 0x2000, 4);
|
|
}
|
|
}
|
|
|
|
// Use a dedicated thread so test-runner work cannot enter the allocation sample.
|
|
private static long MeasureImageQueryAllocations(Action query)
|
|
{
|
|
long allocated = -1;
|
|
Exception? failure = null;
|
|
var thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
for (var warmup = 0; warmup < 100; warmup++) query();
|
|
allocated = 0;
|
|
for (var iteration = 0; iteration < 1000; iteration++)
|
|
{
|
|
var before = GC.GetAllocatedBytesForCurrentThread();
|
|
query();
|
|
allocated += GC.GetAllocatedBytesForCurrentThread() - before;
|
|
}
|
|
}
|
|
catch (Exception exception) { failure = exception; }
|
|
}) { IsBackground = true };
|
|
thread.Start();
|
|
Assert.True(thread.Join(TimeSpan.FromSeconds(30)));
|
|
if (failure is not null) System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(failure).Throw();
|
|
return allocated;
|
|
}
|
|
}
|