blob: ff9fcd507d96c66c04f59ba42d0f13d25bba943c [file] [log] [blame]
/*
* MVKDescriptorSet.mm
*
* Copyright (c) 2015-2021 The Brenwill Workshop Ltd. (http://www.brenwill.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MVKDescriptorSet.h"
#include "MVKCommandBuffer.h"
#include "MVKCommandEncoderState.h"
#include "MVKPipeline.h"
#include "MVKInstance.h"
#include "MVKOSExtensions.h"
#pragma mark -
#pragma mark MVKDescriptorSetLayout
// A null cmdEncoder can be passed to perform a validation pass
void MVKDescriptorSetLayout::bindDescriptorSet(MVKCommandEncoder* cmdEncoder,
VkPipelineBindPoint pipelineBindPoint,
uint32_t descSetIndex,
MVKDescriptorSet* descSet,
MVKShaderResourceBinding& dslMTLRezIdxOffsets,
MVKArrayRef<uint32_t> dynamicOffsets,
uint32_t& dynamicOffsetIndex) {
if (!cmdEncoder) { clearConfigurationResult(); }
if (_isPushDescriptorLayout ) { return; }
if (cmdEncoder) { cmdEncoder->bindDescriptorSet(pipelineBindPoint, descSetIndex,
descSet, dslMTLRezIdxOffsets,
dynamicOffsets, dynamicOffsetIndex); }
if ( !isUsingMetalArgumentBuffers() ) {
for (auto& dslBind : _bindings) {
dslBind.bind(cmdEncoder, descSet, dslMTLRezIdxOffsets, dynamicOffsets, dynamicOffsetIndex);
}
}
}
static const void* getWriteParameters(VkDescriptorType type, const VkDescriptorImageInfo* pImageInfo,
const VkDescriptorBufferInfo* pBufferInfo, const VkBufferView* pTexelBufferView,
const VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock,
size_t& stride) {
const void* pData;
switch (type) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
pData = pBufferInfo;
stride = sizeof(VkDescriptorBufferInfo);
break;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
pData = pImageInfo;
stride = sizeof(VkDescriptorImageInfo);
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
pData = pTexelBufferView;
stride = sizeof(MVKBufferView*);
break;
case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
pData = pInlineUniformBlock;
stride = sizeof(VkWriteDescriptorSetInlineUniformBlockEXT);
break;
default:
pData = nullptr;
stride = 0;
}
return pData;
}
// A null cmdEncoder can be passed to perform a validation pass
void MVKDescriptorSetLayout::pushDescriptorSet(MVKCommandEncoder* cmdEncoder,
MVKArrayRef<VkWriteDescriptorSet>& descriptorWrites,
MVKShaderResourceBinding& dslMTLRezIdxOffsets) {
if (!_isPushDescriptorLayout) return;
if (!cmdEncoder) { clearConfigurationResult(); }
for (const VkWriteDescriptorSet& descWrite : descriptorWrites) {
uint32_t dstBinding = descWrite.dstBinding;
uint32_t dstArrayElement = descWrite.dstArrayElement;
uint32_t descriptorCount = descWrite.descriptorCount;
const VkDescriptorImageInfo* pImageInfo = descWrite.pImageInfo;
const VkDescriptorBufferInfo* pBufferInfo = descWrite.pBufferInfo;
const VkBufferView* pTexelBufferView = descWrite.pTexelBufferView;
const VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock = nullptr;
if (_device->_enabledExtensions.vk_EXT_inline_uniform_block.enabled) {
for (const auto* next = (VkBaseInStructure*)descWrite.pNext; next; next = next->pNext) {
switch (next->sType) {
case VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT: {
pInlineUniformBlock = (VkWriteDescriptorSetInlineUniformBlockEXT*)next;
break;
}
default:
break;
}
}
}
if (!_bindingToIndex.count(dstBinding)) continue;
// Note: This will result in us walking off the end of the array
// in case there are too many updates... but that's ill-defined anyway.
for (; descriptorCount; dstBinding++) {
if (!_bindingToIndex.count(dstBinding)) continue;
size_t stride;
const void* pData = getWriteParameters(descWrite.descriptorType, pImageInfo,
pBufferInfo, pTexelBufferView, pInlineUniformBlock, stride);
uint32_t descriptorsPushed = 0;
uint32_t bindIdx = _bindingToIndex[dstBinding];
_bindings[bindIdx].push(cmdEncoder, dstArrayElement, descriptorCount,
descriptorsPushed, descWrite.descriptorType,
stride, pData, dslMTLRezIdxOffsets);
pBufferInfo += descriptorsPushed;
pImageInfo += descriptorsPushed;
pTexelBufferView += descriptorsPushed;
}
}
}
// A null cmdEncoder can be passed to perform a validation pass
void MVKDescriptorSetLayout::pushDescriptorSet(MVKCommandEncoder* cmdEncoder,
MVKDescriptorUpdateTemplate* descUpdateTemplate,
const void* pData,
MVKShaderResourceBinding& dslMTLRezIdxOffsets) {
if (!_isPushDescriptorLayout ||
descUpdateTemplate->getType() != VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS_KHR)
return;
if (!cmdEncoder) { clearConfigurationResult(); }
for (uint32_t i = 0; i < descUpdateTemplate->getNumberOfEntries(); i++) {
const VkDescriptorUpdateTemplateEntryKHR* pEntry = descUpdateTemplate->getEntry(i);
uint32_t dstBinding = pEntry->dstBinding;
uint32_t dstArrayElement = pEntry->dstArrayElement;
uint32_t descriptorCount = pEntry->descriptorCount;
const void* pCurData = (const char*)pData + pEntry->offset;
if (!_bindingToIndex.count(dstBinding)) continue;
// Note: This will result in us walking off the end of the array
// in case there are too many updates... but that's ill-defined anyway.
for (; descriptorCount; dstBinding++) {
if (!_bindingToIndex.count(dstBinding)) continue;
uint32_t descriptorsPushed = 0;
uint32_t bindIdx = _bindingToIndex[dstBinding];
_bindings[bindIdx].push(cmdEncoder, dstArrayElement, descriptorCount,
descriptorsPushed, pEntry->descriptorType,
pEntry->stride, pCurData, dslMTLRezIdxOffsets);
pCurData = (const char*)pCurData + pEntry->stride * descriptorsPushed;
}
}
}
void MVKDescriptorSetLayout::populateShaderConversionConfig(mvk::SPIRVToMSLConversionConfiguration& shaderConfig,
MVKShaderResourceBinding& dslMTLRezIdxOffsets,
uint32_t descSetIndex) {
uint32_t bindCnt = (uint32_t)_bindings.size();
for (uint32_t bindIdx = 0; bindIdx < bindCnt; bindIdx++) {
_bindings[bindIdx].populateShaderConversionConfig(shaderConfig, dslMTLRezIdxOffsets, descSetIndex);
}
// Mark if Metal argument buffers are in use, but this descriptor set layout is not using them.
if (isUsingMetalArgumentBuffers() && !isUsingMetalArgumentBuffer()) {
shaderConfig.discreteDescriptorSets.push_back(descSetIndex);
}
}
bool MVKDescriptorSetLayout::populateBindingUse(MVKBitArray& bindingUse,
SPIRVToMSLConversionConfiguration& context,
MVKShaderStage stage,
uint32_t descSetIndex) {
static const spv::ExecutionModel spvExecModels[] = {
spv::ExecutionModelVertex,
spv::ExecutionModelTessellationControl,
spv::ExecutionModelTessellationEvaluation,
spv::ExecutionModelFragment,
spv::ExecutionModelGLCompute
};
bool descSetIsUsed = false;
uint32_t bindCnt = (uint32_t)_bindings.size();
bindingUse.resize(bindCnt);
for (uint32_t bindIdx = 0; bindIdx < bindCnt; bindIdx++) {
auto& dslBind = _bindings[bindIdx];
if (context.isResourceUsed(spvExecModels[stage], descSetIndex, dslBind.getBinding())) {
bindingUse.setBit(bindIdx);
descSetIsUsed = true;
}
}
return descSetIsUsed;
}
MVKDescriptorSetLayout::MVKDescriptorSetLayout(MVKDevice* device,
const VkDescriptorSetLayoutCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
uint32_t bindCnt = pCreateInfo->bindingCount;
const auto* pBindingFlags = getBindingFlags(pCreateInfo);
// The bindings in VkDescriptorSetLayoutCreateInfo do not need to provided in order of binding number.
// However, several subsequent operations, such as the dynamic offsets in vkCmdBindDescriptorSets()
// are ordered by binding number. To prepare for this, sort the bindings by binding number.
struct BindInfo {
const VkDescriptorSetLayoutBinding* pBinding;
VkDescriptorBindingFlags bindingFlags;
};
MVKSmallVector<BindInfo, 64> sortedBindings;
sortedBindings.reserve(bindCnt);
for (uint32_t bindIdx = 0; bindIdx < bindCnt; bindIdx++) {
sortedBindings.push_back( { &pCreateInfo->pBindings[bindIdx], pBindingFlags ? pBindingFlags[bindIdx] : 0 } );
}
std::sort(sortedBindings.begin(), sortedBindings.end(), [](BindInfo bindInfo1, BindInfo bindInfo2) {
return bindInfo1.pBinding->binding < bindInfo2.pBinding->binding;
});
_descriptorCount = 0;
_isPushDescriptorLayout = mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR);
_bindings.reserve(bindCnt);
for (uint32_t bindIdx = 0; bindIdx < bindCnt; bindIdx++) {
BindInfo& bindInfo = sortedBindings[bindIdx];
_bindings.emplace_back(_device, this, bindInfo.pBinding, bindInfo.bindingFlags, _descriptorCount);
_bindingToIndex[bindInfo.pBinding->binding] = bindIdx;
_descriptorCount += _bindings.back().getDescriptorCount();
}
initMTLArgumentEncoder();
}
// Find and return an array of binding flags from the pNext chain of pCreateInfo,
// or return nullptr if the chain does not include binding flags.
const VkDescriptorBindingFlags* MVKDescriptorSetLayout::getBindingFlags(const VkDescriptorSetLayoutCreateInfo* pCreateInfo) {
for (const auto* next = (VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) {
switch (next->sType) {
case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT: {
auto* pDescSetLayoutBindingFlags = (VkDescriptorSetLayoutBindingFlagsCreateInfoEXT*)next;
return pDescSetLayoutBindingFlags->bindingCount ? pDescSetLayoutBindingFlags->pBindingFlags : nullptr;
}
default:
break;
}
}
return nullptr;
}
void MVKDescriptorSetLayout::initMTLArgumentEncoder() {
if (isUsingDescriptorSetMetalArgumentBuffers() && isUsingMetalArgumentBuffer()) {
@autoreleasepool {
NSMutableArray<MTLArgumentDescriptor*>* args = [NSMutableArray arrayWithCapacity: _bindings.size()];
for (auto& dslBind : _bindings) { dslBind.addMTLArgumentDescriptors(args); }
_mtlArgumentEncoder.init(args.count ? [getMTLDevice() newArgumentEncoderWithArguments: args] : nil);
}
}
}
#pragma mark -
#pragma mark MVKDescriptorSet
VkDescriptorType MVKDescriptorSet::getDescriptorType(uint32_t binding) {
return _layout->getBinding(binding)->getDescriptorType();
}
MVKDescriptor* MVKDescriptorSet::getDescriptor(uint32_t binding, uint32_t elementIndex) {
return _descriptors[_layout->getDescriptorIndex(binding, elementIndex)];
}
id<MTLBuffer> MVKDescriptorSet::getMetalArgumentBuffer() { return _pool->_metalArgumentBuffer; }
template<typename DescriptorAction>
void MVKDescriptorSet::write(const DescriptorAction* pDescriptorAction,
size_t stride,
const void* pData) {
#define writeDescriptorAt(IDX) \
do { \
MVKDescriptor* mvkDesc = _descriptors[descIdx]; \
if (mvkDesc->getDescriptorType() == descType) { \
mvkDesc->write(mvkDSLBind, this, IDX, stride, pData); \
_metalArgumentBufferDirtyDescriptors.setBit(descIdx); \
} \
} while(false)
MVKDescriptorSetLayoutBinding* mvkDSLBind = _layout->getBinding(pDescriptorAction->dstBinding);
VkDescriptorType descType = mvkDSLBind->getDescriptorType();
if (descType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
// For inline buffers dstArrayElement is a byte offset
uint32_t descIdx = _layout->getDescriptorIndex(pDescriptorAction->dstBinding);
writeDescriptorAt(pDescriptorAction->dstArrayElement);
} else {
uint32_t descStartIdx = _layout->getDescriptorIndex(pDescriptorAction->dstBinding, pDescriptorAction->dstArrayElement);
uint32_t elemCnt = pDescriptorAction->descriptorCount;
for (uint32_t elemIdx = 0; elemIdx < elemCnt; elemIdx++) {
uint32_t descIdx = descStartIdx + elemIdx;
writeDescriptorAt(elemIdx);
}
}
}
void MVKDescriptorSet::read(const VkCopyDescriptorSet* pDescriptorCopy,
VkDescriptorImageInfo* pImageInfo,
VkDescriptorBufferInfo* pBufferInfo,
VkBufferView* pTexelBufferView,
VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
MVKDescriptorSetLayoutBinding* mvkDSLBind = _layout->getBinding(pDescriptorCopy->srcBinding);
VkDescriptorType descType = mvkDSLBind->getDescriptorType();
uint32_t descCnt = pDescriptorCopy->descriptorCount;
if (descType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
// For inline buffers srcArrayElement is a byte offset
MVKDescriptor* mvkDesc = getDescriptor(pDescriptorCopy->srcBinding);
if (mvkDesc->getDescriptorType() == descType) {
mvkDesc->read(mvkDSLBind, this, pDescriptorCopy->srcArrayElement, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
}
} else {
uint32_t srcStartIdx = _layout->getDescriptorIndex(pDescriptorCopy->srcBinding, pDescriptorCopy->srcArrayElement);
for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
MVKDescriptor* mvkDesc = _descriptors[srcStartIdx + descIdx];
if (mvkDesc->getDescriptorType() == descType) {
mvkDesc->read(mvkDSLBind, this, descIdx, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
}
}
}
}
const MVKMTLBufferAllocation* MVKDescriptorSet::acquireMTLBufferRegion(NSUInteger length) {
return _pool->_inlineBlockMTLBufferAllocator.acquireMTLBufferRegion(length);
}
VkResult MVKDescriptorSet::allocate(MVKDescriptorSetLayout* layout,
uint32_t variableDescriptorCount,
NSUInteger mtlArgBufferOffset) {
_layout = layout;
_variableDescriptorCount = variableDescriptorCount;
// If the Metal argument buffer offset has not been set yet, set it now.
if ( !_metalArgumentBufferOffset ) { _metalArgumentBufferOffset = mtlArgBufferOffset; }
uint32_t descCnt = layout->getDescriptorCount();
_descriptors.reserve(descCnt);
_metalArgumentBufferDirtyDescriptors.resize(descCnt);
uint32_t bindCnt = (uint32_t)layout->_bindings.size();
for (uint32_t bindIdx = 0; bindIdx < bindCnt; bindIdx++) {
MVKDescriptorSetLayoutBinding* mvkDSLBind = &layout->_bindings[bindIdx];
uint32_t elemCnt = mvkDSLBind->getDescriptorCount(this);
for (uint32_t elemIdx = 0; elemIdx < elemCnt; elemIdx++) {
VkDescriptorType descType = mvkDSLBind->getDescriptorType();
uint32_t descIdx = (uint32_t)_descriptors.size();
MVKDescriptor* mvkDesc = nullptr;
setConfigurationResult(_pool->allocateDescriptor(descType, &mvkDesc));
if ( !wasConfigurationSuccessful() ) { return getConfigurationResult(); }
if (mvkDesc->usesDynamicBufferOffsets()) { _dynamicOffsetDescriptorCount++; }
if (mvkDSLBind->usesImmutableSamplers()) { _metalArgumentBufferDirtyDescriptors.setBit(descIdx); }
_descriptors.push_back(mvkDesc);
}
}
return getConfigurationResult();
}
void MVKDescriptorSet::free(bool isPoolReset) {
_layout = nullptr;
_dynamicOffsetDescriptorCount = 0;
_variableDescriptorCount = 0;
// Only reset the Metal arg buffer offset if the entire pool is being reset
if (isPoolReset) { _metalArgumentBufferOffset = 0; }
// Pooled descriptors don't need to be individually freed under pool resets.
if ( !(_pool->_hasPooledDescriptors && isPoolReset) ) {
for (auto mvkDesc : _descriptors) { _pool->freeDescriptor(mvkDesc); }
}
_descriptors.clear();
_descriptors.shrink_to_fit();
_metalArgumentBufferDirtyDescriptors.resize(0);
clearConfigurationResult();
}
MVKDescriptorSet::MVKDescriptorSet(MVKDescriptorPool* pool) : MVKVulkanAPIDeviceObject(pool->_device), _pool(pool) {
free(true);
}
#pragma mark -
#pragma mark MVKDescriptorTypePool
// If preallocated, find the next availalble descriptor.
// If not preallocated, create one on the fly.
template<class DescriptorClass>
VkResult MVKDescriptorTypePool<DescriptorClass>::allocateDescriptor(MVKDescriptor** pMVKDesc,
MVKDescriptorPool* pool) {
DescriptorClass* mvkDesc;
if (pool->_hasPooledDescriptors) {
size_t availDescIdx = _availability.getIndexOfFirstSetBit(true);
if (availDescIdx >= _availability.size()) { return VK_ERROR_OUT_OF_POOL_MEMORY; }
mvkDesc = &_descriptors[availDescIdx];
mvkDesc->reset(); // Clear before reusing.
} else {
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.
template<typename DescriptorClass>
void MVKDescriptorTypePool<DescriptorClass>::freeDescriptor(MVKDescriptor* mvkDesc,
MVKDescriptorPool* pool) {
if (pool->_hasPooledDescriptors) {
size_t descIdx = (DescriptorClass*)mvkDesc - _descriptors.data();
_availability.setBit(descIdx);
} else {
mvkDesc->destroy();
}
}
// Preallocated descriptors will be reset when they are reused
template<typename DescriptorClass>
void MVKDescriptorTypePool<DescriptorClass>::reset() {
_availability.setAllBits();
}
template<typename DescriptorClass>
MVKDescriptorTypePool<DescriptorClass>::MVKDescriptorTypePool(size_t poolSize) :
_descriptors(poolSize),
_availability(poolSize, true) {}
#pragma mark -
#pragma mark MVKDescriptorPool
VkResult MVKDescriptorPool::allocateDescriptorSets(const VkDescriptorSetAllocateInfo* pAllocateInfo,
VkDescriptorSet* pDescriptorSets) {
VkResult rslt = VK_SUCCESS;
const auto* pVarDescCounts = getVariableDecriptorCounts(pAllocateInfo);
for (uint32_t dsIdx = 0; dsIdx < pAllocateInfo->descriptorSetCount; dsIdx++) {
MVKDescriptorSetLayout* mvkDSL = (MVKDescriptorSetLayout*)pAllocateInfo->pSetLayouts[dsIdx];
if ( !mvkDSL->isPushDescriptorLayout() ) {
rslt = allocateDescriptorSet(mvkDSL, (pVarDescCounts ? pVarDescCounts[dsIdx] : 0), &pDescriptorSets[dsIdx]);
if (rslt) { return rslt; }
}
}
return rslt;
}
// Find and return an array of variable descriptor counts from the pNext chain of pCreateInfo,
// or return nullptr if the chain does not include variable descriptor counts.
const uint32_t* MVKDescriptorPool::getVariableDecriptorCounts(const VkDescriptorSetAllocateInfo* pAllocateInfo) {
for (const auto* next = (VkBaseInStructure*)pAllocateInfo->pNext; next; next = next->pNext) {
switch (next->sType) {
case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT: {
auto* pVarDescSetVarCounts = (VkDescriptorSetVariableDescriptorCountAllocateInfoEXT*)next;
return pVarDescSetVarCounts->descriptorSetCount ? pVarDescSetVarCounts->pDescriptorCounts : nullptr;
}
default:
break;
}
}
return nullptr;
}
// Retieves the first available descriptor set from the pool, and configures it.
// If none are available, returns an error.
VkResult MVKDescriptorPool::allocateDescriptorSet(MVKDescriptorSetLayout* mvkDSL,
uint32_t variableDescriptorCount,
VkDescriptorSet* pVKDS) {
VkResult rslt = VK_ERROR_OUT_OF_POOL_MEMORY;
NSUInteger mtlArgBuffAllocSize = mvkDSL->getMTLArgumentEncoder().mtlArgumentEncoderSize;
NSUInteger mtlArgBuffAlignedSize = mvkAlignByteCount(mtlArgBuffAllocSize,
getDevice()->_pMetalFeatures->mtlBufferAlignment);
size_t dsCnt = _descriptorSetAvailablility.size();
_descriptorSetAvailablility.enumerateEnabledBits(true, [&](size_t dsIdx) {
bool isSpaceAvail = true; // If not using Metal arg buffers, space will always be available.
MVKDescriptorSet* mvkDS = &_descriptorSets[dsIdx];
NSUInteger mtlArgBuffOffset = mvkDS->_metalArgumentBufferOffset;
// If the desc set is using a Metal argument buffer, we also need to see if the desc set
// will fit in the slot that might already have been allocated for it in the Metal argument
// buffer from a previous allocation that was returned. If this pool has been reset recently,
// then the desc sets will not have had a Metal argument buffer allocation assigned yet.
if (isUsingDescriptorSetMetalArgumentBuffers() && mvkDSL->isUsingMetalArgumentBuffer()) {
// If the offset has not been set (and it's not the first desc set except
// on a reset pool), set the offset and update the next available offset value.
if ( !mtlArgBuffOffset && (dsIdx || !_nextMetalArgumentBufferOffset)) {
mtlArgBuffOffset = _nextMetalArgumentBufferOffset;
_nextMetalArgumentBufferOffset += mtlArgBuffAlignedSize;
}
// Get the offset of the next desc set, if one exists and
// its offset has been set, or the end of the arg buffer.
size_t nextDSIdx = dsIdx + 1;
NSUInteger nextOffset = (nextDSIdx < dsCnt ? _descriptorSets[nextDSIdx]._metalArgumentBufferOffset : 0);
if ( !nextOffset ) { nextOffset = _metalArgumentBuffer.length; }
isSpaceAvail = (mtlArgBuffOffset + mtlArgBuffAllocSize) <= nextOffset;
}
if (isSpaceAvail) {
rslt = mvkDS->allocate(mvkDSL, variableDescriptorCount, mtlArgBuffOffset);
if (rslt) {
freeDescriptorSet(mvkDS, false);
} else {
*pVKDS = (VkDescriptorSet)mvkDS;
}
return false;
}
return true;
});
return rslt;
}
VkResult MVKDescriptorPool::freeDescriptorSets(uint32_t count, const VkDescriptorSet* pDescriptorSets) {
for (uint32_t dsIdx = 0; dsIdx < count; dsIdx++) {
freeDescriptorSet((MVKDescriptorSet*)pDescriptorSets[dsIdx], false);
}
return VK_SUCCESS;
}
// Descriptor sets are held in contiguous memory, so the index of the returning descriptor
// set can be calculated by pointer differences, and it can be marked as available.
// Don't bother individually set descriptor set availability if pool is being reset.
void MVKDescriptorPool::freeDescriptorSet(MVKDescriptorSet* mvkDS, bool isPoolReset) {
if ( !mvkDS ) { return; } // Vulkan allows NULL refs.
if (mvkDS->_pool == this) {
mvkDS->free(isPoolReset);
if ( !isPoolReset ) {
size_t dsIdx = mvkDS - _descriptorSets.data();
_descriptorSetAvailablility.setBit(dsIdx);
}
} else {
reportError(VK_ERROR_INITIALIZATION_FAILED, "A descriptor set is being returned to a descriptor pool that did not allocate it.");
}
}
// Free all descriptor sets and reset descriptor pools
VkResult MVKDescriptorPool::reset(VkDescriptorPoolResetFlags flags) {
for (auto& mvkDS : _descriptorSets) { freeDescriptorSet(&mvkDS, true); }
_descriptorSetAvailablility.setAllBits();
_uniformBufferDescriptors.reset();
_storageBufferDescriptors.reset();
_uniformBufferDynamicDescriptors.reset();
_storageBufferDynamicDescriptors.reset();
_inlineUniformBlockDescriptors.reset();
_sampledImageDescriptors.reset();
_storageImageDescriptors.reset();
_inputAttachmentDescriptors.reset();
_samplerDescriptors.reset();
_combinedImageSamplerDescriptors.reset();
_uniformTexelBufferDescriptors.reset();
_storageTexelBufferDescriptors.reset();
_nextMetalArgumentBufferOffset = 0;
return VK_SUCCESS;
}
// Allocate a descriptor of the specified type
VkResult MVKDescriptorPool::allocateDescriptor(VkDescriptorType descriptorType,
MVKDescriptor** pMVKDesc) {
switch (descriptorType) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
return _uniformBufferDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
return _storageBufferDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
return _uniformBufferDynamicDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
return _storageBufferDynamicDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
return _inlineUniformBlockDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
return _sampledImageDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
return _storageImageDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
return _inputAttachmentDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_SAMPLER:
return _samplerDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
return _combinedImageSamplerDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
return _uniformTexelBufferDescriptors.allocateDescriptor(pMVKDesc, this);
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
return _storageTexelBufferDescriptors.allocateDescriptor(pMVKDesc, this);
default:
return reportError(VK_ERROR_INITIALIZATION_FAILED, "Unrecognized VkDescriptorType %d.", descriptorType);
}
}
void MVKDescriptorPool::freeDescriptor(MVKDescriptor* mvkDesc) {
VkDescriptorType descriptorType = mvkDesc->getDescriptorType();
switch (descriptorType) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
return _uniformBufferDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
return _storageBufferDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
return _uniformBufferDynamicDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
return _storageBufferDynamicDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
return _inlineUniformBlockDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
return _sampledImageDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
return _storageImageDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
return _inputAttachmentDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_SAMPLER:
return _samplerDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
return _combinedImageSamplerDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
return _uniformTexelBufferDescriptors.freeDescriptor(mvkDesc, this);
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
return _storageTexelBufferDescriptors.freeDescriptor(mvkDesc, this);
default:
reportError(VK_ERROR_INITIALIZATION_FAILED, "Unrecognized VkDescriptorType %d.", descriptorType);
}
}
// Return the size of the preallocated pool for descriptors of the specified type,
// or zero if we are not preallocating descriptors in the pool.
// There may be more than one poolSizeCount instance for the desired VkDescriptorType.
// Accumulate the descriptor count for the desired VkDescriptorType accordingly.
static size_t getPoolSize(const VkDescriptorPoolCreateInfo* pCreateInfo, VkDescriptorType descriptorType, bool poolDescriptors) {
uint32_t descCnt = 0;
if (poolDescriptors) {
uint32_t poolCnt = pCreateInfo->poolSizeCount;
for (uint32_t poolIdx = 0; poolIdx < poolCnt; poolIdx++) {
auto& poolSize = pCreateInfo->pPoolSizes[poolIdx];
if (poolSize.type == descriptorType) { descCnt += poolSize.descriptorCount; }
}
}
return descCnt;
}
// Return the size of the preallocated pool for descriptors of the
// VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT type, or zero if we
// are not preallocating descriptors in the pool.
// For consistency with getPoolSize() behavior, we support more than one pNext entry
// for inline blocks. Accumulate the descriptor count for inline blocks accordingly.
static size_t getInlineBlockPoolSize(const VkDescriptorPoolCreateInfo* pCreateInfo, bool poolDescriptors) {
uint32_t descCnt = 0;
if (poolDescriptors) {
for (const auto* next = (VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) {
switch (next->sType) {
case VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_INLINE_UNIFORM_BLOCK_CREATE_INFO_EXT: {
auto* pDescPoolInlineBlockCreateInfo = (VkDescriptorPoolInlineUniformBlockCreateInfoEXT*)next;
descCnt += pDescPoolInlineBlockCreateInfo->maxInlineUniformBlockBindings;
break;
}
default:
break;
}
}
}
return descCnt;
}
// Although poolDescriptors is derived from MVKConfiguration, it is passed in here to ensure all components of this instance see a SVOT for this value.
// Alternate might have been to force _hasPooledDescriptors to be set first by changing member declaration order in class declaration.
MVKDescriptorPool::MVKDescriptorPool(MVKDevice* device, const VkDescriptorPoolCreateInfo* pCreateInfo, bool poolDescriptors) :
MVKVulkanAPIDeviceObject(device),
_descriptorSets(pCreateInfo->maxSets, MVKDescriptorSet(this)),
_descriptorSetAvailablility(pCreateInfo->maxSets, true),
_uniformBufferDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, poolDescriptors)),
_storageBufferDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, poolDescriptors)),
_uniformBufferDynamicDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, poolDescriptors)),
_storageBufferDynamicDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, poolDescriptors)),
_inlineUniformBlockDescriptors(getInlineBlockPoolSize(pCreateInfo, poolDescriptors)),
_sampledImageDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, poolDescriptors)),
_storageImageDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, poolDescriptors)),
_inputAttachmentDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, poolDescriptors)),
_samplerDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_SAMPLER, poolDescriptors)),
_combinedImageSamplerDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, poolDescriptors)),
_uniformTexelBufferDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, poolDescriptors)),
_storageTexelBufferDescriptors(getPoolSize(pCreateInfo, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, poolDescriptors)),
_inlineBlockMTLBufferAllocator(device, device->_pMetalFeatures->dynamicMTLBufferSize, true),
_hasPooledDescriptors(poolDescriptors) {
initMetalArgumentBuffer(pCreateInfo);
}
void MVKDescriptorPool::initMetalArgumentBuffer(const VkDescriptorPoolCreateInfo* pCreateInfo) {
_metalArgumentBuffer = nil;
_nextMetalArgumentBufferOffset = 0;
if ( !isUsingDescriptorSetMetalArgumentBuffers() ) { return; }
@autoreleasepool {
NSUInteger mtlBuffCnt = 0;
NSUInteger mtlTexCnt = 0;
NSUInteger mtlSampCnt = 0;
uint32_t poolCnt = pCreateInfo->poolSizeCount;
for (uint32_t poolIdx = 0; poolIdx < poolCnt; poolIdx++) {
auto& poolSize = pCreateInfo->pPoolSizes[poolIdx];
switch (poolSize.type) {
// VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT counts handled separately below
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
mtlBuffCnt += poolSize.descriptorCount;
break;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
mtlTexCnt += poolSize.descriptorCount;
break;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
mtlTexCnt += poolSize.descriptorCount;
mtlBuffCnt += poolSize.descriptorCount;
break;
case VK_DESCRIPTOR_TYPE_SAMPLER:
mtlSampCnt += poolSize.descriptorCount;
break;
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
mtlTexCnt += poolSize.descriptorCount;
mtlSampCnt += poolSize.descriptorCount;
break;
default:
break;
}
}
// VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT counts pulled separately
for (const auto* next = (VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) {
switch (next->sType) {
case VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_INLINE_UNIFORM_BLOCK_CREATE_INFO_EXT: {
auto* pDescPoolInlineBlockCreateInfo = (VkDescriptorPoolInlineUniformBlockCreateInfoEXT*)next;
mtlBuffCnt += pDescPoolInlineBlockCreateInfo->maxInlineUniformBlockBindings;
break;
}
default:
break;
}
}
// Each descriptor set uses a separate Metal argument buffer, but all of these descriptor set
// Metal argument buffers share a single MTLBuffer. This single MTLBuffer needs to be large enough
// to hold all of the Metal resources for the descriptors. In addition, depending on the platform,
// a Metal argument buffer may have a fixed overhead storage, in addition to the storage required
// to hold the resources. This overhead per descriptor set is conservatively calculated by measuring
// the size of a Metal argument buffer containing one of each type of resource (S1), and the size
// of a Metal argument buffer containing two of each type of resource (S2), and then calculating
// the fixed overhead per argument buffer as (2 * S1 - S2). To this is added the overhead due to
// the alignment of each descriptor set Metal argument buffer offset.
NSUInteger overheadPerDescSet = (2 * getMetalArgumentBufferResourceStorageSize(1, 1, 1) -
getMetalArgumentBufferResourceStorageSize(2, 2, 2) +
_device->_pMetalFeatures->mtlBufferAlignment);
// Measure the size of an argument buffer that would hold all of the resources
// managed in this pool, then add any overhead for all the descriptor sets.
NSUInteger metalArgBuffSize = getMetalArgumentBufferResourceStorageSize(mtlBuffCnt, mtlTexCnt, mtlSampCnt);
metalArgBuffSize += (overheadPerDescSet * (pCreateInfo->maxSets - 1)); // metalArgBuffSize already includes overhead for one descriptor set
if (metalArgBuffSize) {
NSUInteger maxMTLBuffSize = _device->_pMetalFeatures->maxMTLBufferSize;
if (metalArgBuffSize > maxMTLBuffSize) {
setConfigurationResult(reportError(VK_ERROR_FRAGMENTATION_EXT, "vkCreateDescriptorPool(): The requested descriptor storage of %d MB is larger than the maximum descriptor storage of %d MB per VkDescriptorPool.", (uint32_t)(metalArgBuffSize / MEBI), (uint32_t)(maxMTLBuffSize / MEBI)));
metalArgBuffSize = maxMTLBuffSize;
}
_metalArgumentBuffer = [getMTLDevice() newBufferWithLength: metalArgBuffSize options: MTLResourceStorageModeShared]; // retained
_metalArgumentBuffer.label = @"Argument buffer";
}
}
}
// Returns the size of a Metal argument buffer containing the number of various types.
// Make sure any call to this function is wrapped in @autoreleasepool.
NSUInteger MVKDescriptorPool::getMetalArgumentBufferResourceStorageSize(NSUInteger bufferCount,
NSUInteger textureCount,
NSUInteger samplerCount) {
NSMutableArray<MTLArgumentDescriptor*>* args = [NSMutableArray arrayWithCapacity: 3];
NSUInteger argIdx = 0;
[args addObject: getMTLArgumentDescriptor(MTLDataTypePointer, argIdx, bufferCount)];
argIdx += bufferCount;
[args addObject: getMTLArgumentDescriptor(MTLDataTypeTexture, argIdx, textureCount)];
argIdx += textureCount;
[args addObject: getMTLArgumentDescriptor(MTLDataTypeSampler, argIdx, samplerCount)];
argIdx += samplerCount;
id<MTLArgumentEncoder> argEnc = [getMTLDevice() newArgumentEncoderWithArguments: args];
NSUInteger metalArgBuffSize = argEnc.encodedLength;
[argEnc release];
return metalArgBuffSize;
}
// Returns a MTLArgumentDescriptor of a particular type.
// To be conservative, use some worse-case values, in case content makes a difference in argument size.
MTLArgumentDescriptor* MVKDescriptorPool::getMTLArgumentDescriptor(MTLDataType resourceType, NSUInteger argIndex, NSUInteger count) {
auto* argDesc = [MTLArgumentDescriptor argumentDescriptor];
argDesc.dataType = resourceType;
argDesc.access = MTLArgumentAccessReadWrite;
argDesc.index = argIndex;
argDesc.arrayLength = count;
argDesc.textureType = MTLTextureTypeCubeArray;
return argDesc;
}
MVKDescriptorPool::~MVKDescriptorPool() {
reset(0);
[_metalArgumentBuffer release];
_metalArgumentBuffer = nil;
}
#pragma mark -
#pragma mark MVKDescriptorUpdateTemplate
const VkDescriptorUpdateTemplateEntryKHR* MVKDescriptorUpdateTemplate::getEntry(uint32_t n) const {
return &_entries[n];
}
uint32_t MVKDescriptorUpdateTemplate::getNumberOfEntries() const {
return (uint32_t)_entries.size();
}
VkDescriptorUpdateTemplateTypeKHR MVKDescriptorUpdateTemplate::getType() const {
return _type;
}
MVKDescriptorUpdateTemplate::MVKDescriptorUpdateTemplate(MVKDevice* device,
const VkDescriptorUpdateTemplateCreateInfoKHR* pCreateInfo) :
MVKVulkanAPIDeviceObject(device), _type(pCreateInfo->templateType) {
for (uint32_t i = 0; i < pCreateInfo->descriptorUpdateEntryCount; i++)
_entries.push_back(pCreateInfo->pDescriptorUpdateEntries[i]);
}
#pragma mark -
#pragma mark Support functions
// Updates the resource bindings in the descriptor sets inditified in the specified content.
void mvkUpdateDescriptorSets(uint32_t writeCount,
const VkWriteDescriptorSet* pDescriptorWrites,
uint32_t copyCount,
const VkCopyDescriptorSet* pDescriptorCopies) {
// Perform the write updates
for (uint32_t i = 0; i < writeCount; i++) {
const VkWriteDescriptorSet* pDescWrite = &pDescriptorWrites[i];
size_t stride;
MVKDescriptorSet* dstSet = (MVKDescriptorSet*)pDescWrite->dstSet;
const VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock = nullptr;
if (dstSet->getDevice()->_enabledExtensions.vk_EXT_inline_uniform_block.enabled) {
for (const auto* next = (VkBaseInStructure*)pDescWrite->pNext; next; next = next->pNext) {
switch (next->sType) {
case VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT: {
pInlineUniformBlock = (VkWriteDescriptorSetInlineUniformBlockEXT*)next;
break;
}
default:
break;
}
}
}
const void* pData = getWriteParameters(pDescWrite->descriptorType, pDescWrite->pImageInfo,
pDescWrite->pBufferInfo, pDescWrite->pTexelBufferView,
pInlineUniformBlock, stride);
dstSet->write(pDescWrite, stride, pData);
}
// Perform the copy updates by reading bindings from one set and writing to other set.
for (uint32_t i = 0; i < copyCount; i++) {
const VkCopyDescriptorSet* pDescCopy = &pDescriptorCopies[i];
uint32_t descCnt = pDescCopy->descriptorCount;
VkDescriptorImageInfo imgInfos[descCnt];
VkDescriptorBufferInfo buffInfos[descCnt];
VkBufferView texelBuffInfos[descCnt];
// For inline block create a temp buffer of descCnt bytes to hold data during copy.
uint8_t dstBuffer[descCnt];
VkWriteDescriptorSetInlineUniformBlockEXT inlineUniformBlock;
inlineUniformBlock.pData = dstBuffer;
inlineUniformBlock.dataSize = descCnt;
MVKDescriptorSet* srcSet = (MVKDescriptorSet*)pDescCopy->srcSet;
srcSet->read(pDescCopy, imgInfos, buffInfos, texelBuffInfos, &inlineUniformBlock);
MVKDescriptorSet* dstSet = (MVKDescriptorSet*)pDescCopy->dstSet;
VkDescriptorType descType = dstSet->getDescriptorType(pDescCopy->dstBinding);
size_t stride;
const void* pData = getWriteParameters(descType, imgInfos, buffInfos, texelBuffInfos, &inlineUniformBlock, stride);
dstSet->write(pDescCopy, stride, pData);
}
}
// Updates the resource bindings in the given descriptor set from the specified template.
void mvkUpdateDescriptorSetWithTemplate(VkDescriptorSet descriptorSet,
VkDescriptorUpdateTemplateKHR updateTemplate,
const void* pData) {
MVKDescriptorSet* dstSet = (MVKDescriptorSet*)descriptorSet;
MVKDescriptorUpdateTemplate* pTemplate = (MVKDescriptorUpdateTemplate*)updateTemplate;
if (pTemplate->getType() != VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR)
return;
// Perform the updates
for (uint32_t i = 0; i < pTemplate->getNumberOfEntries(); i++) {
const VkDescriptorUpdateTemplateEntryKHR* pEntry = pTemplate->getEntry(i);
const void* pCurData = (const char*)pData + pEntry->offset;
dstSet->write(pEntry, pEntry->stride, pCurData);
}
}