Merge pull request 'perf(device): pop retired deletions off the front in O(ready) (#116)' (#135) from claude/issue-116 into master

This commit is contained in:
catbot 2026-06-17 21:38:28 +02:00
commit 5948bb91c1
2 changed files with 20 additions and 15 deletions

View file

@ -1010,21 +1010,22 @@ void Device::EnqueueDeletion(VkBuffer buffer, VkDeviceMemory memory) {
} }
void Device::ReclaimDeletions() { void Device::ReclaimDeletions() {
// Free every entry whose retire frame has been reached, compacting the // Free every entry whose retire frame has been reached. An entry tagged at
// survivors down in place. An entry tagged at frame F retires at // frame F retires at F + framesInFlight: by the time the CPU has begun that
// F + framesInFlight: by the time the CPU has begun that many later // many later frames (each gated by a per-image fence wait), single-queue
// frames (each gated by a per-image fence wait), single-queue submission // submission order guarantees all GPU work from frame F is complete.
// order guarantees all GPU work from frame F is complete. //
std::size_t kept = 0; // frameCounter is monotonic, so retireAfter is non-decreasing down the
for (PendingDeletion& entry : deletionQueue) { // queue and the ready entries are a contiguous prefix: pop them off the
if (entry.retireAfter + framesInFlight <= frameCounter) { // front and stop at the first one still in flight (everything behind it is
// newer, hence also in flight). O(ready), not O(queue).
while (!deletionQueue.empty() &&
deletionQueue.front().retireAfter + framesInFlight <= frameCounter) {
const PendingDeletion& entry = deletionQueue.front();
vkDestroyBuffer(device, entry.buffer, nullptr); vkDestroyBuffer(device, entry.buffer, nullptr);
vkFreeMemory(device, entry.memory, nullptr); vkFreeMemory(device, entry.memory, nullptr);
} else { deletionQueue.pop_front();
deletionQueue[kept++] = entry;
} }
}
deletionQueue.resize(kept);
} }
void Device::DrainDeletions() { void Device::DrainDeletions() {

View file

@ -320,7 +320,11 @@ export namespace Crafter {
// Zero until set, which only makes ReclaimDeletions more eager — and it // Zero until set, which only makes ReclaimDeletions more eager — and it
// is never called before a Window (which sets this) starts rendering. // is never called before a Window (which sets this) starts rendering.
inline static std::uint8_t framesInFlight = 0; inline static std::uint8_t framesInFlight = 0;
inline static std::vector<PendingDeletion> deletionQueue; // A deque, not a vector: frameCounter is monotonic, so retireAfter is
// non-decreasing down the queue and the entries ready to reclaim are
// always a prefix. ReclaimDeletions pops that prefix off the front in
// O(ready) instead of walking and compacting the whole queue.
inline static std::deque<PendingDeletion> deletionQueue;
// Tag {buffer, memory} for deletion after framesInFlight more frames. // Tag {buffer, memory} for deletion after framesInFlight more frames.
// A VK_NULL_HANDLE buffer is ignored (nothing to free). // A VK_NULL_HANDLE buffer is ignored (nothing to free).
static void EnqueueDeletion(VkBuffer buffer, VkDeviceMemory memory); static void EnqueueDeletion(VkBuffer buffer, VkDeviceMemory memory);