//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The deletion queue takes handles from any thread. Every Vulkan handle // owner's destructor queues its handles, and owners go where they're made: a // loader thread drops the buffers of a model it built while the render loop // reclaims the queue each frame. The queue was a plain deque, so those // pushes and pops raced and corrupted it. // // Four threads make and drop buffers while this one steps frames and // reclaims, as Window::Render does; then everything has to drain cleanly. // // Needs a Vulkan device and no window. #include #include "vulkan/vulkan.h" import Crafter.Graphics; import std; using namespace Crafter; int main() { Device::Initialize(); Device::framesInFlight = 2; Device::frameCounter = 0; // Each round, every thread makes its buffers, then all drop theirs at // once, so the queue's pushes overlap each other and the reclaims. constexpr int kThreads = 4; constexpr int kBuffers = 400; constexpr int kRounds = 5; for (int round = 0; round < kRounds; ++round) { std::latch made(kThreads + 1); std::atomic dropping = kThreads; std::vector makers; for (int t = 0; t < kThreads; ++t) { makers.emplace_back([&] { std::vector> buffers(kBuffers); for (VulkanBuffer& buffer : buffers) { buffer.Create(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 16); } made.arrive_and_wait(); buffers.clear(); --dropping; }); } made.arrive_and_wait(); while (dropping > 0) { ++Device::frameCounter; Device::ReclaimDeletions(); } for (std::thread& maker : makers) maker.join(); } Device::DrainDeletions(); const bool empty = Device::deletionQueue.empty(); std::println("{} {} buffers dropped at once on {} threads while frames were reclaimed, {} times, and drained", empty ? "PASS" : "FAIL", kThreads * kBuffers, kThreads, kRounds); return empty ? EXIT_SUCCESS : EXIT_FAILURE; }