Skip to content

Commit dfe6646

Browse files
committed
Add reset functionality to IDescriptorPool.
1 parent 7ba6cc2 commit dfe6646

File tree

7 files changed

+164
-153
lines changed

7 files changed

+164
-153
lines changed

include/nbl/asset/IDescriptorSetLayout.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ class NBL_API IDescriptorSetLayout : public virtual core::IReferenceCounted
157157
inline uint32_t getTotalCount() const { return (m_count == 0ull) ? 0u : m_storageOffsets[m_count - 1].data; }
158158

159159
private:
160+
// error C2248 : 'nbl::asset::IDescriptorSetLayout<nbl::video::IGPUSampler>::CBindingRedirect::CBindingRedirect'
161+
// : cannot access private member declared in class 'nbl::asset::IDescriptorSetLayout<nbl::video::IGPUSampler>::CBindingRedirect'
160162
friend class IDescriptorSetLayout;
161163
struct SBuildInfo
162164
{

include/nbl/video/IDescriptorPool.h

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ class IGPUDescriptorSetLayout;
2121

2222
class NBL_API IDescriptorPool : public core::IReferenceCounted, public IBackendObject
2323
{
24-
friend class IGPUDescriptorSet;
25-
2624
public:
2725
enum E_CREATE_FLAGS : uint32_t
2826
{
@@ -61,14 +59,15 @@ class NBL_API IDescriptorPool : public core::IReferenceCounted, public IBackendO
6159
}
6260

6361
bool createDescriptorSets(uint32_t count, const IGPUDescriptorSetLayout* const* layouts, core::smart_refctd_ptr<IGPUDescriptorSet>* output);
64-
65-
// TODO(achal): Remove.
66-
bool freeDescriptorSets(const uint32_t descriptorSetCount, IGPUDescriptorSet* const* const descriptorSets);
62+
63+
bool reset();
6764

6865
inline uint32_t getCapacity() const { return m_maxSets; }
6966

7067
protected:
71-
explicit IDescriptorPool(core::smart_refctd_ptr<const ILogicalDevice>&& dev, const IDescriptorPool::E_CREATE_FLAGS flags, uint32_t _maxSets, const uint32_t poolSizeCount, const IDescriptorPool::SDescriptorPoolSize* poolSizes) : IBackendObject(std::move(dev)), m_maxSets(_maxSets), m_flags(flags)
68+
explicit IDescriptorPool(core::smart_refctd_ptr<const ILogicalDevice>&& dev, const IDescriptorPool::E_CREATE_FLAGS flags, uint32_t _maxSets,
69+
const uint32_t poolSizeCount, const IDescriptorPool::SDescriptorPoolSize* poolSizes)
70+
: IBackendObject(std::move(dev)), m_maxSets(_maxSets), m_flags(flags), m_version(0u)
7271
{
7372
std::fill_n(m_maxDescriptorCount, static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT), 0u);
7473

@@ -79,7 +78,7 @@ class NBL_API IDescriptorPool : public core::IReferenceCounted, public IBackendO
7978
m_descriptorAllocators[i] = std::make_unique<allocator_state_t>(m_maxDescriptorCount[i], m_flags & ECF_FREE_DESCRIPTOR_SET_BIT);
8079

8180
// For (possibly) mutable samplers.
82-
m_descriptorAllocators[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)] = std::make_unique<allocator_state_t>(m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)], m_flags & ECF_FREE_DESCRIPTOR_SET_BIT);
81+
m_descriptorAllocators[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)] = std::make_unique<allocator_state_t>(m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER)], m_flags & ECF_FREE_DESCRIPTOR_SET_BIT);
8382

8483
// Initialize the storages.
8584
m_textureStorage = std::make_unique<core::StorageTrivializer<core::smart_refctd_ptr<video::IGPUImageView>>[]>(m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER)]);
@@ -93,19 +92,65 @@ class NBL_API IDescriptorPool : public core::IReferenceCounted, public IBackendO
9392
virtual ~IDescriptorPool() {}
9493

9594
virtual bool createDescriptorSets_impl(uint32_t count, const IGPUDescriptorSetLayout* const* layouts, SDescriptorOffsets* const offsets, core::smart_refctd_ptr<IGPUDescriptorSet>* output) = 0;
96-
virtual bool freeDescriptorSets_impl(const uint32_t descriptorSetCount, IGPUDescriptorSet* const* const descriptorSets) = 0;
95+
96+
virtual bool reset_impl() = 0;
9797

9898
uint32_t m_maxSets;
9999

100100
private:
101+
inline core::smart_refctd_ptr<asset::IDescriptor>* getDescriptorStorage(const asset::IDescriptor::E_TYPE type) const
102+
{
103+
core::smart_refctd_ptr<asset::IDescriptor>* baseAddress;
104+
switch (type)
105+
{
106+
case asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER:
107+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_textureStorage.get());
108+
break;
109+
case asset::IDescriptor::E_TYPE::ET_STORAGE_IMAGE:
110+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_storageImageStorage.get());
111+
break;
112+
case asset::IDescriptor::E_TYPE::ET_UNIFORM_TEXEL_BUFFER:
113+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_UTB_STBStorage.get());
114+
break;
115+
case asset::IDescriptor::E_TYPE::ET_STORAGE_TEXEL_BUFFER:
116+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_UTB_STBStorage.get()) + m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_UNIFORM_TEXEL_BUFFER)];
117+
break;
118+
case asset::IDescriptor::E_TYPE::ET_UNIFORM_BUFFER:
119+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_UBO_SSBOStorage.get());
120+
break;
121+
case asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER:
122+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_UBO_SSBOStorage.get()) + m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_UNIFORM_BUFFER)];
123+
break;
124+
case asset::IDescriptor::E_TYPE::ET_UNIFORM_BUFFER_DYNAMIC:
125+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_UBO_SSBOStorage.get()) + (m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_UNIFORM_BUFFER)] + m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER)]);
126+
break;
127+
case asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER_DYNAMIC:
128+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_UBO_SSBOStorage.get()) + (m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_UNIFORM_BUFFER)] + m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER)] + m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_UNIFORM_BUFFER_DYNAMIC)]);
129+
break;
130+
case asset::IDescriptor::E_TYPE::ET_INPUT_ATTACHMENT:
131+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_storageImageStorage.get()) + m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_STORAGE_IMAGE)];
132+
break;
133+
case asset::IDescriptor::E_TYPE::ET_ACCELERATION_STRUCTURE:
134+
baseAddress = reinterpret_cast<core::smart_refctd_ptr<asset::IDescriptor>*>(m_accelerationStructureStorage.get());
135+
break;
136+
default:
137+
assert(!"Invalid code path.");
138+
return nullptr;
139+
}
140+
141+
return baseAddress;
142+
}
143+
144+
inline core::smart_refctd_ptr<IGPUSampler>* getMutableSamplerStorage() const
145+
{
146+
return reinterpret_cast<core::smart_refctd_ptr<IGPUSampler>*>(m_mutableSamplerStorage.get());
147+
}
148+
149+
friend class IGPUDescriptorSet;
101150
// Returns the offset into the pool's descriptor storage. These offsets will be combined
102151
// later with base memory addresses to get the actual memory address where we put the core::smart_refctd_ptr<const IDescriptor>.
103152
SDescriptorOffsets allocateDescriptorOffsets(const IGPUDescriptorSetLayout* layout);
104153

105-
const IDescriptorPool::E_CREATE_FLAGS m_flags;
106-
107-
uint32_t m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)];
108-
109154
struct allocator_state_t
110155
{
111156
allocator_state_t(const uint32_t maxDescriptorCount, const bool allowsFreeing)
@@ -145,6 +190,22 @@ class NBL_API IDescriptorPool : public core::IReferenceCounted, public IBackendO
145190
generalAllocator.free_addr(allocatedOffset, count);
146191
}
147192

193+
inline void reset(const bool allowsFreeing)
194+
{
195+
if (!allowsFreeing)
196+
linearAllocator.reset();
197+
else
198+
generalAllocator.reset();
199+
}
200+
201+
inline uint32_t getAllocatedDescriptorCount(const bool allowsFreeing) const
202+
{
203+
if (!allowsFreeing)
204+
return linearAllocator.get_allocated_size();
205+
else
206+
return generalAllocator.get_allocated_size();
207+
}
208+
148209
union
149210
{
150211
core::LinearAddressAllocator<uint32_t> linearAllocator;
@@ -154,6 +215,10 @@ class NBL_API IDescriptorPool : public core::IReferenceCounted, public IBackendO
154215
};
155216
std::unique_ptr<allocator_state_t> m_descriptorAllocators[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT) + 1];
156217

218+
const IDescriptorPool::E_CREATE_FLAGS m_flags;
219+
uint32_t m_maxDescriptorCount[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)];
220+
std::atomic_uint32_t m_version;
221+
157222
std::unique_ptr<core::StorageTrivializer<core::smart_refctd_ptr<video::IGPUImageView>>[]> m_textureStorage;
158223
std::unique_ptr<core::StorageTrivializer<core::smart_refctd_ptr<video::IGPUSampler>>[]> m_mutableSamplerStorage;
159224
std::unique_ptr<core::StorageTrivializer<core::smart_refctd_ptr<IGPUImageView>>[]> m_storageImageStorage; // storage image | input attachment

include/nbl/video/IGPUDescriptorSet.h

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,19 @@ class NBL_API IGPUDescriptorSet : public asset::IDescriptorSet<const IGPUDescrip
3333

3434
inline uint64_t getVersion() const { return m_version.load(); }
3535

36-
inline void incrementVersion() { m_version.fetch_add(1ull); }
37-
3836
inline IDescriptorPool* getPool() const { return m_pool.get(); }
3937

38+
inline bool isZombie() const
39+
{
40+
if (m_pool->m_version.load() > m_parentPoolVersion)
41+
return true;
42+
else
43+
return false;
44+
}
45+
4046
inline core::smart_refctd_ptr<asset::IDescriptor>* getAllDescriptors(const asset::IDescriptor::E_TYPE type) const
4147
{
42-
auto* baseAddress = getDescriptorStorage(type);
48+
auto* baseAddress = m_pool->getDescriptorStorage(type);
4349
if (baseAddress == nullptr)
4450
return nullptr;
4551

@@ -52,7 +58,7 @@ class NBL_API IGPUDescriptorSet : public asset::IDescriptorSet<const IGPUDescrip
5258

5359
inline core::smart_refctd_ptr<IGPUSampler>* getAllMutableSamplers() const
5460
{
55-
auto* baseAddress = getMutableSamplerStorage();
61+
auto* baseAddress = m_pool->getMutableSamplerStorage();
5662
if (baseAddress == nullptr)
5763
return nullptr;
5864

@@ -63,6 +69,52 @@ class NBL_API IGPUDescriptorSet : public asset::IDescriptorSet<const IGPUDescrip
6369
return baseAddress + poolOffset;
6470
}
6571

72+
inline uint32_t getDescriptorStorageOffset(const asset::IDescriptor::E_TYPE type) const { return m_descriptorStorageOffsets.data[static_cast<uint32_t>(type)]; }
73+
inline uint32_t getMutableSamplerStorageOffset() const { return m_descriptorStorageOffsets.data[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)]; }
74+
75+
protected:
76+
virtual ~IGPUDescriptorSet()
77+
{
78+
if (!isZombie())
79+
{
80+
const bool allowsFreeing = m_pool->m_flags & IDescriptorPool::ECF_FREE_DESCRIPTOR_SET_BIT;
81+
for (auto i = 0u; i < static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT); ++i)
82+
{
83+
// There is no descriptor of such type in the set.
84+
if (m_descriptorStorageOffsets.data[i] == ~0u)
85+
continue;
86+
87+
const auto type = static_cast<asset::IDescriptor::E_TYPE>(i);
88+
89+
const uint32_t allocatedOffset = getDescriptorStorageOffset(type);
90+
assert(allocatedOffset != ~0u);
91+
92+
const uint32_t count = m_layout->getTotalDescriptorCount(type);
93+
assert(count != 0u);
94+
95+
std::destroy_n(m_pool->getDescriptorStorage(type) + allocatedOffset, count);
96+
97+
if (allowsFreeing)
98+
m_pool->m_descriptorAllocators[i]->free(allocatedOffset, count);
99+
}
100+
101+
const auto mutableSamplerCount = m_layout->getTotalMutableSamplerCount();
102+
if (mutableSamplerCount > 0)
103+
{
104+
const uint32_t allocatedOffset = getMutableSamplerStorageOffset();
105+
assert(allocatedOffset != ~0u);
106+
107+
std::destroy_n(m_pool->getMutableSamplerStorage() + allocatedOffset, mutableSamplerCount);
108+
109+
if (allowsFreeing)
110+
m_pool->m_descriptorAllocators[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)]->free(allocatedOffset, mutableSamplerCount);
111+
}
112+
}
113+
}
114+
115+
private:
116+
friend class ILogicalDevice;
117+
66118
// This assumes that descriptors of a particular type in the set will always be contiguous in pool's storage memory, regardless of which binding in the set they belong to.
67119
inline core::smart_refctd_ptr<asset::IDescriptor>* getDescriptors(const asset::IDescriptor::E_TYPE type, const uint32_t binding) const
68120
{
@@ -90,32 +142,10 @@ class NBL_API IGPUDescriptorSet : public asset::IDescriptorSet<const IGPUDescrip
90142
return samplers + localOffset;
91143
}
92144

93-
inline uint32_t getDescriptorStorageOffset(const asset::IDescriptor::E_TYPE type) const { return m_descriptorStorageOffsets.data[static_cast<uint32_t>(type)]; }
94-
inline uint32_t getMutableSamplerStorageOffset() const { return m_descriptorStorageOffsets.data[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)]; }
95-
96-
protected:
97-
virtual ~IGPUDescriptorSet()
98-
{
99-
for (auto i = 0u; i < static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT); ++i)
100-
{
101-
// There is no descriptor of such type in the set.
102-
if (m_descriptorStorageOffsets.data[i] == ~0u)
103-
continue;
104-
105-
const auto type = static_cast<asset::IDescriptor::E_TYPE>(i);
106-
std::destroy_n(getDescriptorStorage(type) + m_descriptorStorageOffsets.data[i], m_layout->getTotalDescriptorCount(type));
107-
}
108-
109-
const auto mutableSamplerCount = m_layout->getTotalMutableSamplerCount();
110-
if (mutableSamplerCount > 0)
111-
std::destroy_n(getMutableSamplerStorage() + getMutableSamplerStorageOffset(), mutableSamplerCount);
112-
}
113-
114-
private:
115-
core::smart_refctd_ptr<asset::IDescriptor>* getDescriptorStorage(const asset::IDescriptor::E_TYPE type) const;
116-
core::smart_refctd_ptr<IGPUSampler>* getMutableSamplerStorage() const;
145+
inline void incrementVersion() { m_version.fetch_add(1ull); }
117146

118147
std::atomic_uint64_t m_version;
148+
const uint32_t m_parentPoolVersion;
119149
core::smart_refctd_ptr<IDescriptorPool> m_pool;
120150
IDescriptorPool::SDescriptorOffsets m_descriptorStorageOffsets;
121151
};

src/nbl/video/CVulkanDescriptorPool.cpp

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,12 @@ bool CVulkanDescriptorPool::createDescriptorSets_impl(uint32_t count, const IGPU
6060
return false;
6161
}
6262

63-
bool CVulkanDescriptorPool::freeDescriptorSets_impl(const uint32_t descriptorSetCount, IGPUDescriptorSet* const* const descriptorSets)
63+
bool CVulkanDescriptorPool::reset_impl()
6464
{
65-
core::vector<VkDescriptorSet> vk_descriptorSets(descriptorSetCount, VK_NULL_HANDLE);
66-
67-
for (auto i = 0; i < descriptorSetCount; ++i)
68-
{
69-
if (descriptorSets[i]->getAPIType() != EAT_VULKAN)
70-
return false;
71-
72-
vk_descriptorSets[i] = static_cast<CVulkanDescriptorSet*>(descriptorSets[i])->getInternalObject();
73-
}
74-
75-
const CVulkanLogicalDevice* vulkanDevice = static_cast<const CVulkanLogicalDevice*>(getOriginDevice());
65+
const auto* vulkanDevice = static_cast<const CVulkanLogicalDevice*>(getOriginDevice());
7666
auto* vk = vulkanDevice->getFunctionTable();
77-
return vk->vk.vkFreeDescriptorSets(vulkanDevice->getInternalObject(), m_descriptorPool, descriptorSetCount, vk_descriptorSets.data()) == VK_SUCCESS;
67+
const bool success = (vk->vk.vkResetDescriptorPool(vulkanDevice->getInternalObject(), m_descriptorPool, 0) == VK_SUCCESS);
68+
return success;
7869
}
7970

8071
}

src/nbl/video/CVulkanDescriptorPool.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class CVulkanDescriptorPool : public IDescriptorPool
2424

2525
private:
2626
bool createDescriptorSets_impl(uint32_t count, const IGPUDescriptorSetLayout* const* layouts, SDescriptorOffsets *const offsets, core::smart_refctd_ptr<IGPUDescriptorSet>* output) override;
27-
bool freeDescriptorSets_impl(const uint32_t descriptorSetCount, IGPUDescriptorSet* const* const descriptorSets) final override;
27+
bool reset_impl() override;
2828

2929
VkDescriptorPool m_descriptorPool;
3030
};

src/nbl/video/IDescriptorPool.cpp

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@ bool IDescriptorPool::createDescriptorSets(uint32_t count, const IGPUDescriptorS
1919
return createDescriptorSets_impl(count, layouts, descriptorOffsets.data(), output);
2020
}
2121

22+
bool IDescriptorPool::reset()
23+
{
24+
const bool allowsFreeing = m_flags & ECF_FREE_DESCRIPTOR_SET_BIT;
25+
for (uint32_t t = 0; t < static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT); ++t)
26+
{
27+
if (!m_descriptorAllocators[t])
28+
continue;
29+
30+
const uint32_t allocatedCount = m_descriptorAllocators[t]->getAllocatedDescriptorCount(allowsFreeing);
31+
std::destroy_n(getDescriptorStorage(static_cast<asset::IDescriptor::E_TYPE>(t)), allocatedCount);
32+
33+
m_descriptorAllocators[t]->reset(m_flags & ECF_FREE_DESCRIPTOR_SET_BIT);
34+
}
35+
36+
m_version.fetch_add(1u);
37+
38+
return reset_impl();
39+
}
40+
2241
IDescriptorPool::SDescriptorOffsets IDescriptorPool::allocateDescriptorOffsets(const IGPUDescriptorSetLayout* layout)
2342
{
2443
SDescriptorOffsets offsets;
@@ -42,52 +61,4 @@ IDescriptorPool::SDescriptorOffsets IDescriptorPool::allocateDescriptorOffsets(c
4261
return offsets;
4362
}
4463

45-
bool IDescriptorPool::freeDescriptorSets(const uint32_t descriptorSetCount, IGPUDescriptorSet* const* const descriptorSets)
46-
{
47-
const bool allowsFreeing = m_flags & ECF_FREE_DESCRIPTOR_SET_BIT;
48-
if (!allowsFreeing)
49-
return false;
50-
51-
for (auto i = 0u; i < descriptorSetCount; ++i)
52-
{
53-
for (auto t = 0u; t < static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT); ++i)
54-
{
55-
const auto type = static_cast<asset::IDescriptor::E_TYPE>(t);
56-
57-
const uint32_t allocatedOffset = descriptorSets[i]->getDescriptorStorageOffset(type);
58-
if (allocatedOffset == ~0u)
59-
continue;
60-
61-
const uint32_t count = descriptorSets[i]->getLayout()->getTotalDescriptorCount(type);
62-
assert(count != 0u);
63-
64-
auto* descriptors = descriptorSets[i]->getAllDescriptors(type);
65-
assert(descriptors);
66-
67-
for (auto c = 0u; c < count; ++c)
68-
descriptors[c].~smart_refctd_ptr();
69-
70-
m_descriptorAllocators[t]->free(allocatedOffset, count);
71-
}
72-
73-
const uint32_t count = descriptorSets[i]->getLayout()->getTotalMutableSamplerCount();
74-
if (count > 0)
75-
{
76-
const uint32_t allocatedOffset = descriptorSets[i]->getMutableSamplerStorageOffset();
77-
if (allocatedOffset == ~0u)
78-
continue;
79-
80-
auto* samplers = descriptorSets[i]->getAllMutableSamplers();
81-
assert(samplers);
82-
83-
for (auto c = 0u; c < count; ++c)
84-
samplers[c].~smart_refctd_ptr();
85-
86-
m_descriptorAllocators[static_cast<uint32_t>(asset::IDescriptor::E_TYPE::ET_COUNT)]->free(allocatedOffset, count);
87-
}
88-
}
89-
90-
return freeDescriptorSets_impl(descriptorSetCount, descriptorSets);
91-
}
92-
9364
}

0 commit comments

Comments
 (0)