vk: delay destruction

This commit is contained in:
Powei Feng
2026-02-27 11:17:39 -08:00
parent 82246d934d
commit 891003e076
7 changed files with 107 additions and 31 deletions

View File

@@ -332,6 +332,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t binding, fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
VkDeviceSize offset, VkDeviceSize size) noexcept {
prepareForUpdate(set);
VkDescriptorBufferInfo const info = {
.buffer = bufferObject->getVkBuffer(),
.offset = offset,
@@ -368,20 +369,15 @@ void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescr
// Build a new descriptor set from the new layout
VkDescriptorSetLayout const genLayout = set->boundLayout;
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
Bitmask samplers = layout->bitmask.sampler;
samplers.unset(binding);
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
// We fold the two stages into just the lower half of the bits to denote a combined set of
// bindings.
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
VkDescriptorSet const srcSet = set->getVkSet();
copySet(srcSet, newSet, copyBindings);
copySet(srcSet, newSet, layout);
set->addNewSet(newSet,
[this, layoutCount = layout->count, genLayout, newSet](VulkanDescriptorSet*) {
this->manualRecycle(layoutCount, genLayout, newSet);
});
} else {
prepareForUpdate(set);
}
VkDescriptorSet const vkset = set->getVkSet();
@@ -448,21 +444,41 @@ void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count co
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
void VulkanDescriptorSetCache::prepareForUpdate(
fvkmemory::resource_ptr<VulkanDescriptorSet> set) noexcept {
if (set->isBound()) {
auto layout = set->getLayout();
VkDescriptorSetLayout const vklayout = set->boundLayout;
VkDescriptorSet const newSet = mDescriptorPool->obtainSet(layout->count, vklayout);
copySet(set->getVkSet(), newSet, layout);
set->addNewSet(newSet,
[this, layoutCount = layout->count, vklayout, newSet](VulkanDescriptorSet*) {
this->manualRecycle(layoutCount, vklayout, newSet);
});
}
}
void VulkanDescriptorSetCache::copySet(VkDescriptorSet srcSet, VkDescriptorSet dstSet,
fvkutils::SamplerBitmask bindings) const {
// TODO: fix the size for better memory management
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) const {
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
Bitmask const samplers = layout->bitmask.sampler;
Bitmask const inputAttachments = layout->bitmask.inputAttachment;
Bitmask const bindings = foldBitsInHalf(ubo | samplers | inputAttachments);
std::vector<VkCopyDescriptorSet> copies;
bindings.forEachSetBit([&](size_t index) {
copies.push_back({
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
.srcSet = srcSet,
.srcBinding = (uint32_t) index,
.dstSet = dstSet,
.dstBinding = (uint32_t) index,
.descriptorCount = 1,
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
.srcSet = srcSet,
.srcBinding = (uint32_t) index,
.dstSet = dstSet,
.dstBinding = (uint32_t) index,
.descriptorCount = 1,
});
});
vkUpdateDescriptorSets(mDevice, 0, nullptr, copies.size(), copies.data());
if (!copies.empty()) {
vkUpdateDescriptorSets(mDevice, 0, nullptr, (uint32_t) copies.size(), copies.data());
}
}

View File

@@ -90,8 +90,10 @@ public:
void resetCachedState() noexcept { mLastBoundInfo = {}; }
private:
void prepareForUpdate(fvkmemory::resource_ptr<VulkanDescriptorSet> set) noexcept;
void copySet(VkDescriptorSet srcSet, VkDescriptorSet destSet,
fvkutils::SamplerBitmask copyBindings) const;
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) const;
class DescriptorInfinitePool;

View File

@@ -260,8 +260,8 @@ void VulkanDescriptorSet::gc() {
void VulkanDescriptorSet::addNewSet(VkDescriptorSet vkSet, OnRecycle&& onRecycleFn) {
gc();
mCurrentSetIndex = mSets.size();
mSets.push_back({ vkSet, std::move(onRecycleFn) });
mCurrentSetIndex = (uint8_t) (mSets.size() - 1);
}
PushConstantDescription::PushConstantDescription(backend::Program const& program) {

View File

@@ -191,7 +191,8 @@ public:
void referencedBy(VulkanCommandBuffer& commands);
bool isBound() const {
return bool(mSets[mCurrentSetIndex].fenceStatus);
auto const& set = mSets[mCurrentSetIndex];
return set.fenceStatus && set.fenceStatus->getStatus() != VK_SUCCESS;
}
// The current layout used by the descriptor set. This one will match the bindings, including

View File

@@ -780,10 +780,16 @@ void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
VkCommandBuffer const cmdbuf = commands->buffer();
VkImageLayout const layout =
fvkutils::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
const bool isDepth = fvkutils::isVkDepthFormat(mState->mVkFormat);
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = static_cast<VkAccessFlags>(isDepth ? (VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT)
: (VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT)),
.oldLayout = layout,
.newLayout = layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -791,9 +797,13 @@ void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
.image = mState->mTextureImage,
.subresourceRange = range,
};
VkPipelineStageFlags dstStage = isDepth ? (VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT)
: VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
dstStage,
0, 0, nullptr, 0, nullptr, 1, &barrier);
}
@@ -802,9 +812,13 @@ void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
VkCommandBuffer const cmdbuf = commands->buffer();
VkImageLayout const layout
= fvkutils::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
const bool isDepth = fvkutils::isVkDepthFormat(mState->mVkFormat);
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.srcAccessMask = static_cast<VkAccessFlags>(isDepth ? VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
: VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = layout,
.newLayout = layout,
@@ -813,7 +827,12 @@ void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
.image = mState->mTextureImage,
.subresourceRange = range,
};
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VkPipelineStageFlags srcStage = isDepth ? (VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT)
: VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
vkCmdPipelineBarrier(cmdbuf, srcStage,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}

View File

@@ -41,23 +41,55 @@ void ResourceManager::gc() noexcept {
list.clear();
};
FrameGcList frameGc;
{
// Note that we're not copying mThreadSafeGcList because the objects here do not have
// resource_ptrs to other handle objects, so their desctruction would not add more elements
// to mThreadSafeGcList.
std::unique_lock<utils::Mutex> lock(mThreadSafeGcListMutex);
destroyAll(mThreadSafeGcList);
std::swap(frameGc.threadSafeGcList, mThreadSafeGcList);
}
GcList gcs;
std::swap(gcs, mGcList);
destroyAll(gcs);
std::swap(frameGc.gcList, mGcList);
mFramesGcList.push_back(std::move(frameGc));
if (mFramesGcList.size() > 4) {
FrameGcList oldest = std::move(mFramesGcList.front());
mFramesGcList.erase(mFramesGcList.begin());
destroyAll(oldest.threadSafeGcList);
destroyAll(oldest.gcList);
}
FVK_SYSTRACE_END();
}
void ResourceManager::terminate() noexcept {
while (!mThreadSafeGcList.empty() || !mGcList.empty()) {
gc();
auto destroyAll = [this](GcList& list) {
for (auto const& [type, id]: list) {
destroyWithType(type, id);
}
list.clear();
};
while (!mThreadSafeGcList.empty() || !mGcList.empty() || !mFramesGcList.empty()) {
if (!mFramesGcList.empty()) {
FrameGcList oldest = std::move(mFramesGcList.front());
mFramesGcList.erase(mFramesGcList.begin());
destroyAll(oldest.threadSafeGcList);
destroyAll(oldest.gcList);
}
GcList threadSafeList;
{
std::unique_lock<utils::Mutex> lock(mThreadSafeGcListMutex);
std::swap(threadSafeList, mThreadSafeGcList);
}
destroyAll(threadSafeList);
GcList gcs;
std::swap(gcs, mGcList);
destroyAll(gcs);
}
}

View File

@@ -100,6 +100,12 @@ private:
GcList mThreadSafeGcList;
GcList mGcList;
struct FrameGcList {
GcList threadSafeGcList;
GcList gcList;
};
std::vector<FrameGcList> mFramesGcList;
template<typename D>
friend struct resource_ptr;