[VideoOut] Read resource words through cached clean pages and the backing alias
TryReadGuestWord and TryReadResidentGuestBytes remember the last 64 pages found free of GPU-dirty bytes, keyed by the GPU-modified version, and read them through the backing alias inside the alias access window that each command stream slice now holds. GPU-dirty checks are byte-precise, and a resident read refuses unreadable ranges before it asks the page tracker.
This commit is contained in:
parent
afa7018e13
commit
46a9dd223d
|
|
@ -196,7 +196,20 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
SliceResult result;
|
||||
using (RenderPhaseProfile.Measure(RenderPhaseProfile.Phase.CommandStream))
|
||||
{
|
||||
result = _commandStream.ProcessOne();
|
||||
var aliasAccess = _guestBacking?.TryEnterBackingAliasAccess() == true;
|
||||
_backingAliasAccess = aliasAccess;
|
||||
try
|
||||
{
|
||||
result = _commandStream.ProcessOne();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_backingAliasAccess = false;
|
||||
if (aliasAccess)
|
||||
{
|
||||
_guestBacking!.ExitBackingAliasAccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (result)
|
||||
|
|
|
|||
|
|
@ -99,20 +99,43 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
{
|
||||
using var profile = ResourceMaterializationProfile.Measure(ResourceMaterializationProfile.Phase.GuestRead);
|
||||
word = 0;
|
||||
// Resource planning can inspect a dynamic descriptor before the draw has
|
||||
// supplied a valid guest address. Do not pass an invalid range to the
|
||||
// page tracker: it treats that as an emulator invariant violation and
|
||||
// terminates the process. A failed read lets the materializer reject or
|
||||
// specialize the source normally.
|
||||
if (!_guestMemory.CanRead(address, sizeof(uint)))
|
||||
if (IsCleanReadPage(address, sizeof(uint)))
|
||||
{
|
||||
return false;
|
||||
if (TryGetAliasPointer(address, sizeof(uint), out var alias))
|
||||
{
|
||||
word = System.Runtime.CompilerServices.Unsafe.ReadUnaligned<uint>(alias);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_bufferCache.TrySynchronizeCpuRead(address, sizeof(uint),
|
||||
SharpEmu.HLE.GuestMemory.GuestMemoryProfile.ReadbackSource.ShaderResourceRead))
|
||||
else
|
||||
{
|
||||
return false;
|
||||
// Resource planning can inspect a dynamic descriptor before the draw has
|
||||
// supplied a valid guest address. Do not pass an invalid range to the
|
||||
// page tracker: it treats that as an emulator invariant violation and
|
||||
// terminates the process. A failed read lets the materializer reject or
|
||||
// specialize the source normally.
|
||||
if (!_guestMemory.CanRead(address, sizeof(uint)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_bufferCache.HasGpuDirtyBytes(address, sizeof(uint)))
|
||||
{
|
||||
if (Diagnostics.GpuReadTrace.Enabled)
|
||||
{
|
||||
Diagnostics.GpuReadTrace.Record(address, ResourceMaterializationCache.ReadingTable);
|
||||
}
|
||||
|
||||
if (!_bufferCache.TrySynchronizeCpuRead(address, sizeof(uint),
|
||||
SharpEmu.HLE.GuestMemory.GuestMemoryProfile.ReadbackSource.ShaderResourceRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NoteCleanReadPage(address, sizeof(uint));
|
||||
}
|
||||
}
|
||||
|
||||
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
||||
|
|
@ -156,13 +179,100 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
public bool TryReadResidentGuestBytes(ulong address, Span<byte> destination, bool clean)
|
||||
{
|
||||
var size = (ulong)destination.Length;
|
||||
if (_bufferCache.HasGpuDirtyPages(address, size) ||
|
||||
(clean && (_bufferCache.HasGpuDirtyBytes(address, size) || _imageCache.HasGpuModifiedImageBytes(address, size))))
|
||||
if (clean || !IsCleanReadPage(address, size))
|
||||
{
|
||||
if (!_guestMemory.CanRead(address, size) ||
|
||||
_bufferCache.HasGpuDirtyBytes(address, size) ||
|
||||
(clean && (_bufferCache.HasGpuDirtyPages(address, size) || _imageCache.HasGpuModifiedImageBytes(address, size))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
NoteCleanReadPage(address, size);
|
||||
}
|
||||
|
||||
if (TryGetAliasPointer(address, size, out var alias))
|
||||
{
|
||||
new ReadOnlySpan<byte>(alias, destination.Length).CopyTo(destination);
|
||||
return true;
|
||||
}
|
||||
|
||||
return _guestMemory.TryRead(address, destination);
|
||||
}
|
||||
|
||||
private const ulong CleanReadPageBytes = 0x1000;
|
||||
private const int CleanReadPageSlots = 64;
|
||||
private CleanReadPages? _cleanReadPages;
|
||||
private bool _backingAliasAccess;
|
||||
|
||||
private sealed class CleanReadPages
|
||||
{
|
||||
public readonly ulong[] Tags = new ulong[CleanReadPageSlots];
|
||||
public readonly long[] Versions = new long[CleanReadPageSlots];
|
||||
public readonly ulong[] Aliases = new ulong[CleanReadPageSlots];
|
||||
public readonly object?[] Snapshots = new object?[CleanReadPageSlots];
|
||||
}
|
||||
|
||||
private bool TryGetAliasPointer(ulong address, ulong size, out byte* pointer)
|
||||
{
|
||||
pointer = null;
|
||||
if (!_backingAliasAccess || _cleanReadPages is not { } pages ||
|
||||
!TryGetCleanReadPage(address, size, out var page, out var slot) || pages.Tags[slot] != page + 1 ||
|
||||
_guestBacking.BackingAliasSnapshot is not { } snapshot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _guestMemory.TryRead(address, destination);
|
||||
if (pages.Aliases[slot] == 0 || !ReferenceEquals(pages.Snapshots[slot], snapshot))
|
||||
{
|
||||
if (!_guestBacking.TryResolveBackingAlias(page, CleanReadPageBytes, out var resolved) || resolved == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pages.Aliases[slot] = resolved;
|
||||
pages.Snapshots[slot] = snapshot;
|
||||
}
|
||||
|
||||
pointer = (byte*)(pages.Aliases[slot] + (address - page));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetCleanReadPage(ulong address, ulong size, out ulong page, out int slot)
|
||||
{
|
||||
page = address & ~(CleanReadPageBytes - 1);
|
||||
slot = (int)((page / CleanReadPageBytes) & (CleanReadPageSlots - 1));
|
||||
return size != 0 && address + size > address && ((address + size - 1) & ~(CleanReadPageBytes - 1)) == page;
|
||||
}
|
||||
|
||||
private bool IsCleanReadPage(ulong address, ulong size) =>
|
||||
_cleanReadPages is { } pages &&
|
||||
TryGetCleanReadPage(address, size, out var page, out var slot) &&
|
||||
pages.Tags[slot] == page + 1 &&
|
||||
pages.Versions[slot] == _bufferCache.GpuModifiedVersion;
|
||||
|
||||
private void NoteCleanReadPage(ulong address, ulong size)
|
||||
{
|
||||
if (!TryGetCleanReadPage(address, size, out var page, out var slot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var version = _bufferCache.GpuModifiedVersion;
|
||||
if (!_guestMemory.CanRead(page, CleanReadPageBytes) || _bufferCache.HasGpuDirtyBytes(page, CleanReadPageBytes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pages = _cleanReadPages ??= new CleanReadPages();
|
||||
if (pages.Tags[slot] != page + 1)
|
||||
{
|
||||
pages.Tags[slot] = page + 1;
|
||||
pages.Aliases[slot] = 0;
|
||||
pages.Snapshots[slot] = null;
|
||||
}
|
||||
|
||||
pages.Versions[slot] = version;
|
||||
}
|
||||
|
||||
public ulong CreateShaderModule(IGuestCompiledShader shader, ShaderStage stage, ulong hash, ulong programId)
|
||||
|
|
|
|||
Loading…
Reference in New Issue