Skip to content

Commit

Permalink
Support dynamically allocating descriptors when pool is exhausted.
Browse files Browse the repository at this point in the history
- MVKDescriptorTypePool if the pool is exhausted, report a warning,
  allocate a new descriptor instance, and when it is freed, destroy it.
- Deprecate MVKConfiguration::preallocateDescriptors and
  MVK_CONFIG_PREALLOCATE_DESCRIPTORS, as both preallocation
  and dynamic allocation of descriptors are now supported.
- vkAllocateDescriptorSets(), per Vulkan spec, if any descriptor set
  allocation fails, free any successful allocations, and populate all
  descriptor set pointers with VK_NULL_HANDLE.
- vkUpdateDescriptorSets() accepts null VkDescriptorSets as no-ops.
- MVKDescriptorPool::getLogDescription() include count of remaining
  descriptors in the pool, for diagnostic logging during debugging.
  • Loading branch information
billhollings committed Jul 25, 2024
1 parent bec3ee3 commit 7f8886f
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 73 deletions.
13 changes: 0 additions & 13 deletions Docs/MoltenVK_Configuration_Parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,19 +324,6 @@ You can also use the `MVK_CONFIG_ACTIVITY_PERFORMANCE_LOGGING_STYLE` and
`MVK_CONFIG_PERFORMANCE_LOGGING_FRAME_COUNT` parameters to configure when to log the performance statistics collected by this parameter.


---------------------------------------
#### MVK_CONFIG_PREALLOCATE_DESCRIPTORS

##### Type: Boolean
##### Default: `1`

Controls whether **MoltenVK** should preallocate memory in each `VkDescriptorPool` according
to the values of the `VkDescriptorPoolSize` parameters. Doing so may improve descriptor set
allocation performance and memory stability at a cost of preallocated application memory.
If this setting is disabled, the descriptors required for a descriptor set will be individually
dynamically allocated in application memory when the descriptor set itself is allocated.


---------------------------------------
#### MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS

Expand Down
4 changes: 4 additions & 0 deletions Docs/Whats_New.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ MoltenVK 1.2.11

Released TBD

- Support dynamically allocating descriptors when pool is exhausted.
- Deprecate `MVKConfiguration::preallocateDescriptors` and `MVK_CONFIG_PREALLOCATE_DESCRIPTORS` environment variable.
- vkAllocateDescriptorSets(), per Vulkan spec, if any descriptor set allocation fails,
populate all descriptor set pointers with `VK_NULL_HANDLE`.
- Fix rendering issue with render pass that immediately follows a kernel dispatch.


Expand Down
2 changes: 1 addition & 1 deletion MoltenVK/MoltenVK/API/mvk_private_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ typedef struct {
MVKConfigAutoGPUCaptureScope autoGPUCaptureScope; /**< MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE */
const char* autoGPUCaptureOutputFilepath; /**< MVK_CONFIG_AUTO_GPU_CAPTURE_OUTPUT_FILE */
VkBool32 texture1DAs2D; /**< MVK_CONFIG_TEXTURE_1D_AS_2D */
VkBool32 preallocateDescriptors; /**< MVK_CONFIG_PREALLOCATE_DESCRIPTORS */
VkBool32 preallocateDescriptors; /**< Obsolete, deprecated, and ignored. */
VkBool32 useCommandPooling; /**< MVK_CONFIG_USE_COMMAND_POOLING */
VkBool32 useMTLHeap; /**< MVK_CONFIG_USE_MTLHEAP */
MVKConfigActivityPerformanceLoggingStyle activityPerformanceLoggingStyle; /**< MVK_CONFIG_ACTIVITY_PERFORMANCE_LOGGING_STYLE */
Expand Down
9 changes: 5 additions & 4 deletions MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,10 @@ class MVKDescriptorSet : public MVKVulkanAPIDeviceObject {
void setBufferSize(uint32_t descIdx, uint32_t value);

MVKDescriptorPool* _pool;
MVKDescriptorSetLayout* _layout;
MVKDescriptorSetLayout* _layout = nullptr;
MVKMTLBufferAllocation* _bufferSizesBuffer = nullptr;
MVKSmallVector<MVKDescriptor*> _descriptors;
MVKMetalArgumentBuffer _argumentBuffer;
MVKMTLBufferAllocation* _bufferSizesBuffer = nullptr;
uint32_t _dynamicOffsetDescriptorCount;
uint32_t _variableDescriptorCount;
};
Expand All @@ -242,9 +242,11 @@ class MVKDescriptorTypePool : public MVKBaseObject {
protected:
friend class MVKDescriptorPool;

VkResult allocateDescriptor(MVKDescriptor** pMVKDesc, MVKDescriptorPool* pool);
VkResult allocateDescriptor(MVKDescriptor** pMVKDesc, MVKDescriptorPool* pool, VkDescriptorType descType);
void freeDescriptor(MVKDescriptor* mvkDesc, MVKDescriptorPool* pool);
void reset();
size_t size() { return _availability.size(); }
size_t getRemainingDescriptorCount();

MVKSmallVector<DescriptorClass> _descriptors;
MVKBitArray _availability;
Expand Down Expand Up @@ -294,7 +296,6 @@ class MVKDescriptorPool : public MVKVulkanAPIDeviceObject {
size_t getPoolSize(const VkDescriptorPoolCreateInfo* pCreateInfo, VkDescriptorType descriptorType);
std::string getLogDescription();

bool _hasPooledDescriptors;
MVKSmallVector<MVKDescriptorSet> _descriptorSets;
MVKBitArray _descriptorSetAvailablility;
id<MTLBuffer> _metalArgumentBuffer;
Expand Down
121 changes: 68 additions & 53 deletions MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
Original file line number Diff line number Diff line change
Expand Up @@ -521,10 +521,7 @@ static void populateAuxBuffer(mvk::SPIRVToMSLConversionConfiguration& shaderConf

if (isPoolReset) { _argumentBuffer.setArgumentBuffer(_pool->_metalArgumentBuffer, 0, nil); }

// Pooled descriptors don't need to be individually freed under pool resets.
if ( !(_pool->_hasPooledDescriptors && isPoolReset) ) {
for (auto mvkDesc : _descriptors) { _pool->freeDescriptor(mvkDesc); }
}
for (auto mvkDesc : _descriptors) { _pool->freeDescriptor(mvkDesc); }
_descriptors.clear();
_descriptors.shrink_to_fit();

Expand Down Expand Up @@ -557,33 +554,35 @@ static void populateAuxBuffer(mvk::SPIRVToMSLConversionConfiguration& shaderConf
#pragma mark -
#pragma mark MVKDescriptorTypePool

// If preallocated, find the next availalble descriptor.
// If not preallocated, create one on the fly.
// Find the next availalble descriptor in the pool, or if the pool is exhausted, create one on the fly.
template<class DescriptorClass>
VkResult MVKDescriptorTypePool<DescriptorClass>::allocateDescriptor(MVKDescriptor** pMVKDesc,
MVKDescriptorPool* pool) {
MVKDescriptorPool* pool,
VkDescriptorType descType) {
DescriptorClass* mvkDesc;
if (pool->_hasPooledDescriptors) {
size_t availDescIdx = _availability.getIndexOfFirstSetBit(true);
if (availDescIdx >= _availability.size()) { return VK_ERROR_OUT_OF_POOL_MEMORY; }
size_t availDescIdx = _availability.getIndexOfFirstSetBit(true);
if (availDescIdx < size()) {
mvkDesc = &_descriptors[availDescIdx];
mvkDesc->reset(); // Clear before reusing.
} else {
reportWarning(VK_ERROR_OUT_OF_POOL_MEMORY, "VkDescriptorPool exhausted pool of %zu %s descriptors. Allocating dynamically.", size(), mvkVkDescriptorTypeName(descType));
mvkDesc = new DescriptorClass();
}
*pMVKDesc = mvkDesc;
return VK_SUCCESS;
}

// If preallocated, descriptors are held in contiguous memory, so the index of the returning
// descriptor can be calculated by pointer differences, and it can be marked as available.
// The descriptor will be reset when it is re-allocated. This streamlines the reset() of this pool.
// If not preallocated, simply destroy returning descriptor.
// If the descriptor is from the pool, mark it as available, otherwise destroy it.
// Pooled descriptors are held in contiguous memory, so the index of the returning
// descriptor can be calculated by typed pointer differences. The descriptor will
// be reset when it is re-allocated. This streamlines a pool reset().
template<typename DescriptorClass>
void MVKDescriptorTypePool<DescriptorClass>::freeDescriptor(MVKDescriptor* mvkDesc,
MVKDescriptorPool* pool) {
if (pool->_hasPooledDescriptors) {
size_t descIdx = (DescriptorClass*)mvkDesc - _descriptors.data();
DescriptorClass* pDesc = (DescriptorClass*)mvkDesc;
DescriptorClass* pFirstDesc = _descriptors.data();
int64_t descIdx = pDesc >= pFirstDesc ? pDesc - pFirstDesc : pFirstDesc - pDesc;
if (descIdx >= 0 && descIdx < size()) {
_availability.setBit(descIdx);
} else {
mvkDesc->destroy();
Expand All @@ -596,6 +595,13 @@ static void populateAuxBuffer(mvk::SPIRVToMSLConversionConfiguration& shaderConf
_availability.setAllBits();
}

template<typename DescriptorClass>
size_t MVKDescriptorTypePool<DescriptorClass>::getRemainingDescriptorCount() {
size_t enabledCount = 0;
_availability.enumerateEnabledBits(false, [&](size_t bitIdx) { enabledCount++; return true; });
return enabledCount;
}

template<typename DescriptorClass>
MVKDescriptorTypePool<DescriptorClass>::MVKDescriptorTypePool(size_t poolSize) :
_descriptors(poolSize),
Expand All @@ -620,11 +626,20 @@ static void populateAuxBuffer(mvk::SPIRVToMSLConversionConfiguration& shaderConf
}

@autoreleasepool {
for (uint32_t dsIdx = 0; dsIdx < pAllocateInfo->descriptorSetCount; dsIdx++) {
auto dsCnt = pAllocateInfo->descriptorSetCount;
for (uint32_t dsIdx = 0; dsIdx < dsCnt; dsIdx++) {
MVKDescriptorSetLayout* mvkDSL = (MVKDescriptorSetLayout*)pAllocateInfo->pSetLayouts[dsIdx];
if ( !mvkDSL->_isPushDescriptorLayout ) {
VkResult rslt = allocateDescriptorSet(mvkDSL, (pVarDescCounts ? pVarDescCounts[dsIdx] : 0), &pDescriptorSets[dsIdx]);
if (rslt) { return rslt; }
if (rslt) {
// Per Vulkan spec, if any descriptor set allocation fails, free any successful
// allocations, and populate all descriptor set pointers with VK_NULL_HANDLE.
freeDescriptorSets(dsIdx, pDescriptorSets);
for (uint32_t i = 0; i < dsCnt; i++) { pDescriptorSets[i] = VK_NULL_HANDLE; }
return rslt;
}
} else {
pDescriptorSets[dsIdx] = VK_NULL_HANDLE;
}
}
}
Expand Down Expand Up @@ -750,40 +765,40 @@ static void populateAuxBuffer(mvk::SPIRVToMSLConversionConfiguration& shaderConf
MVKDescriptor** pMVKDesc) {
switch (descriptorType) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
return _uniformBufferDescriptors.allocateDescriptor(pMVKDesc, this);
return _uniformBufferDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
return _storageBufferDescriptors.allocateDescriptor(pMVKDesc, this);
return _storageBufferDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
return _uniformBufferDynamicDescriptors.allocateDescriptor(pMVKDesc, this);
return _uniformBufferDynamicDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
return _storageBufferDynamicDescriptors.allocateDescriptor(pMVKDesc, this);
return _storageBufferDynamicDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
return _inlineUniformBlockDescriptors.allocateDescriptor(pMVKDesc, this);
return _inlineUniformBlockDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
return _sampledImageDescriptors.allocateDescriptor(pMVKDesc, this);
return _sampledImageDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
return _storageImageDescriptors.allocateDescriptor(pMVKDesc, this);
return _storageImageDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
return _inputAttachmentDescriptors.allocateDescriptor(pMVKDesc, this);
return _inputAttachmentDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_SAMPLER:
return _samplerDescriptors.allocateDescriptor(pMVKDesc, this);
return _samplerDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
return _combinedImageSamplerDescriptors.allocateDescriptor(pMVKDesc, this);
return _combinedImageSamplerDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
return _uniformTexelBufferDescriptors.allocateDescriptor(pMVKDesc, this);
return _uniformTexelBufferDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
return _storageTexelBufferDescriptors.allocateDescriptor(pMVKDesc, this);
return _storageTexelBufferDescriptors.allocateDescriptor(pMVKDesc, this, descriptorType);

default:
return reportError(VK_ERROR_INITIALIZATION_FAILED, "Unrecognized VkDescriptorType %d.", descriptorType);
Expand Down Expand Up @@ -834,17 +849,12 @@ static void populateAuxBuffer(mvk::SPIRVToMSLConversionConfiguration& shaderConf
}
}

// Return the size of the preallocated pool for descriptors of the specified type,
// or zero if we are not preallocating descriptors in the pool.
// Return the size of the preallocated pool for descriptors of the specified type.
// There may be more than one poolSizeCount instance for the desired VkDescriptorType.
// Accumulate the descriptor count for the desired VkDescriptorType accordingly.
// For descriptors of the VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT type,
// we accumulate the count via the pNext chain.
size_t MVKDescriptorPool::getPoolSize(const VkDescriptorPoolCreateInfo* pCreateInfo,
VkDescriptorType descriptorType) {

if ( !_hasPooledDescriptors ) { return 0; }

size_t MVKDescriptorPool::getPoolSize(const VkDescriptorPoolCreateInfo* pCreateInfo, VkDescriptorType descriptorType) {
uint32_t descCnt = 0;
if (descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
for (const auto* next = (VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) {
Expand All @@ -868,26 +878,28 @@ static void populateAuxBuffer(mvk::SPIRVToMSLConversionConfiguration& shaderConf
}

std::string MVKDescriptorPool::getLogDescription() {
#define STR(name) #name
#define printDescCnt(descType, spacing, descPool) descStr << "\n\t" STR(VK_DESCRIPTOR_TYPE_##descType) ": " spacing << _##descPool##Descriptors.size() << " (" << _##descPool##Descriptors.getRemainingDescriptorCount() << " remaining)";

std::stringstream descStr;
descStr << "VkDescriptorPool " << this << " with " << _descriptorSetAvailablility.size() << " descriptor sets, and descriptors:";
descStr << "\n\tVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: " << _uniformBufferDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_STORAGE_BUFFER: " << _storageBufferDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: " << _uniformBufferDynamicDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: " << _storageBufferDynamicDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT: " << _inlineUniformBlockDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: " << _sampledImageDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_STORAGE_IMAGE: " << _storageImageDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: " << _inputAttachmentDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_SAMPLER: " << _samplerDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: " << _combinedImageSamplerDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: " << _uniformTexelBufferDescriptors._availability.size();
descStr << "\n\tVK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: " << _storageTexelBufferDescriptors._availability.size();
descStr << "VkDescriptorPool " << this << " with " << _descriptorSetAvailablility.size() << " descriptor sets, and pooled descriptors:";
printDescCnt(UNIFORM_BUFFER, " ", uniformBuffer);
printDescCnt(STORAGE_BUFFER, " ", storageBuffer);
printDescCnt(UNIFORM_BUFFER_DYNAMIC, " ", uniformBufferDynamic);
printDescCnt(STORAGE_BUFFER_DYNAMIC, " ", storageBufferDynamic);
printDescCnt(INLINE_UNIFORM_BLOCK_EXT, "", inlineUniformBlock);
printDescCnt(SAMPLED_IMAGE, " ", sampledImage);
printDescCnt(STORAGE_IMAGE, " ", storageImage);
printDescCnt(INPUT_ATTACHMENT, " ", inputAttachment);
printDescCnt(SAMPLER, " ", sampler);
printDescCnt(COMBINED_IMAGE_SAMPLER, " ", combinedImageSampler);
printDescCnt(UNIFORM_TEXEL_BUFFER, " ", uniformTexelBuffer);
printDescCnt(STORAGE_TEXEL_BUFFER, " ", storageTexelBuffer);
return descStr.str();
}

MVKDescriptorPool::MVKDescriptorPool(MVKDevice* device, const VkDescriptorPoolCreateInfo* pCreateInfo) :
MVKVulkanAPIDeviceObject(device),
_hasPooledDescriptors(getMVKConfig().preallocateDescriptors), // Set this first! Accessed by MVKDescriptorSet constructor and getPoolSize() in following lines.
_descriptorSets(pCreateInfo->maxSets, MVKDescriptorSet(this)),
_descriptorSetAvailablility(pCreateInfo->maxSets, true),
_mtlBufferAllocator(_device, getMetalFeatures().maxMTLBufferSize, true),
Expand Down Expand Up @@ -1096,6 +1108,8 @@ void mvkUpdateDescriptorSets(uint32_t writeCount,
size_t stride;
MVKDescriptorSet* dstSet = (MVKDescriptorSet*)pDescWrite->dstSet;

if( !dstSet ) { continue; } // Nulls are permitted

const VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock = nullptr;
if (dstSet->getEnabledExtensions().vk_EXT_inline_uniform_block.enabled) {
for (const auto* next = (VkBaseInStructure*)pDescWrite->pNext; next; next = next->pNext) {
Expand Down Expand Up @@ -1134,9 +1148,10 @@ void mvkUpdateDescriptorSets(uint32_t writeCount,
inlineUniformBlock.dataSize = descCnt;

MVKDescriptorSet* srcSet = (MVKDescriptorSet*)pDescCopy->srcSet;
srcSet->read(pDescCopy, imgInfos, buffInfos, texelBuffInfos, &inlineUniformBlock);

MVKDescriptorSet* dstSet = (MVKDescriptorSet*)pDescCopy->dstSet;
if( !srcSet || !dstSet ) { continue; } // Nulls are permitted

srcSet->read(pDescCopy, imgInfos, buffInfos, texelBuffInfos, &inlineUniformBlock);
VkDescriptorType descType = dstSet->getDescriptorType(pDescCopy->dstBinding);
size_t stride;
const void* pData = getWriteParameters(descType, imgInfos, buffInfos, texelBuffInfos, &inlineUniformBlock, stride);
Expand Down
2 changes: 1 addition & 1 deletion MoltenVK/MoltenVK/Utility/MVKConfigMembers.def
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ MVK_CONFIG_MEMBER(semaphoreSupportStyle, MVKVkSemaphoreSupportS
MVK_CONFIG_MEMBER(autoGPUCaptureScope, MVKConfigAutoGPUCaptureScope, AUTO_GPU_CAPTURE_SCOPE)
MVK_CONFIG_MEMBER_STRING(autoGPUCaptureOutputFilepath, const char*, AUTO_GPU_CAPTURE_OUTPUT_FILE)
MVK_CONFIG_MEMBER(texture1DAs2D, VkBool32, TEXTURE_1D_AS_2D)
MVK_CONFIG_MEMBER(preallocateDescriptors, VkBool32, PREALLOCATE_DESCRIPTORS)
MVK_CONFIG_MEMBER(preallocateDescriptors, VkBool32, PREALLOCATE_DESCRIPTORS) // Deprecated legacy
MVK_CONFIG_MEMBER(useCommandPooling, VkBool32, USE_COMMAND_POOLING)
MVK_CONFIG_MEMBER(useMTLHeap, VkBool32, USE_MTLHEAP)
MVK_CONFIG_MEMBER(apiVersionToAdvertise, uint32_t, API_VERSION_TO_ADVERTISE)
Expand Down
2 changes: 1 addition & 1 deletion MoltenVK/MoltenVK/Utility/MVKEnvironment.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ void mvkSetConfig(MVKConfiguration& dstMVKConfig, const MVKConfiguration& srcMVK
# define MVK_CONFIG_TEXTURE_1D_AS_2D 1
#endif

/** Preallocate descriptors when creating VkDescriptorPool. Enabled by default. */
/** Obsolete, deprecated, and ignored. */
#ifndef MVK_CONFIG_PREALLOCATE_DESCRIPTORS
# define MVK_CONFIG_PREALLOCATE_DESCRIPTORS 1
#endif
Expand Down

0 comments on commit 7f8886f

Please sign in to comment.