renderer_vulkan: Fix crashing when updating descriptors

* During pipeline configure the function would acquire some payload space from the descriptor update queue,
  write the descriptor data on the GPU thread and give the scheduler a pointer to the beginning of said space to update it later.
  TickFrame resets the payload cursor, used to track acquires, back to the beginning of the buffer.
  This wasn't a problem before since WaitWorker was called at the end of the frame but now it is.
  If a frame writes to a cursor before the scheduler catches up, it will crash

* To fix this the payload buffer has been increased to account for the in flight frames that are allowed to exist now.
  TickFrame will switch between the payload spaces instead of resetting
This commit is contained in:
GPUCode 2023-04-08 21:53:13 +03:00
parent 2ad9acf795
commit 50791cb974
2 changed files with 17 additions and 4 deletions

View file

@ -14,13 +14,18 @@ namespace Vulkan {
UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_) UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_)
: device{device_}, scheduler{scheduler_} { : device{device_}, scheduler{scheduler_} {
payload_start = payload.data();
payload_cursor = payload.data(); payload_cursor = payload.data();
} }
UpdateDescriptorQueue::~UpdateDescriptorQueue() = default; UpdateDescriptorQueue::~UpdateDescriptorQueue() = default;
void UpdateDescriptorQueue::TickFrame() { void UpdateDescriptorQueue::TickFrame() {
payload_cursor = payload.data(); if (++frame_index >= FRAMES_IN_FLIGHT) {
frame_index = 0;
}
payload_start = payload.data() + frame_index * FRAME_PAYLOAD_SIZE;
payload_cursor = payload_start;
} }
void UpdateDescriptorQueue::Acquire() { void UpdateDescriptorQueue::Acquire() {
@ -28,10 +33,10 @@ void UpdateDescriptorQueue::Acquire() {
// This is the maximum number of entries a single draw call might use. // This is the maximum number of entries a single draw call might use.
static constexpr size_t MIN_ENTRIES = 0x400; static constexpr size_t MIN_ENTRIES = 0x400;
if (std::distance(payload.data(), payload_cursor) + MIN_ENTRIES >= payload.max_size()) { if (std::distance(payload_start, payload_cursor) + MIN_ENTRIES >= FRAME_PAYLOAD_SIZE) {
LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread"); LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread");
scheduler.WaitWorker(); scheduler.WaitWorker();
payload_cursor = payload.data(); payload_cursor = payload_start;
} }
upload_start = payload_cursor; upload_start = payload_cursor;
} }

View file

@ -29,6 +29,12 @@ struct DescriptorUpdateEntry {
}; };
class UpdateDescriptorQueue final { class UpdateDescriptorQueue final {
// This should be plenty for the vast majority of cases. Most desktop platforms only
// provide up to 3 swapchain images.
static constexpr size_t FRAMES_IN_FLIGHT = 5;
static constexpr size_t FRAME_PAYLOAD_SIZE = 0x10000;
static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT;
public: public:
explicit UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_); explicit UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_);
~UpdateDescriptorQueue(); ~UpdateDescriptorQueue();
@ -73,9 +79,11 @@ private:
const Device& device; const Device& device;
Scheduler& scheduler; Scheduler& scheduler;
size_t frame_index{0};
DescriptorUpdateEntry* payload_cursor = nullptr; DescriptorUpdateEntry* payload_cursor = nullptr;
DescriptorUpdateEntry* payload_start = nullptr;
const DescriptorUpdateEntry* upload_start = nullptr; const DescriptorUpdateEntry* upload_start = nullptr;
std::array<DescriptorUpdateEntry, 0x10000> payload; std::array<DescriptorUpdateEntry, PAYLOAD_SIZE> payload;
}; };
} // namespace Vulkan } // namespace Vulkan