[GPU] Avoid command-worker readbacks for clean bytes on GPU-dirty pages (#1059)
* [GPU] Read clean command bytes without faulting on neighbouring GPU data * [Tests] Enable portable Vulkan devices and release fixture slabs
This commit is contained in:
parent
a9356eb8d2
commit
8b631d3b03
|
|
@ -328,6 +328,18 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable
|
|||
return (owner, owner.Offset(guestAddress));
|
||||
}
|
||||
|
||||
// The command worker can read the stable backing alias without changing CPU page
|
||||
// permissions. A GPU counter elsewhere on the same page is not a dependency of
|
||||
// the packet bytes being decoded. Actual GPU-written bytes retain the slow path.
|
||||
private static readonly bool CommandBackingReadsEnabled =
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_COMMAND_BACKING_READS") != "0";
|
||||
|
||||
public bool TryReadCommandBacking(ulong address, Span<byte> destination) =>
|
||||
CommandBackingReadsEnabled && destination.Length != 0 &&
|
||||
!_gpuModifiedRanges.Overlaps(address, (ulong)destination.Length) &&
|
||||
ImageCache is { } images && !images.HasGpuModifiedImageBytes(address, (ulong)destination.Length) &&
|
||||
_backing.TryReadBacking(address, destination);
|
||||
|
||||
public void WriteHostMemory(ulong guestAddress, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (guestAddress == 0 || data.IsEmpty || (ulong)data.Length > ulong.MaxValue - guestAddress)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ public interface IGuestImageCache
|
|||
{
|
||||
ImageRegionInfo QueryRegion(ulong address, ulong size);
|
||||
|
||||
bool HasGpuModifiedImageBytes(ulong address, ulong size) => QueryRegion(address, size).GpuImageBytes;
|
||||
|
||||
bool ClearMetadata(ulong address);
|
||||
|
||||
bool OverlapsDccMetadata(ulong address, ulong size);
|
||||
|
|
|
|||
|
|
@ -290,6 +290,7 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
|
||||
public bool TryReadGuest(ulong address, Span<byte> destination)
|
||||
{
|
||||
if (_bufferCache.TryReadCommandBacking(address, destination)) return true;
|
||||
using (RenderPhaseProfile.MeasureDetail(RenderPhaseProfile.Phase.CommandMemorySync))
|
||||
{
|
||||
if (!_bufferCache.TrySynchronizeCpuRead(address, (ulong)destination.Length,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,53 @@ public sealed class GuestBufferCacheTests : IClassFixture<HeadlessVulkanFixture>
|
|||
{
|
||||
private const ulong Page = GuestBufferCache.CachingPageSize;
|
||||
|
||||
[Fact]
|
||||
public void CommandBackingReadRejectsGpuImageBytesButAllowsCleanPageNeighbours()
|
||||
{
|
||||
if (!GatePrerequisites.Ready(_vulkan)) return;
|
||||
using var harness = new CacheHarness(_vulkan!);
|
||||
var address = harness.MapBacked(Page, ReadWrite);
|
||||
harness.Write(address + 64, new byte[] { 1, 2, 3, 4 });
|
||||
var request = Color32(address, 4);
|
||||
var id = harness.Acquire(ref request);
|
||||
harness.Worker.Run(() =>
|
||||
{
|
||||
Assert.True(harness.Images.TryClearImageFromBuffer(address, 16, 0x22222222));
|
||||
Assert.True(harness.Image(id).IsGpuModified);
|
||||
var tick = harness.Scheduler.CurrentTick;
|
||||
Span<byte> bytes = stackalloc byte[4];
|
||||
Assert.False(harness.Cache.TryReadCommandBacking(address, bytes));
|
||||
Assert.True(harness.Cache.TryReadCommandBacking(address + 64, bytes));
|
||||
Assert.Equal(new byte[] { 1, 2, 3, 4 }, bytes.ToArray());
|
||||
Assert.Equal(tick, harness.Scheduler.CurrentTick);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommandBytesNextToPendingGpuCounterDoNotWaitOrUnprotectThePage()
|
||||
{
|
||||
if (!GatePrerequisites.Ready(_vulkan)) return;
|
||||
using var harness = new CacheHarness(_vulkan!);
|
||||
var address = harness.MapBacked(Page, ReadWrite);
|
||||
harness.Write(address + 16, new byte[] { 1, 2, 3, 4 });
|
||||
harness.Worker.Run(() =>
|
||||
{
|
||||
var (buffer, offset) = harness.Cache.ObtainBuffer(address, 4, isWritten: true);
|
||||
buffer.Fill(offset, 4, 123);
|
||||
var tick = harness.Scheduler.CurrentTick;
|
||||
var protection = harness.Protection(address);
|
||||
Span<byte> bytes = stackalloc byte[4];
|
||||
Assert.True(harness.Cache.TryReadCommandBacking(address + 16, bytes));
|
||||
Assert.Equal(new byte[] { 1, 2, 3, 4 }, bytes.ToArray());
|
||||
Assert.False(harness.Cache.TryReadCommandBacking(address, bytes));
|
||||
Assert.False(harness.Cache.TryReadCommandBacking(address - 2, bytes));
|
||||
Assert.Equal(tick, harness.Scheduler.CurrentTick);
|
||||
Assert.Equal(protection, harness.Protection(address));
|
||||
Assert.True(harness.Cache.HasGpuDirtyPages(address, 4));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void UnalignedImageObtainUploadsTheWholeDirtyPageToItsBufferOwner()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ internal sealed unsafe class HeadlessVulkan : IDisposable
|
|||
public void Dispose()
|
||||
{
|
||||
Vk.DeviceWaitIdle(Device);
|
||||
_deviceInfo?.Slabs.Destroy();
|
||||
Vk.DestroyDevice(Device, null);
|
||||
if (_debugUtils is { } debugUtils)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue