Skip to content

Commit d07d720

Browse files
authored
Lua Editor Part 2 (#57)
* Add assert to UploadBufferHandler to prevent usage during the Render functions Add assert to UploadBufferHandler to prevent usage during the Render functions * Fix issue with descriptor rebinding Descriptor binding affected in-flight frames
1 parent 97c1cbe commit d07d720

7 files changed

Lines changed: 95 additions & 7 deletions

File tree

Source/Renderer/Renderer/Renderer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ namespace Renderer
207207
virtual bool ShouldWaitForUpload() = 0;
208208
virtual void SetHasWaitedForUpload() = 0;
209209
virtual SemaphoreID GetUploadFinishedSemaphore() = 0;
210+
// Re-opens staging uploads for the new frame's Update phase. Call before any uploads, after
211+
// the previous frame's FlipFrame locked them. Uploads requested while locked assert.
212+
virtual void UnlockUploads() = 0;
210213

211214
[[nodiscard]] BufferID CreateBuffer(BufferID bufferID, BufferDesc& desc);
212215
[[nodiscard]] BufferID CreateAndFillBuffer(BufferID bufferID, BufferDesc desc, void* data, size_t dataSize); // Deletes the current BufferID if it's not invalid

Source/Renderer/Renderer/Renderers/Vulkan/Backend/DescriptorHandlerVK.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

Source/Renderer/Renderer/Renderers/Vulkan/Backend/DescriptorHandlerVK.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ namespace Renderer
3333

3434
DescriptorSetID CreateDescriptorSet(const DescriptorSetDesc& desc);
3535

36-
void BindDescriptor(DescriptorSetID setID, u32 binding, BufferID bufferID, VkBuffer buffer, DescriptorType type, u32 frameIndex);
36+
void BindDescriptor(DescriptorSetID setID, u32 binding, BufferID bufferID, DescriptorType type, u32 frameIndex);
37+
38+
// [Frame-safe descriptor rebind] Apply buffer descriptor writes that were deferred while their
39+
// frame slot was in flight. Call once per frame, right after that slot's fence has been waited.
40+
void FlushPendingBufferWrites(u32 frameIndex);
3741
void BindDescriptor(DescriptorSetID setID, u32 binding, VkImageView image, DescriptorType type, bool isRT, u32 frameIndex);
3842
void BindDescriptorArray(DescriptorSetID setID, u32 binding, VkImageView image, u32 arrayOffset, DescriptorType type, bool isRT, u32 frameIndex);
3943
void BindDescriptorArray(DescriptorSetID setID, u32 binding, std::vector<VkImageView>& images, u32 arrayOffset, DescriptorType type, bool isRT, u32 frameIndex);
@@ -51,6 +55,7 @@ namespace Renderer
5155
private:
5256
void CreateDescriptorPool();
5357
void CreateDescriptorSet(DescriptorSet& descriptorSet);
58+
void WriteBufferDescriptor(DescriptorSet& descriptorSet, u32 binding, VkBuffer buffer, DescriptorType type, u32 frameIndex);
5459
bool ValidatePermissionViolations(u32 slot, const DescriptorSet& descriptorSet, const PersistentBitSet& accesses, const BitSet& permissions, const char* permissionName, const PersistentBitSet* usedBindings = nullptr);
5560

5661
private:

Source/Renderer/Renderer/Renderers/Vulkan/Backend/UploadBufferHandlerVK.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ namespace Renderer
115115
std::mutex submitMutex;
116116
SemaphoreID uploadFinishedSemaphore;
117117

118+
// An upload on the render-loop thread while locked would land a frame late, so it asserts.
119+
// Worker-thread (async streaming) uploads are exempt.
120+
std::atomic<bool> uploadsLocked = false;
121+
std::thread::id renderThreadID;
122+
118123
// These are copies of the barriers which needs to run on the main commandlist
119124
moodycamel::ConcurrentQueue<VkBufferMemoryBarrier> bufferMemoryBarriers;
120125
};
@@ -168,6 +173,11 @@ namespace Renderer
168173
{
169174
UploadBufferHandlerVKData* data = static_cast<UploadBufferHandlerVKData*>(_data);
170175

176+
// Per-frame submit point: lock until UnlockUploads() at the end of Render, and capture the
177+
// render-loop thread so the assert is scoped to it.
178+
data->renderThreadID = std::this_thread::get_id();
179+
data->uploadsLocked = true;
180+
171181
for (u32 i = 0; i < data->stagingBuffers.Num; i++)
172182
{
173183
StagingBuffer& stagingBuffer = data->stagingBuffers.Get(i);
@@ -293,6 +303,10 @@ namespace Renderer
293303

294304
UploadBufferHandlerVKData* data = static_cast<UploadBufferHandlerVKData*>(_data);
295305

306+
// Thread check first so worker-thread (async streaming) uploads short-circuit out.
307+
NC_ASSERT(!(std::this_thread::get_id() == data->renderThreadID && data->uploadsLocked),
308+
"UploadBufferHandlerVK : Staging upload requested after this frame's uploads were submitted (FlipFrame). Move it to the Update phase, before Render.");
309+
296310
void* mappedMemory = nullptr;
297311
StagingBufferID stagingBufferID;
298312

@@ -356,6 +370,10 @@ namespace Renderer
356370

357371
UploadBufferHandlerVKData* data = static_cast<UploadBufferHandlerVKData*>(_data);
358372

373+
// Thread check first so worker-thread (async streaming) uploads short-circuit out.
374+
NC_ASSERT(!(std::this_thread::get_id() == data->renderThreadID && data->uploadsLocked),
375+
"UploadBufferHandlerVK : Staging upload requested after this frame's uploads were submitted (FlipFrame). Move it to the Update phase, before Render.");
376+
359377
void* mappedMemory = nullptr;
360378
StagingBufferID stagingBufferID;
361379
size_t offset = Allocate(size, stagingBufferID, mappedMemory);
@@ -483,6 +501,13 @@ namespace Renderer
483501
data->needsWait = false;
484502
}
485503

504+
void UploadBufferHandlerVK::UnlockUploads()
505+
{
506+
// Called at the end of Render; the next frame's Update phase may upload again.
507+
UploadBufferHandlerVKData* data = static_cast<UploadBufferHandlerVKData*>(_data);
508+
data->uploadsLocked = false;
509+
}
510+
486511
size_t UploadBufferHandlerVK::Allocate(size_t size, StagingBufferID& stagingBufferID, void*& mappedMemory)
487512
{
488513
UploadBufferHandlerVKData* data = static_cast<UploadBufferHandlerVKData*>(_data);

Source/Renderer/Renderer/Renderers/Vulkan/Backend/UploadBufferHandlerVK.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ namespace Renderer
4646
SemaphoreID GetUploadFinishedSemaphore();
4747
bool ShouldWaitForUpload();
4848
void SetHasWaitedForUpload();
49+
void UnlockUploads();
4950
private:
5051
size_t Allocate(size_t size, StagingBufferID& stagingBufferID, void*& mappedMemory);
5152
void ExecuteStagingBuffer(VkCommandBuffer commandBuffer, StagingBuffer& stagingBuffer);

Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,10 @@ namespace Renderer
253253

254254
void RendererVK::BindDescriptor(DescriptorSetID descriptorSetID, u32 bindingIndex, BufferID bufferID, DescriptorType type, u32 frameIndex)
255255
{
256-
VkBuffer buffer = _bufferHandler->GetBuffer(bufferID);
257-
_descriptorHandler->BindDescriptor(descriptorSetID, bindingIndex, bufferID, buffer, type, frameIndex);
256+
// [Frame-safe descriptor rebind] The handler records this write per frame-copy and applies it in
257+
// FlushPendingBufferWrites (FlipFrame), after that slot's fence has been waited. The VkBuffer is
258+
// resolved from bufferID at that point, so it always reflects the latest GPUVector generation.
259+
_descriptorHandler->BindDescriptor(descriptorSetID, bindingIndex, bufferID, type, frameIndex);
258260
}
259261

260262
void RendererVK::BindDescriptor(DescriptorSetID descriptorSetID, u32 bindingIndex, ImageID imageID, u32 mipLevel, DescriptorType type, u32 frameIndex)
@@ -409,6 +411,11 @@ namespace Renderer
409411
timeWaited = timer.GetLifeTime();
410412
}
411413

414+
// [Frame-safe descriptor rebind] This slot's previous frame has now completed (fence waited above),
415+
// so it is finally safe to apply any buffer descriptor writes that were deferred while it was in
416+
// flight. Done before any of this frame's command lists record/use the descriptor sets.
417+
_descriptorHandler->FlushPendingBufferWrites(_frameIndex);
418+
412419
_imageHandler->FlipFrame(frameIndex);
413420
if (_renderSizeChanged)
414421
{
@@ -1997,6 +2004,11 @@ namespace Renderer
19972004
return _uploadBufferHandler->GetUploadFinishedSemaphore();
19982005
}
19992006

2007+
void RendererVK::UnlockUploads()
2008+
{
2009+
_uploadBufferHandler->UnlockUploads();
2010+
}
2011+
20002012
void RendererVK::CopyBuffer(BufferID dstBuffer, u64 dstOffset, BufferID srcBuffer, u64 srcOffset, u64 range)
20012013
{
20022014
_uploadBufferHandler->CopyBufferToBuffer(dstBuffer, dstOffset, srcBuffer, srcOffset, range);

Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ namespace Renderer
175175
[[nodiscard]] bool ShouldWaitForUpload() override;
176176
void SetHasWaitedForUpload() override;
177177
[[nodiscard]] SemaphoreID GetUploadFinishedSemaphore() override;
178+
void UnlockUploads() override;
178179

179180
// Uses the upload handler to schedule it for next frames command list
180181
void CopyBuffer(BufferID dstBuffer, u64 dstOffset, BufferID srcBuffer, u64 srcOffset, u64 range) override;

0 commit comments

Comments
 (0)