Skip to content

Commit 31a2065

Browse files
Vulkan: moved dynamic buffer allocations to device context (#420)
1 parent be73a46 commit 31a2065

File tree

9 files changed

+317
-235
lines changed

9 files changed

+317
-235
lines changed

Graphics/GraphicsEngine/include/BufferBase.hpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2022 Diligent Graphics LLC
2+
* Copyright 2019-2025 Diligent Graphics LLC
33
* Copyright 2015-2019 Egor Yusov
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -94,13 +94,26 @@ class BufferBase : public DeviceObjectBase<typename EngineImplTraits::BufferInte
9494
m_pDefaultUAV{nullptr, STDDeleter<BufferViewImplType, TBuffViewObjAllocator>(BuffViewObjAllocator)},
9595
m_pDefaultSRV{nullptr, STDDeleter<BufferViewImplType, TBuffViewObjAllocator>(BuffViewObjAllocator)}
9696
{
97-
ValidateBufferDesc(this->m_Desc, this->GetDevice());
97+
ValidateBufferDesc(this->m_Desc, this->GetDevice()); // May throw
9898

9999
Uint64 DeviceQueuesMask = this->GetDevice()->GetCommandQueueMask();
100100
DEV_CHECK_ERR((this->m_Desc.ImmediateContextMask & DeviceQueuesMask) != 0,
101101
"No bits in the immediate context mask (0x", std::hex, this->m_Desc.ImmediateContextMask,
102102
") correspond to one of ", this->GetDevice()->GetCommandQueueCount(), " available software command queues");
103103
this->m_Desc.ImmediateContextMask &= DeviceQueuesMask;
104+
105+
if (this->m_Desc.Usage == USAGE_DYNAMIC)
106+
{
107+
m_DynamicBufferId = pDevice->AllocateDynamicBufferId();
108+
}
109+
}
110+
111+
~BufferBase()
112+
{
113+
if (m_DynamicBufferId != ~0u)
114+
{
115+
this->m_pDevice->RecycleDynamicBufferId(m_DynamicBufferId);
116+
}
104117
}
105118

106119
IMPLEMENT_QUERY_INTERFACE_IN_PLACE(IID_Buffer, TDeviceObjectBase)
@@ -228,6 +241,11 @@ class BufferBase : public DeviceObjectBase<typename EngineImplTraits::BufferInte
228241
return (this->m_State & State) == State;
229242
}
230243

244+
Uint32 GetDynamicBufferId() const
245+
{
246+
return m_DynamicBufferId;
247+
}
248+
231249
protected:
232250
/// Pure virtual function that creates buffer view for the specific engine implementation.
233251
virtual void CreateViewInternal(const struct BufferViewDesc& ViewDesc, IBufferView** ppView, bool bIsDefaultView) = 0;
@@ -261,6 +279,9 @@ class BufferBase : public DeviceObjectBase<typename EngineImplTraits::BufferInte
261279

262280
MEMORY_PROPERTIES m_MemoryProperties = MEMORY_PROPERTY_UNKNOWN;
263281

282+
// Dynamic buffer Id is used by device contexts to index dynamic allocations
283+
Uint32 m_DynamicBufferId = ~0u;
284+
264285
/// Default UAV addressing the entire buffer
265286
std::unique_ptr<BufferViewImplType, STDDeleter<BufferViewImplType, TBuffViewObjAllocator>> m_pDefaultUAV;
266287

Graphics/GraphicsEngine/include/RenderDeviceBase.hpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
#include <atomic>
3434
#include <thread>
35+
#include <vector>
36+
#include <unordered_set>
3537

3638
#include "RenderDevice.h"
3739
#include "DeviceObjectBase.hpp"
@@ -48,6 +50,7 @@
4850
#include "STDAllocator.hpp"
4951
#include "IndexWrapper.hpp"
5052
#include "ThreadPool.hpp"
53+
#include "SpinLock.hpp"
5154

5255
namespace Diligent
5356
{
@@ -211,6 +214,9 @@ class RenderDeviceBase : public ObjectBase<typename EngineImplTraits::RenderDevi
211214

212215
~RenderDeviceBase()
213216
{
217+
VERIFY(m_RecycledDynamicBufferIds.size() == m_NextDynamicBufferId, "Not all dynamic buffer IDs have been recycled");
218+
VERIFY(m_RecycledDynamicBufferIds.size() == m_DbgRecycledDynamicBufferIds.size(),
219+
"Recycled dynamic buffer ID set does not match the actual number of recycled IDs. This may happen if there were duplicate IDs or because of a bug.");
214220
}
215221

216222
IMPLEMENT_QUERY_INTERFACE_IN_PLACE(IID_RenderDevice, ObjectBase<BaseInterface>)
@@ -346,6 +352,33 @@ class RenderDeviceBase : public ObjectBase<typename EngineImplTraits::RenderDevi
346352
return m_pShaderCompilationThreadPool;
347353
}
348354

355+
Uint32 AllocateDynamicBufferId()
356+
{
357+
Threading::SpinLockGuard Guard{m_RecycledDynamicBufferIdsLock};
358+
if (!m_RecycledDynamicBufferIds.empty())
359+
{
360+
Uint32 Id = m_RecycledDynamicBufferIds.back();
361+
m_RecycledDynamicBufferIds.pop_back();
362+
#ifdef DILIGENT_DEBUG
363+
m_DbgRecycledDynamicBufferIds.erase(Id);
364+
#endif
365+
return Id;
366+
}
367+
else
368+
{
369+
return m_NextDynamicBufferId.fetch_add(1);
370+
}
371+
}
372+
373+
void RecycleDynamicBufferId(Uint32 Id)
374+
{
375+
Threading::SpinLockGuard Guard{m_RecycledDynamicBufferIdsLock};
376+
m_RecycledDynamicBufferIds.push_back(Id);
377+
#ifdef DILIGENT_DEBUG
378+
VERIFY(m_DbgRecycledDynamicBufferIds.emplace(Id).second, "Dynamic buffer ID ", Id, " has already been recycled. This appears to be a bug.");
379+
#endif
380+
}
381+
349382
protected:
350383
virtual void TestTextureFormat(TEXTURE_FORMAT TexFormat) = 0;
351384

@@ -644,6 +677,14 @@ class RenderDeviceBase : public ObjectBase<typename EngineImplTraits::RenderDevi
644677
RefCntAutoPtr<IThreadPool> m_pShaderCompilationThreadPool;
645678

646679
std::atomic<UniqueIdentifier> m_UniqueId{0};
680+
681+
// Dynamic buffer Ids are used by device contexts to index dynamic allocations
682+
std::atomic<Uint32> m_NextDynamicBufferId{0};
683+
Threading::SpinLock m_RecycledDynamicBufferIdsLock;
684+
std::vector<Uint32> m_RecycledDynamicBufferIds;
685+
#ifdef DILIGENT_DEBUG
686+
std::unordered_set<Uint32> m_DbgRecycledDynamicBufferIds;
687+
#endif
647688
};
648689

649690
} // namespace Diligent

Graphics/GraphicsEngineVulkan/include/BufferVkImpl.hpp

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2022 Diligent Graphics LLC
2+
* Copyright 2019-2025 Diligent Graphics LLC
33
* Copyright 2015-2019 Egor Yusov
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -64,31 +64,6 @@ class BufferVkImpl final : public BufferBase<EngineVkImplTraits>
6464

6565
IMPLEMENT_QUERY_INTERFACE_IN_PLACE(IID_BufferVk, TBufferBase)
6666

67-
#ifdef DILIGENT_DEVELOPMENT
68-
void DvpVerifyDynamicAllocation(const DeviceContextVkImpl* pCtx) const;
69-
#endif
70-
71-
size_t GetDynamicOffset(DeviceContextIndex CtxId, const DeviceContextVkImpl* pCtx) const
72-
{
73-
if (m_VulkanBuffer != VK_NULL_HANDLE)
74-
{
75-
return 0;
76-
}
77-
else
78-
{
79-
VERIFY(m_Desc.Usage == USAGE_DYNAMIC, "Dynamic buffer is expected");
80-
VERIFY_EXPR(!m_DynamicData.empty());
81-
#ifdef DILIGENT_DEVELOPMENT
82-
if (pCtx != nullptr)
83-
{
84-
DvpVerifyDynamicAllocation(pCtx);
85-
}
86-
#endif
87-
auto& DynAlloc = m_DynamicData[CtxId];
88-
return DynAlloc.AlignedOffset;
89-
}
90-
}
91-
9267
/// Implementation of IBufferVk::GetVkBuffer().
9368
virtual VkBuffer DILIGENT_CALL_TYPE GetVkBuffer() const override final;
9469

@@ -136,24 +111,6 @@ class BufferVkImpl final : public BufferBase<EngineVkImplTraits>
136111
Uint32 m_DynamicOffsetAlignment = 0;
137112
VkDeviceSize m_BufferMemoryAlignedOffset = 0;
138113

139-
// TODO (assiduous): move dynamic allocations to device context.
140-
static constexpr size_t CacheLineSize = 64;
141-
struct alignas(CacheLineSize) CtxDynamicData : VulkanDynamicAllocation
142-
{
143-
CtxDynamicData() noexcept {}
144-
CtxDynamicData(CtxDynamicData&&) = default;
145-
146-
CtxDynamicData& operator=(VulkanDynamicAllocation&& Allocation)
147-
{
148-
*static_cast<VulkanDynamicAllocation*>(this) = std::move(Allocation);
149-
return *this;
150-
}
151-
152-
Uint8 Padding[CacheLineSize - sizeof(VulkanDynamicAllocation)] = {};
153-
};
154-
static_assert(sizeof(CtxDynamicData) == CacheLineSize, "Unexpected sizeof(CtxDynamicData)");
155-
std::vector<CtxDynamicData, STDAllocatorRawMem<CtxDynamicData>> m_DynamicData;
156-
157114
VulkanUtilities::BufferWrapper m_VulkanBuffer;
158115
VulkanUtilities::VulkanMemoryAllocation m_MemoryAllocation;
159116
};

Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,12 @@ class DeviceContextVkImpl final : public DeviceContextNextGenBase<EngineVkImplTr
407407

408408
QueryManagerVk* GetQueryManager() { return m_pQueryMgr; }
409409

410+
size_t GetDynamicBufferOffset(const BufferVkImpl* pBuffer, bool VerifyAllocation = true);
411+
412+
#ifdef DILIGENT_DEVELOPMENT
413+
void DvpVerifyDynamicAllocation(const BufferVkImpl* pBuffer) const;
414+
#endif
415+
410416
private:
411417
void TransitionRenderTargets(RESOURCE_STATE_TRANSITION_MODE StateTransitionMode);
412418
__forceinline void CommitRenderPassAndFramebuffer(bool VerifyStates);
@@ -593,8 +599,6 @@ class DeviceContextVkImpl final : public DeviceContextNextGenBase<EngineVkImplTr
593599
std::vector<std::pair<Uint64, RefCntAutoPtr<FenceVkImpl>>> m_SignalFences;
594600
std::vector<std::pair<Uint64, RefCntAutoPtr<FenceVkImpl>>> m_WaitFences;
595601

596-
std::unordered_map<BufferVkImpl*, VulkanUploadAllocation> m_UploadAllocations;
597-
598602
struct MappedTextureKey
599603
{
600604
TextureVkImpl* const Texture;
@@ -622,6 +626,16 @@ class DeviceContextVkImpl final : public DeviceContextNextGenBase<EngineVkImplTr
622626
};
623627
std::unordered_map<MappedTextureKey, MappedTexture, MappedTextureKey::Hasher> m_MappedTextures;
624628

629+
struct MappedBuffer
630+
{
631+
VulkanDynamicAllocation Allocation;
632+
#ifdef DILIGENT_DEVELOPMENT
633+
UniqueIdentifier DvpBufferUID = -1;
634+
#endif
635+
};
636+
// NB: using absl::flat_hash_map<const BufferVkImpl*, MappedBuffer> is considerably slower.
637+
std::vector<MappedBuffer> m_MappedBuffers;
638+
625639
// Command pools for every queue family
626640
std::unique_ptr<std::unique_ptr<VulkanUtilities::VulkanCommandBufferPool>[]> m_QueueFamilyCmdPools;
627641
// Command pool for the family for which we are recording commands

Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp

Lines changed: 5 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2024 Diligent Graphics LLC
2+
* Copyright 2019-2025 Diligent Graphics LLC
33
* Copyright 2015-2019 Egor Yusov
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -194,7 +194,7 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase
194194

195195
void AssignDescriptorSetAllocation(Uint32 SetIndex, DescriptorSetAllocation&& Allocation)
196196
{
197-
auto& DescrSet = GetDescriptorSet(SetIndex);
197+
DescriptorSet& DescrSet = GetDescriptorSet(SetIndex);
198198
VERIFY(DescrSet.GetSize() > 0, "Descriptor set is empty");
199199
VERIFY(!DescrSet.m_DescriptorSetAllocation, "Descriptor set allocation has already been initialized");
200200
DescrSet.m_DescriptorSetAllocation = std::move(Allocation);
@@ -260,9 +260,9 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase
260260
template <bool VerifyOnly>
261261
void TransitionResources(DeviceContextVkImpl* pCtxVkImpl);
262262

263-
__forceinline Uint32 GetDynamicBufferOffsets(DeviceContextIndex CtxId,
264-
std::vector<uint32_t>& Offsets,
265-
Uint32 StartInd) const;
263+
Uint32 GetDynamicBufferOffsets(DeviceContextVkImpl* pCtx,
264+
std::vector<uint32_t>& Offsets,
265+
Uint32 StartInd) const;
266266

267267
private:
268268
Resource* GetFirstResourcePtr()
@@ -316,83 +316,4 @@ __forceinline auto ShaderResourceCacheVk::Resource::GetDescriptorWriteInfo<Descr
316316
template <>
317317
__forceinline auto ShaderResourceCacheVk::Resource::GetDescriptorWriteInfo<DescriptorType::AccelerationStructure>() const { return GetAccelerationStructureWriteInfo(); }
318318

319-
320-
__forceinline Uint32 ShaderResourceCacheVk::GetDynamicBufferOffsets(DeviceContextIndex CtxId,
321-
std::vector<uint32_t>& Offsets,
322-
Uint32 StartInd) const
323-
{
324-
// If any of the sets being bound include dynamic uniform or storage buffers, then
325-
// pDynamicOffsets includes one element for each array element in each dynamic descriptor
326-
// type binding in each set. Values are taken from pDynamicOffsets in an order such that
327-
// all entries for set N come before set N+1; within a set, entries are ordered by the binding
328-
// numbers (unclear if this is SPIRV binding or VkDescriptorSetLayoutBinding number) in the
329-
// descriptor set layouts; and within a binding array, elements are in order. (13.2.5)
330-
331-
// In each descriptor set, all uniform buffers with dynamic offsets (DescriptorType::UniformBufferDynamic)
332-
// for every shader stage come first, followed by all storage buffers with dynamic offsets
333-
// (DescriptorType::StorageBufferDynamic and DescriptorType::StorageBufferDynamic_ReadOnly) for every shader stage,
334-
// followed by all other resources.
335-
Uint32 OffsetInd = StartInd;
336-
for (Uint32 set = 0; set < m_NumSets; ++set)
337-
{
338-
const auto& DescrSet = GetDescriptorSet(set);
339-
const auto SetSize = DescrSet.GetSize();
340-
341-
Uint32 res = 0;
342-
while (res < SetSize)
343-
{
344-
const auto& Res = DescrSet.GetResource(res);
345-
if (Res.Type == DescriptorType::UniformBufferDynamic)
346-
{
347-
const auto* pBufferVk = Res.pObject.ConstPtr<BufferVkImpl>();
348-
// Do not verify dynamic allocation here as there may be some buffers that are not used by the PSO.
349-
// The allocations of the buffers that are actually used will be verified by
350-
// PipelineResourceSignatureVkImpl::DvpValidateCommittedResource().
351-
const auto Offset = pBufferVk != nullptr ? pBufferVk->GetDynamicOffset(CtxId, nullptr /* Do not verify allocation*/) : 0;
352-
// The effective offset used for dynamic uniform and storage buffer bindings is the sum of the relative
353-
// offset taken from pDynamicOffsets, and the base address of the buffer plus base offset in the descriptor set.
354-
// The range of the dynamic uniform and storage buffer bindings is the buffer range as specified in the descriptor set.
355-
Offsets[OffsetInd++] = StaticCast<Uint32>(Res.BufferDynamicOffset + Offset);
356-
++res;
357-
}
358-
else
359-
break;
360-
}
361-
362-
while (res < SetSize)
363-
{
364-
const auto& Res = DescrSet.GetResource(res);
365-
if (Res.Type == DescriptorType::StorageBufferDynamic ||
366-
Res.Type == DescriptorType::StorageBufferDynamic_ReadOnly)
367-
{
368-
const auto* pBufferVkView = Res.pObject.ConstPtr<BufferViewVkImpl>();
369-
const auto* pBufferVk = pBufferVkView != nullptr ? pBufferVkView->GetBuffer<const BufferVkImpl>() : nullptr;
370-
// Do not verify dynamic allocation here as there may be some buffers that are not used by the PSO.
371-
// The allocations of the buffers that are actually used will be verified by
372-
// PipelineResourceSignatureVkImpl::DvpValidateCommittedResource().
373-
const auto Offset = pBufferVk != nullptr ? pBufferVk->GetDynamicOffset(CtxId, nullptr /* Do not verify allocation*/) : 0;
374-
// The effective offset used for dynamic uniform and storage buffer bindings is the sum of the relative
375-
// offset taken from pDynamicOffsets, and the base address of the buffer plus base offset in the descriptor set.
376-
// The range of the dynamic uniform and storage buffer bindings is the buffer range as specified in the descriptor set.
377-
Offsets[OffsetInd++] = StaticCast<Uint32>(Res.BufferDynamicOffset + Offset);
378-
++res;
379-
}
380-
else
381-
break;
382-
}
383-
384-
#ifdef DILIGENT_DEBUG
385-
for (; res < SetSize; ++res)
386-
{
387-
const auto& Res = DescrSet.GetResource(res);
388-
VERIFY((Res.Type != DescriptorType::UniformBufferDynamic &&
389-
Res.Type != DescriptorType::StorageBufferDynamic &&
390-
Res.Type != DescriptorType::StorageBufferDynamic_ReadOnly),
391-
"All dynamic uniform and storage buffers are expected to go first in the beginning of each descriptor set");
392-
}
393-
#endif
394-
}
395-
return OffsetInd - StartInd;
396-
}
397-
398319
} // namespace Diligent

0 commit comments

Comments
 (0)