diff --git a/src/SharpEmu.Libs/Gpu/Buffers/GpuBuffer.cs b/src/SharpEmu.Libs/Gpu/Buffers/GpuBuffer.cs index fec9cf7..6250279 100644 --- a/src/SharpEmu.Libs/Gpu/Buffers/GpuBuffer.cs +++ b/src/SharpEmu.Libs/Gpu/Buffers/GpuBuffer.cs @@ -172,16 +172,39 @@ public unsafe class GpuBuffer : IDisposable // A readback of it only has to wait for that tick, not for all queued work. public ulong LastGpuWriteTick { get; private set; } + private readonly WriteTickMap _writeTicks = new(); + private int _writeTickPruneCount = 64; + // Called by every path that records a GPU write into this buffer. - public void NoteGpuWrite() + public void NoteGpuWrite() => NoteGpuWrite(0, Size); + + public void NoteGpuWrite(ulong offset, ulong size) { var tick = _scheduler.CurrentTick; if (tick > LastGpuWriteTick) { LastGpuWriteTick = tick; } + + if (size == 0 || offset >= Size) + { + return; + } + + _writeTicks.Assign(offset, Math.Min(size, Size - offset), tick); + if (_writeTicks.Count >= _writeTickPruneCount) + { + if (Volatile.Read(ref _foreignReads) == 0) + { + _writeTicks.Prune(_scheduler.Timeline.CompletedTick + 1); + } + + _writeTickPruneCount = Math.Max(64, _writeTicks.Count * 2); + } } + public ulong LastGpuWriteTickIn(ulong offset, ulong size) => _writeTicks.MaxTick(offset, size); + public void Write(ulong offset, ReadOnlySpan source) { if (_mapped == null || offset > Size || (ulong)source.Length > Size - offset) @@ -243,7 +266,7 @@ public unsafe class GpuBuffer : IDisposable } command.EndRendering(); - NoteGpuWrite(); + NoteGpuWrite(destinationOffset, size); var vk = _device.Vk; var native = new CommandBuffer(command.Handle); var before = stackalloc BufferMemoryBarrier2[2]; @@ -271,7 +294,7 @@ public unsafe class GpuBuffer : IDisposable var command = _scheduler.Current; command.EndRendering(); - NoteGpuWrite(); + NoteGpuWrite(offset, size); var vk = _device.Vk; var native = new CommandBuffer(command.Handle); var before = CreateBarrier(offset, size, MemoryAccess, AccessFlags.TransferWriteBit); diff --git a/src/SharpEmu.Libs/Gpu/Buffers/WriteTickMap.cs b/src/SharpEmu.Libs/Gpu/Buffers/WriteTickMap.cs new file mode 100644 index 0000000..66d8d5d --- /dev/null +++ b/src/SharpEmu.Libs/Gpu/Buffers/WriteTickMap.cs @@ -0,0 +1,143 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.Libs.Gpu.Buffers; + +public sealed class WriteTickMap +{ + private readonly List _entries = []; + private ulong _floor; + + private readonly record struct Entry(ulong Begin, ulong End, ulong Tick); + + public int Count => _entries.Count; + + public ulong Floor => _floor; + + public void Assign(ulong begin, ulong size, ulong tick) + { + if (size == 0) + { + return; + } + + var end = begin + size; + var index = FirstEndingAfter(begin); + var removed = 0; + while (index + removed < _entries.Count && _entries[index + removed].Begin < end) + { + removed++; + } + + Entry? left = null; + Entry? right = null; + if (removed != 0) + { + var first = _entries[index]; + if (first.Begin < begin) + { + left = first with { End = begin }; + } + + var last = _entries[index + removed - 1]; + if (last.End > end) + { + right = last with { Begin = end }; + } + + _entries.RemoveRange(index, removed); + } + + var insert = index; + if (left is { } leftPiece) + { + _entries.Insert(insert++, leftPiece); + } + + _entries.Insert(insert, new Entry(begin, end, tick)); + var middle = insert++; + if (right is { } rightPiece) + { + _entries.Insert(insert, rightPiece); + } + + if (middle + 1 < _entries.Count) + { + _ = TryMerge(middle, middle + 1); + } + + if (middle > 0) + { + _ = TryMerge(middle - 1, middle); + } + } + + public ulong MaxTick(ulong begin, ulong size) + { + var tick = _floor; + if (size == 0) + { + return tick; + } + + var end = begin + size; + for (var index = FirstEndingAfter(begin); index < _entries.Count && _entries[index].Begin < end; index++) + { + tick = Math.Max(tick, _entries[index].Tick); + } + + return tick; + } + + public void Prune(ulong below) + { + var kept = 0; + for (var index = 0; index < _entries.Count; index++) + { + var entry = _entries[index]; + if (entry.Tick < below) + { + _floor = Math.Max(_floor, entry.Tick); + continue; + } + + _entries[kept++] = entry; + } + + _entries.RemoveRange(kept, _entries.Count - kept); + } + + private bool TryMerge(int left, int right) + { + var a = _entries[left]; + var b = _entries[right]; + if (a.End != b.Begin || a.Tick != b.Tick) + { + return false; + } + + _entries[left] = a with { End = b.End }; + _entries.RemoveAt(right); + return true; + } + + private int FirstEndingAfter(ulong address) + { + var low = 0; + var high = _entries.Count; + while (low < high) + { + var middle = low + ((high - low) >> 1); + if (_entries[middle].End <= address) + { + low = middle + 1; + } + else + { + high = middle; + } + } + + return low; + } +}