Merge #944 (pr/gpu-memory-allocations)
This commit is contained in:
commit
446ea699ed
|
|
@ -114,13 +114,24 @@ public sealed class GuestPageTracker
|
|||
}
|
||||
|
||||
// Removes protection from a range; a GPU-dirty region flushes through onFlush without the lock.
|
||||
public bool InvalidateRegion(ulong vaddr, ulong size, Action onFlush)
|
||||
public bool InvalidateRegion(ulong vaddr, ulong size, Action onFlush) =>
|
||||
InvalidateRegion(vaddr, size, onFlush, static flush =>
|
||||
{
|
||||
flush();
|
||||
return true;
|
||||
}, out _);
|
||||
|
||||
// Allocation-free form for the CPU-write path, which runs on every tracked guest write: the flush
|
||||
// takes its state as an argument, and flushesSucceeded is false when any flush returned false.
|
||||
public bool InvalidateRegion<TState>(ulong vaddr, ulong size, TState state, Func<TState, bool> onFlush,
|
||||
out bool flushesSucceeded)
|
||||
{
|
||||
RejectUploadCallbackReentry();
|
||||
var tracked = false;
|
||||
VisitRegions(vaddr, size, create: false, (region, offset, bytes) =>
|
||||
var visit = new InvalidateVisit<TState>(state, onFlush);
|
||||
VisitRegions(vaddr, size, create: false, ref visit, static (TrackedRegion region, ulong offset, ulong bytes,
|
||||
ref InvalidateVisit<TState> visit) =>
|
||||
{
|
||||
tracked = true;
|
||||
visit.Tracked = true;
|
||||
bool shouldFlush;
|
||||
using (region.Lock.Hold())
|
||||
{
|
||||
|
|
@ -133,12 +144,21 @@ public sealed class GuestPageTracker
|
|||
|
||||
if (shouldFlush)
|
||||
{
|
||||
onFlush();
|
||||
visit.FlushesSucceeded &= visit.OnFlush(visit.State);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return tracked;
|
||||
flushesSucceeded = visit.FlushesSucceeded;
|
||||
return visit.Tracked;
|
||||
}
|
||||
|
||||
private struct InvalidateVisit<TState>(TState state, Func<TState, bool> onFlush)
|
||||
{
|
||||
public readonly TState State = state;
|
||||
public readonly Func<TState, bool> OnFlush = onFlush;
|
||||
public bool Tracked;
|
||||
public bool FlushesSucceeded = true;
|
||||
}
|
||||
|
||||
public void ForEachDownloadRange(ulong vaddr, ulong size, bool clear, Action<ulong, ulong>? preflight, Action<ulong, ulong> visit)
|
||||
|
|
@ -365,7 +385,13 @@ public sealed class GuestPageTracker
|
|||
}
|
||||
|
||||
// Visits (region, offset, bytes) per 4 MiB chunk; a true result stops the walk early.
|
||||
private bool VisitRegions(ulong vaddr, ulong size, bool create, Func<TrackedRegion, ulong, ulong, bool> visit)
|
||||
private delegate bool RegionVisitor<TState>(TrackedRegion region, ulong offset, ulong bytes, ref TState state);
|
||||
|
||||
private bool VisitRegions(ulong vaddr, ulong size, bool create, Func<TrackedRegion, ulong, ulong, bool> visit) =>
|
||||
VisitRegions(vaddr, size, create, ref visit, static (TrackedRegion region, ulong offset, ulong bytes,
|
||||
ref Func<TrackedRegion, ulong, ulong, bool> visit) => visit(region, offset, bytes));
|
||||
|
||||
private bool VisitRegions<TState>(ulong vaddr, ulong size, bool create, ref TState state, RegionVisitor<TState> visit)
|
||||
{
|
||||
ValidateRange(vaddr, size);
|
||||
var remaining = size;
|
||||
|
|
@ -380,7 +406,7 @@ public sealed class GuestPageTracker
|
|||
region = GetOrCreateRegion(index);
|
||||
}
|
||||
|
||||
if (region != null && visit(region, offset, bytes))
|
||||
if (region != null && visit(region, offset, bytes, ref state))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,10 +126,10 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable
|
|||
// A CPU write fault: true when the range is tracked and any GPU data reached guest memory.
|
||||
bool IGuestBufferStore.MarkCpuWrite(ulong address, ulong size)
|
||||
{
|
||||
var completed = true;
|
||||
var tracked = _tracker.InvalidateRegion(address, size,
|
||||
() => completed &= ReadMemoryOrAwaitShutdown(address, size, isWrite: true,
|
||||
GuestMemoryProfile.ReadbackSource.CpuWriteInvalidation));
|
||||
var tracked = _tracker.InvalidateRegion(address, size, (Cache: this, Address: address, Size: size),
|
||||
static write => write.Cache.ReadMemoryOrAwaitShutdown(write.Address, write.Size, isWrite: true,
|
||||
GuestMemoryProfile.ReadbackSource.CpuWriteInvalidation),
|
||||
out var completed);
|
||||
if (GuestGpuMemoryHook.Traces(address, size))
|
||||
GuestGpuMemoryHook.Trace(address, size, $"buffer-write tracked={tracked} completed={completed}");
|
||||
return tracked && completed;
|
||||
|
|
@ -325,8 +325,11 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable
|
|||
throw SubmissionScheduler.Fatal($"Could not write the required direct backing: addr=0x{guestAddress:X16} size=0x{data.Length:X16}");
|
||||
}
|
||||
|
||||
// Registered buffers are ordered and disjoint: walk only the ones the write overlaps.
|
||||
var end = guestAddress + (ulong)data.Length;
|
||||
for (var index = 0; index < _registry.RegisteredCount; index++)
|
||||
for (var index = _registry.FindFirstOverlappingIndex(guestAddress);
|
||||
index < _registry.RegisteredCount && _registry.GetRegisteredAddress(index) < end;
|
||||
index++)
|
||||
{
|
||||
var address = _registry.GetRegisteredAddress(index);
|
||||
var bufferIdentifier = _registry.GetRegisteredIdentifier(index);
|
||||
|
|
@ -376,9 +379,10 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable
|
|||
images.InvalidateMemory(guestAddress, size);
|
||||
}
|
||||
|
||||
var values = new uint[4096];
|
||||
Array.Fill(values, value);
|
||||
var bytes = MemoryMarshal.AsBytes<uint>(values);
|
||||
// Labels and small clears dominate; fill a stack chunk once and repeat it.
|
||||
Span<uint> values = stackalloc uint[(int)Math.Min(size / sizeof(uint), 1024UL)];
|
||||
values.Fill(value);
|
||||
var bytes = MemoryMarshal.AsBytes(values);
|
||||
for (ulong offset = 0; offset < size;)
|
||||
{
|
||||
var chunk = (int)Math.Min(size - offset, (ulong)bytes.Length);
|
||||
|
|
@ -420,17 +424,24 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable
|
|||
images.InvalidateMemory(dstVaddr, size);
|
||||
}
|
||||
|
||||
var bytes = new byte[64 * 1024];
|
||||
for (ulong offset = 0; offset < size;)
|
||||
var bytes = System.Buffers.ArrayPool<byte>.Shared.Rent((int)Math.Min(size, 64UL * 1024));
|
||||
try
|
||||
{
|
||||
var chunk = (int)Math.Min(size - offset, (ulong)bytes.Length);
|
||||
if (!_backing.TryReadBacking(srcVaddr + offset, bytes.AsSpan(0, chunk)))
|
||||
for (ulong offset = 0; offset < size;)
|
||||
{
|
||||
throw SubmissionScheduler.Fatal("The host DMA source has no direct backing.");
|
||||
}
|
||||
var chunk = (int)Math.Min(size - offset, (ulong)bytes.Length);
|
||||
if (!_backing.TryReadBacking(srcVaddr + offset, bytes.AsSpan(0, chunk)))
|
||||
{
|
||||
throw SubmissionScheduler.Fatal("The host DMA source has no direct backing.");
|
||||
}
|
||||
|
||||
WriteHostMemory(dstVaddr + offset, bytes.AsSpan(0, chunk));
|
||||
offset += (ulong)chunk;
|
||||
WriteHostMemory(dstVaddr + offset, bytes.AsSpan(0, chunk));
|
||||
offset += (ulong)chunk;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Buffers.ArrayPool<byte>.Shared.Return(bytes);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO.Hashing;
|
||||
using SharpEmu.HLE;
|
||||
|
|
@ -48,6 +49,9 @@ public static class ShaderIdentity
|
|||
}
|
||||
|
||||
// The declared hash when it is not zero, else the content hash of every code range.
|
||||
[ThreadStatic]
|
||||
private static XxHash3? _hash;
|
||||
|
||||
public static ulong Compute(ICpuMemory memory, ulong codeAddress, ReadOnlySpan<(ulong Address, uint SizeBytes)> ranges, string label)
|
||||
{
|
||||
if (!TryReadDeclaredHash(memory, codeAddress, out var declaredHash))
|
||||
|
|
@ -60,16 +64,26 @@ public static class ShaderIdentity
|
|||
return declaredHash;
|
||||
}
|
||||
|
||||
var hash = new XxHash3();
|
||||
// Runs per draw for shaders without a declared hash: the hasher and code buffer are reused.
|
||||
var hash = _hash ??= new XxHash3();
|
||||
hash.Reset();
|
||||
foreach (var (address, sizeBytes) in ranges)
|
||||
{
|
||||
var code = new byte[sizeBytes];
|
||||
if (!memory.TryRead(address, code))
|
||||
var code = ArrayPool<byte>.Shared.Rent((int)sizeBytes);
|
||||
try
|
||||
{
|
||||
throw Scheduling.SubmissionScheduler.Fatal($"The shader code is unreadable: label={label} shader=0x{address:X16} size=0x{sizeBytes:X8}.");
|
||||
}
|
||||
var span = code.AsSpan(0, (int)sizeBytes);
|
||||
if (!memory.TryRead(address, span))
|
||||
{
|
||||
throw Scheduling.SubmissionScheduler.Fatal($"The shader code is unreadable: label={label} shader=0x{address:X16} size=0x{sizeBytes:X8}.");
|
||||
}
|
||||
|
||||
hash.Append(code);
|
||||
hash.Append(span);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(code);
|
||||
}
|
||||
}
|
||||
|
||||
return hash.GetCurrentHashAsUInt64();
|
||||
|
|
|
|||
Loading…
Reference in New Issue