This PR adds recycling of descriptor pools. Currently descriptor pools are discarded when full or context is flushed. This PR allows descriptor pools to be discarded for reuse. It is also more conservative and only discard Descriptor pools when they are full or fragmented. When using the Vulkan backend a small amount of descriptor memory can leak. Even when we clean up all resources, drivers can still keep data around on the GPU. Eventually this can lead to out of memory issues depending on how the GPU driver actually manages descriptor sets. When the descriptor sets of the descriptor pool aren't used anymore the VKDiscardPool will recycle the pools back to its original VKDescriptorPools. It needs to be the same instance as descriptor pools/sets are owned by a single thread. Pull Request: https://projects.blender.org/blender/blender/pulls/144992
155 lines
4.5 KiB
C++
155 lines
4.5 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup gpu
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "BLI_mutex.hh"
|
|
|
|
#include "vk_common.hh"
|
|
|
|
#include "vk_descriptor_pools.hh"
|
|
#include "vk_immediate.hh"
|
|
|
|
namespace blender::gpu {
|
|
class VKDevice;
|
|
class VKDiscardPool;
|
|
|
|
template<typename Item> class TimelineResources : Vector<std::pair<TimelineValue, Item>> {
|
|
friend class VKDiscardPool;
|
|
|
|
public:
|
|
void append_timeline(TimelineValue timeline, Item item)
|
|
{
|
|
this->append(std::pair(timeline, item));
|
|
}
|
|
|
|
void update_timeline(TimelineValue timeline)
|
|
{
|
|
for (std::pair<TimelineValue, Item> &pair : *this) {
|
|
pair.first = timeline;
|
|
}
|
|
}
|
|
|
|
int64_t size() const
|
|
{
|
|
return static_cast<const Vector<std::pair<TimelineValue, Item>> &>(*this).size();
|
|
}
|
|
bool is_empty() const
|
|
{
|
|
return static_cast<const Vector<std::pair<TimelineValue, Item>> &>(*this).is_empty();
|
|
}
|
|
|
|
/**
|
|
* Remove all items that are used in a timeline before or equal to the current_timeline.
|
|
*/
|
|
template<typename Deleter> void remove_old(TimelineValue current_timeline, Deleter deleter)
|
|
{
|
|
int64_t first_index_to_keep = 0;
|
|
for (std::pair<TimelineValue, Item> &item : *this) {
|
|
if (item.first > current_timeline) {
|
|
break;
|
|
}
|
|
deleter(item.second);
|
|
first_index_to_keep++;
|
|
}
|
|
|
|
if (first_index_to_keep > 0) {
|
|
this->remove(0, first_index_to_keep);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Pool of resources that are discarded, but can still be in used and cannot be destroyed.
|
|
*
|
|
* When GPU resources are deleted (`GPU_*_delete`) the GPU handles are kept inside a discard pool.
|
|
* When we are sure that the resource isn't used on the GPU anymore we can safely destroy it.
|
|
*
|
|
* When preparing the next frame, the previous frame can still be rendered. Resources that needs to
|
|
* be destroyed can only be when the previous frame has been completed and being displayed on the
|
|
* screen.
|
|
*/
|
|
class VKDiscardPool {
|
|
friend class VKDevice;
|
|
friend class VKBackend;
|
|
|
|
private:
|
|
TimelineResources<std::pair<VkImage, VmaAllocation>> images_;
|
|
TimelineResources<std::pair<VkBuffer, VmaAllocation>> buffers_;
|
|
TimelineResources<VkImageView> image_views_;
|
|
TimelineResources<VkBufferView> buffer_views_;
|
|
TimelineResources<VkShaderModule> shader_modules_;
|
|
TimelineResources<VkPipeline> pipelines_;
|
|
TimelineResources<VkPipelineLayout> pipeline_layouts_;
|
|
TimelineResources<VkRenderPass> render_passes_;
|
|
TimelineResources<VkFramebuffer> framebuffers_;
|
|
TimelineResources<std::pair<VkDescriptorPool, VKDescriptorPools *>> descriptor_pools_;
|
|
|
|
Mutex mutex_;
|
|
|
|
TimelineValue timeline_ = UINT64_MAX;
|
|
|
|
public:
|
|
void deinit(VKDevice &device);
|
|
|
|
void discard_image(VkImage vk_image, VmaAllocation vma_allocation);
|
|
void discard_image_view(VkImageView vk_image_view);
|
|
void discard_buffer(VkBuffer vk_buffer, VmaAllocation vma_allocation);
|
|
void discard_buffer_view(VkBufferView vk_buffer_view);
|
|
void discard_shader_module(VkShaderModule vk_shader_module);
|
|
void discard_pipeline(VkPipeline vk_pipeline);
|
|
void discard_pipeline_layout(VkPipelineLayout vk_pipeline_layout);
|
|
void discard_framebuffer(VkFramebuffer vk_framebuffer);
|
|
void discard_render_pass(VkRenderPass vk_render_pass);
|
|
void discard_descriptor_pool_for_reuse(VkDescriptorPool vk_descriptor_pool,
|
|
VKDescriptorPools *descriptor_pools);
|
|
|
|
/**
|
|
* Move discarded resources from src_pool into this.
|
|
*
|
|
* GPU resources that are discarded from the dependency graph are stored in the device orphaned
|
|
* data. When a swap-chain context list is made active the orphaned data can be merged into a
|
|
* swap-chain discard pool.
|
|
*
|
|
* All moved items will receive a new timeline.
|
|
*
|
|
* Function must be externally synced (
|
|
*
|
|
* <source>
|
|
* {
|
|
* std::scoped_lock lock(pool.mutex_get()));
|
|
* pool.move_data(src_pool, timeline);
|
|
* }
|
|
* </source>
|
|
*/
|
|
void move_data(VKDiscardPool &src_pool, TimelineValue timeline);
|
|
inline Mutex &mutex_get()
|
|
{
|
|
return mutex_;
|
|
}
|
|
void destroy_discarded_resources(VKDevice &device, bool force = false);
|
|
|
|
/**
|
|
* Returns the discard pool for the current thread.
|
|
*
|
|
* When active thread has a context it uses the context discard pool.
|
|
* Otherwise a device discard pool is used.
|
|
*/
|
|
static VKDiscardPool &discard_pool_get();
|
|
};
|
|
|
|
class VKResourcePool {
|
|
|
|
public:
|
|
VKDescriptorPools descriptor_pools;
|
|
VKDescriptorSetTracker descriptor_set;
|
|
|
|
void init(VKDevice &device);
|
|
};
|
|
} // namespace blender::gpu
|