@@ -28,6 +28,14 @@ namespace Renderer
2828 };
2929 constexpr u32 maxDescriptorSets = 128 ;
3030
31+ // [Frame-safe descriptor rebind] A buffer descriptor write that must wait until its target frame
32+ // slot is no longer being read by an in-flight frame before it can safely be applied.
33+ struct PendingBufferWrite
34+ {
35+ BufferID bufferID;
36+ DescriptorType type;
37+ };
38+
3139 struct DescriptorSet
3240 {
3341 DescriptorSetDesc desc;
@@ -50,6 +58,11 @@ namespace Renderer
5058
5159 // Reverse map: buffer -> binding (for fast lookup during validation)
5260 std::unordered_map<BufferID::type, u32 > bufferToBinding;
61+
62+ // [Frame-safe descriptor rebind] Buffer-binding writes recorded per frame-copy and applied in
63+ // FlushPendingBufferWrites once that slot's fence has been waited (its previous frame is done),
64+ // so we never rewrite a descriptor copy an in-flight frame is still reading.
65+ std::unordered_map<u32 , PendingBufferWrite> pendingBufferWritesPerSlot[RenderDeviceVK::FRAME_INDEX_COUNT ];
5366 };
5467
5568 struct DescriptorHandlerData : public IDescriptorHandlerData
@@ -339,7 +352,7 @@ namespace Renderer
339352 }
340353 }
341354
342- void DescriptorHandlerVK::BindDescriptor (DescriptorSetID setID, u32 binding, BufferID bufferID, VkBuffer buffer, DescriptorType type, u32 frameIndex)
355+ void DescriptorHandlerVK::BindDescriptor (DescriptorSetID setID, u32 binding, BufferID bufferID, DescriptorType type, u32 frameIndex)
343356 {
344357 ZoneScoped;
345358 DescriptorHandlerData& data = *static_cast <DescriptorHandlerData*>(_data);
@@ -385,15 +398,26 @@ namespace Renderer
385398
386399 // Mark binding as bound
387400 descriptorSet.unboundBindings .Unset (binding);
388-
389- // Vulkan descriptor update
401+
402+ // [Frame-safe descriptor rebind] A GPUVector resize swaps in a NEW VkBuffer, BufferIDs are
403+ // recycled, and two frames are in flight sharing this set's per-frame descriptor copies. The only
404+ // moment at which writing a slot's copy is both safe (the slot's previous frame is fully done, so
405+ // no in-flight read races the write under UPDATE_AFTER_BIND) and current is right after that slot's
406+ // fence has been waited. So record the desired buffer per (slot, binding) here and apply it in
407+ // FlushPendingBufferWrites, which runs in FlipFrame immediately after that fence wait. The actual
408+ // VkBuffer is resolved from bufferID at flush time, so it always reflects the latest generation.
409+ descriptorSet.pendingBufferWritesPerSlot [frameIndex][binding] = PendingBufferWrite{ bufferID, type };
410+ }
411+
412+ void DescriptorHandlerVK::WriteBufferDescriptor (DescriptorSet& descriptorSet, u32 binding, VkBuffer buffer, DescriptorType type, u32 frameIndex)
413+ {
390414 VkDescriptorBufferInfo bufferInfo{};
391415 bufferInfo.buffer = buffer;
392416 bufferInfo.offset = 0 ;
393417 bufferInfo.range = VK_WHOLE_SIZE ;
394418
395419 VkWriteDescriptorSet descriptorWrite{ VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
396- descriptorWrite.dstSet = GetVkDescriptorSet (setID, frameIndex) ;
420+ descriptorWrite.dstSet = descriptorSet. sets [ frameIndex] ;
397421 descriptorWrite.dstBinding = binding;
398422 descriptorWrite.descriptorCount = 1 ;
399423 descriptorWrite.descriptorType = FormatConverterVK::ToVkDescriptorType (type);
@@ -402,6 +426,23 @@ namespace Renderer
402426 vkUpdateDescriptorSets (_device->_device , 1 , &descriptorWrite, 0 , nullptr );
403427 }
404428
429+ void DescriptorHandlerVK::FlushPendingBufferWrites (u32 frameIndex)
430+ {
431+ ZoneScoped;
432+ DescriptorHandlerData& data = *static_cast <DescriptorHandlerData*>(_data);
433+
434+ for (DescriptorSet& descriptorSet : data.descriptorSets )
435+ {
436+ auto & pending = descriptorSet.pendingBufferWritesPerSlot [frameIndex];
437+ for (auto & [binding, write] : pending)
438+ {
439+ VkBuffer buffer = _bufferHandler->GetBuffer (write.bufferID );
440+ WriteBufferDescriptor (descriptorSet, binding, buffer, write.type , frameIndex);
441+ }
442+ pending.clear ();
443+ }
444+ }
445+
405446 void DescriptorHandlerVK::BindDescriptor (DescriptorSetID setID, u32 binding, VkImageView image, DescriptorType type, bool isRT, u32 frameIndex)
406447 {
407448 ZoneScoped;
0 commit comments