Vulkan: Resource Pools

This PR implements #126353; In short: keep discard list as part of swap chain images. This allows
better determination when resources are actually not in use anymore.

## Resource pool

Resource pools keep track of the resources for a swap chain image.

In Blender this is a bit more complicated due to the way GPUContext work. A single thread can have
multiple contexts. Some of them have a swap chain (GHOST Window) other don't (draw manager). The
resource pool should be shared between the contexts running on the same thread.

When opening multiple windows there are also multiple swap chains to consider.

### Discard pile

Resource handles that are deleted and stored in the discard pile. When we are sure that these
resources are not used on the GPU anymore these are destroyed.

### Reusable resources

There are other resources as well like:
- Descriptor sets
- Descriptor pools

## Open issues

There are some limitations that require future PRs to fix including:
- Background rendering
- Handling multiple windows
- Improve CPU/GPU synchronization
- Reuse staging buffers

Pull Request: https://projects.blender.org/blender/blender/pulls/126353
This commit is contained in:
Jeroen Bakker
2024-08-19 15:37:48 +02:00
parent c1aef6b4b0
commit ef3ceb3629
18 changed files with 362 additions and 117 deletions

View File

@@ -716,6 +716,8 @@ typedef struct {
#ifdef WITH_VULKAN_BACKEND
typedef struct {
/** Identifier of the swap chain image in the swap chain. */
uint32_t swap_chain_index;
/** Image handle to the image that will be presented to the user. */
VkImage image;
/** Format of the image. */

View File

@@ -520,6 +520,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
VK_CHECK(vkResetFences(device, 1, &m_fence));
GHOST_VulkanSwapChainData swap_chain_data;
swap_chain_data.swap_chain_index = m_currentImage;
swap_chain_data.image = m_swapchain_images[m_currentImage];
swap_chain_data.format = m_surface_format.format;
swap_chain_data.extent = m_render_extent;
@@ -557,7 +558,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
return GHOST_kFailure;
}
m_currentFrame = (m_currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
m_currentImage = (m_currentImage + 1) % m_swapchain_images.size();
if (swap_buffers_post_callback_) {
swap_buffers_post_callback_();
@@ -569,6 +570,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
GHOST_TSuccess GHOST_ContextVK::getVulkanSwapChainFormat(
GHOST_VulkanSwapChainData *r_swap_chain_data)
{
r_swap_chain_data->swap_chain_index = m_currentImage;
r_swap_chain_data->image = VK_NULL_HANDLE;
r_swap_chain_data->format = m_surface_format.format;
r_swap_chain_data->extent = m_render_extent;
@@ -811,6 +813,9 @@ GHOST_TSuccess GHOST_ContextVK::createSwapchain()
if (image_count > capabilities.maxImageCount && capabilities.maxImageCount > 0) {
image_count = capabilities.maxImageCount;
}
if (capabilities.minImageCount <= 3 && image_count > 3) {
image_count = 3;
}
VkSwapchainCreateInfoKHR create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;

View File

@@ -190,8 +190,6 @@ class GHOST_ContextVK : public GHOST_Context {
VkSurfaceFormatKHR m_surface_format;
VkFence m_fence;
/** frame modulo swapchain_len. Used as index for sync objects. */
int m_currentFrame = 0;
/** Image index in the swapchain. Used as index for render objects. */
uint32_t m_currentImage = 0;

View File

@@ -227,6 +227,7 @@ set(VULKAN_SRC
vulkan/render_graph/vk_resource_access_info.cc
vulkan/render_graph/vk_resource_state_tracker.cc
vulkan/render_graph/vk_scheduler.cc
vulkan/vk_resource_pool.cc
vulkan/vk_resource_tracker.cc
vulkan/vk_sampler.cc
vulkan/vk_samplers.cc
@@ -295,6 +296,7 @@ set(VULKAN_SRC
vulkan/render_graph/vk_resource_access_info.hh
vulkan/render_graph/vk_resource_state_tracker.hh
vulkan/render_graph/vk_scheduler.hh
vulkan/vk_resource_pool.hh
vulkan/vk_resource_tracker.hh
vulkan/vk_sampler.hh
vulkan/vk_samplers.hh

View File

@@ -90,6 +90,13 @@ void VKCommandBuilder::build_node_group(VKRenderGraph &render_graph,
bool is_rendering = false;
for (NodeHandle node_handle : node_group) {
VKRenderGraphNode &node = render_graph.nodes_[node_handle];
#if 0
std::cout << "node_group: " << node_group.first() << "-" << node_group.last()
<< ", node_handle: " << node_handle << ", node_type: " << node.type << "\n";
#endif
#if 0
render_graph.debug_print(node_handle);
#endif
build_pipeline_barriers(render_graph, command_buffer, node_handle, node.pipeline_stage_get());
if (node.type == VKNodeType::BEGIN_RENDERING) {
layer_tracking_begin(render_graph, node_handle);

View File

@@ -120,12 +120,6 @@ class VKRenderGraph : public NonCopyable {
public:
VKSubmissionID submission_id;
/**
* Thread this render graph belongs to.
*
* Contexts of the same thread will share the same render graph. See `VKDevice::render_graph()`.
*/
pthread_t thread_id;
/**
* Construct a new render graph instance.

View File

@@ -174,7 +174,7 @@ Context *VKBackend::context_alloc(void *ghost_window, void *ghost_context)
device.init(ghost_context);
}
VKContext *context = new VKContext(ghost_window, ghost_context, device.render_graph());
VKContext *context = new VKContext(ghost_window, ghost_context, device.current_thread_data());
device.context_register(*context);
GHOST_SetVulkanSwapBuffersCallbacks((GHOST_ContextHandle)ghost_context,
VKContext::swap_buffers_pre_callback,

View File

@@ -166,9 +166,11 @@ bool VKBuffer::free()
}
VKDevice &device = VKBackend::get().device;
device.discard_buffer(vk_buffer_, allocation_);
device.discard_pool_for_current_thread().discard_buffer(vk_buffer_, allocation_);
allocation_ = VK_NULL_HANDLE;
vk_buffer_ = VK_NULL_HANDLE;
return true;
}

View File

@@ -39,6 +39,13 @@ class VKBuffer {
void update(const void *data) const;
void flush() const;
void read(VKContext &context, void *data) const;
/**
* Free the buffer.
*
* Discards the buffer so it can be destroyed safely later. Buffers can still be used when
* rendering so we can only destroy them after the rendering is completed.
*/
bool free();
int64_t size_in_bytes() const

View File

@@ -21,10 +21,8 @@
namespace blender::gpu {
VKContext::VKContext(void *ghost_window,
void *ghost_context,
render_graph::VKRenderGraph &render_graph)
: render_graph(render_graph)
VKContext::VKContext(void *ghost_window, void *ghost_context, VKThreadData &thread_data)
: thread_data_(thread_data), render_graph(thread_data_.render_graph)
{
ghost_window_ = ghost_window;
ghost_context_ = ghost_context;
@@ -56,19 +54,26 @@ VKContext::~VKContext()
void VKContext::sync_backbuffer()
{
VKDevice &device = VKBackend::get().device;
if (ghost_context_) {
VKDevice &device = VKBackend::get().device;
if (!is_init_) {
is_init_ = true;
descriptor_pools_.init(device);
device.init_dummy_buffer(*this);
}
descriptor_pools_.reset();
}
if (ghost_window_) {
GHOST_VulkanSwapChainData swap_chain_data = {};
GHOST_GetVulkanSwapChainFormat((GHOST_WindowHandle)ghost_window_, &swap_chain_data);
if (assign_if_different(thread_data_.current_swap_chain_index,
swap_chain_data.swap_chain_index))
{
thread_data_.current_swap_chain_index = swap_chain_data.swap_chain_index;
VKResourcePool &resource_pool = thread_data_.resource_pool_get();
resource_pool.discard_pool.destroy_discarded_resources(device);
resource_pool.reset();
resource_pool.discard_pool.move_data(device.orphaned_data);
}
const bool reset_framebuffer = swap_chain_format_ != swap_chain_data.format ||
vk_extent_.width != swap_chain_data.extent.width ||
@@ -98,6 +103,11 @@ void VKContext::sync_backbuffer()
vk_extent_ = swap_chain_data.extent;
}
}
#if 0
else (is_background) {
discard all orphaned data
}
#endif
}
void VKContext::activate()
@@ -147,6 +157,16 @@ void VKContext::memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb)
/** \name State manager
* \{ */
VKDescriptorPools &VKContext::descriptor_pools_get()
{
return thread_data_.resource_pool_get().descriptor_pools;
}
VKDescriptorSetTracker &VKContext::descriptor_set_get()
{
return thread_data_.resource_pool_get().descriptor_set;
}
VKStateManager &VKContext::state_manager_get() const
{
return *static_cast<VKStateManager *>(state_manager);
@@ -263,8 +283,9 @@ void VKContext::update_pipeline_data(VKShader &vk_shader,
/* Update descriptor set. */
r_pipeline_data.vk_descriptor_set = VK_NULL_HANDLE;
if (vk_shader.has_descriptor_set()) {
descriptor_set_.update(*this);
r_pipeline_data.vk_descriptor_set = descriptor_set_get().active_descriptor_set()->vk_handle();
VKDescriptorSetTracker &descriptor_set = descriptor_set_get();
descriptor_set.update(*this);
r_pipeline_data.vk_descriptor_set = descriptor_set.active_descriptor_set()->vk_handle();
}
}
@@ -339,7 +360,6 @@ void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_c
#if 0
device.debug_print();
#endif
device.destroy_discarded_resources();
}
void VKContext::swap_buffers_post_handler()

View File

@@ -16,6 +16,7 @@
#include "vk_common.hh"
#include "vk_debug.hh"
#include "vk_descriptor_pools.hh"
#include "vk_resource_pool.hh"
namespace blender::gpu {
class VKFrameBuffer;
@@ -23,12 +24,10 @@ class VKVertexAttributeObject;
class VKBatch;
class VKStateManager;
class VKShader;
class VKThreadData;
class VKContext : public Context, NonCopyable {
private:
VKDescriptorPools descriptor_pools_;
VKDescriptorSetTracker descriptor_set_;
VkExtent2D vk_extent_ = {};
VkFormat swap_chain_format_ = {};
GPUTexture *surface_texture_ = nullptr;
@@ -39,10 +38,12 @@ class VKContext : public Context, NonCopyable {
bool is_init_ = false;
VKThreadData &thread_data_;
public:
render_graph::VKRenderGraph &render_graph;
VKContext(void *ghost_window, void *ghost_context, render_graph::VKRenderGraph &render_graph);
VKContext(void *ghost_window, void *ghost_context, VKThreadData &thread_data);
virtual ~VKContext();
void activate() override;
@@ -98,16 +99,8 @@ class VKContext : public Context, NonCopyable {
return static_cast<VKContext *>(Context::get());
}
VKDescriptorPools &descriptor_pools_get()
{
return descriptor_pools_;
}
VKDescriptorSetTracker &descriptor_set_get()
{
return descriptor_set_;
}
VKDescriptorPools &descriptor_pools_get();
VKDescriptorSetTracker &descriptor_set_get();
VKStateManager &state_manager_get() const;
static void swap_buffers_pre_callback(const GHOST_VulkanSwapChainData *data);

View File

@@ -40,17 +40,17 @@ void VKDevice::deinit()
return;
}
{
std::scoped_lock mutex(resources.mutex);
for (render_graph::VKRenderGraph *render_graph : render_graphs_) {
delete render_graph;
}
render_graphs_.clear();
}
dummy_buffer_.free();
samplers_.free();
destroy_discarded_resources();
{
while (!thread_data_.is_empty()) {
VKThreadData *thread_data = thread_data_.pop_last();
thread_data->deinit(*this);
delete thread_data;
}
thread_data_.clear();
}
pipelines.free_data();
vkDestroyPipelineCache(vk_device_, vk_pipeline_cache_, vk_allocation_callbacks);
descriptor_set_layouts_.deinit();
@@ -346,26 +346,65 @@ std::string VKDevice::driver_version() const
/** \} */
/* -------------------------------------------------------------------- */
/** \name VKThreadData
* \{ */
VKThreadData::VKThreadData(VKDevice &device,
pthread_t thread_id,
std::unique_ptr<render_graph::VKCommandBufferInterface> command_buffer,
render_graph::VKResourceStateTracker &resources)
: thread_id(thread_id), render_graph(std::move(command_buffer), resources)
{
for (VKResourcePool &resource_pool : swap_chain_resources) {
resource_pool.init(device);
}
}
void VKThreadData::deinit(VKDevice &device)
{
for (VKResourcePool &resource_pool : swap_chain_resources) {
resource_pool.deinit(device);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Resource management
* \{ */
render_graph::VKRenderGraph &VKDevice::render_graph()
VKThreadData &VKDevice::current_thread_data()
{
std::scoped_lock mutex(resources.mutex);
pthread_t current_thread_id = pthread_self();
for (render_graph::VKRenderGraph *render_graph : render_graphs_) {
if (pthread_equal(render_graph->thread_id, current_thread_id)) {
return *render_graph;
for (VKThreadData *thread_data : thread_data_) {
if (pthread_equal(thread_data->thread_id, current_thread_id)) {
return *thread_data;
}
}
render_graph::VKRenderGraph *render_graph = new render_graph::VKRenderGraph(
std::make_unique<render_graph::VKCommandBufferWrapper>(), resources);
render_graph->thread_id = current_thread_id;
render_graphs_.append(render_graph);
return *render_graph;
VKThreadData *thread_data = new VKThreadData(
*this,
current_thread_id,
std::make_unique<render_graph::VKCommandBufferWrapper>(),
resources);
thread_data_.append(thread_data);
return *thread_data;
}
VKDiscardPool &VKDevice::discard_pool_for_current_thread()
{
std::scoped_lock mutex(resources.mutex);
pthread_t current_thread_id = pthread_self();
for (VKThreadData *thread_data : thread_data_) {
if (pthread_equal(thread_data->thread_id, current_thread_id)) {
return thread_data->resource_pool_get().discard_pool;
}
}
return orphaned_data;
}
void VKDevice::context_register(VKContext &context)
@@ -382,43 +421,6 @@ Span<std::reference_wrapper<VKContext>> VKDevice::contexts_get() const
return contexts_;
};
void VKDevice::discard_image(VkImage vk_image, VmaAllocation vma_allocation)
{
discarded_images_.append(std::pair(vk_image, vma_allocation));
}
void VKDevice::discard_image_view(VkImageView vk_image_view)
{
discarded_image_views_.append(vk_image_view);
}
void VKDevice::discard_buffer(VkBuffer vk_buffer, VmaAllocation vma_allocation)
{
discarded_buffers_.append(std::pair(vk_buffer, vma_allocation));
}
void VKDevice::destroy_discarded_resources()
{
VK_ALLOCATION_CALLBACKS
while (!discarded_image_views_.is_empty()) {
VkImageView vk_image_view = discarded_image_views_.pop_last();
vkDestroyImageView(vk_device_, vk_image_view, vk_allocation_callbacks);
}
while (!discarded_images_.is_empty()) {
std::pair<VkImage, VmaAllocation> image_allocation = discarded_images_.pop_last();
resources.remove_image(image_allocation.first);
vmaDestroyImage(mem_allocator_get(), image_allocation.first, image_allocation.second);
}
while (!discarded_buffers_.is_empty()) {
std::pair<VkBuffer, VmaAllocation> buffer_allocation = discarded_buffers_.pop_last();
resources.remove_buffer(buffer_allocation.first);
vmaDestroyBuffer(mem_allocator_get(), buffer_allocation.first, buffer_allocation.second);
}
}
void VKDevice::memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb) const
{
VmaBudget budgets[VK_MAX_MEMORY_HEAPS];
@@ -459,10 +461,6 @@ void VKDevice::debug_print()
os << " Compute: " << pipelines.compute_pipelines_.size() << "\n";
os << "Descriptor sets\n";
os << " VkDescriptorSetLayouts: " << descriptor_set_layouts_.size() << "\n";
os << "Discarded resources\n";
os << " VkImageView: " << discarded_image_views_.size() << "\n";
os << " VkImage: " << discarded_images_.size() << "\n";
os << " VkBuffer: " << discarded_buffers_.size() << "\n";
os << "\n";
}

View File

@@ -19,6 +19,7 @@
#include "vk_descriptor_pools.hh"
#include "vk_descriptor_set_layouts.hh"
#include "vk_pipeline_pool.hh"
#include "vk_resource_pool.hh"
#include "vk_samplers.hh"
namespace blender::gpu {
@@ -54,6 +55,35 @@ struct VKWorkarounds {
} vertex_formats;
};
/**
* Shared resources between contexts that run in the same thread.
*/
class VKThreadData : public NonCopyable, NonMovable {
public:
/** Thread ID this instance belongs to. */
pthread_t thread_id;
render_graph::VKRenderGraph render_graph;
uint32_t current_swap_chain_index = UINT32_MAX;
std::array<VKResourcePool, 5> swap_chain_resources;
VKThreadData(VKDevice &device,
pthread_t thread_id,
std::unique_ptr<render_graph::VKCommandBufferInterface> command_buffer,
render_graph::VKResourceStateTracker &resources);
void deinit(VKDevice &device);
/**
* Get the active resource pool.
*/
VKResourcePool &resource_pool_get()
{
if (current_swap_chain_index >= swap_chain_resources.size()) {
return swap_chain_resources[0];
}
return swap_chain_resources[current_swap_chain_index];
}
};
class VKDevice : public NonCopyable {
private:
/** Copies of the handles owned by the GHOST context. */
@@ -99,15 +129,12 @@ class VKDevice : public NonCopyable {
/** Buffer to bind to unbound resource locations. */
VKBuffer dummy_buffer_;
Vector<std::pair<VkImage, VmaAllocation>> discarded_images_;
Vector<std::pair<VkBuffer, VmaAllocation>> discarded_buffers_;
Vector<VkImageView> discarded_image_views_;
std::string glsl_patch_;
Vector<render_graph::VKRenderGraph *> render_graphs_;
Vector<VKThreadData *> thread_data_;
public:
render_graph::VKResourceStateTracker resources;
VKDiscardPool orphaned_data;
VKPipelinePool pipelines;
/**
@@ -238,9 +265,24 @@ class VKDevice : public NonCopyable {
* \{ */
/**
* Get the render graph associated with the current thread. Create a new one when not existing.
* Get or create current thread data.
*/
render_graph::VKRenderGraph &render_graph();
VKThreadData &current_thread_data();
/**
* Get the discard pool for the current thread.
*
* When the active thread has a context a discard pool associated to the thread is returned.
* When there is no context the orphan discard pool is returned.
*
* A thread with a context can have multiple discard pools. One for each swapchain image.
* A thread without a context is most likely a discarded resource triggered during dependency
* graph update. A deps graph update from the viewport during playback or editing; or a deps
* graph update when rendering. These can happen from a different thread which will don't have a
* context at all.
*/
VKDiscardPool &discard_pool_for_current_thread();
void context_register(VKContext &context);
void context_unregister(VKContext &context);
Span<std::reference_wrapper<VKContext>> contexts_get() const;
@@ -250,11 +292,6 @@ class VKDevice : public NonCopyable {
return dummy_buffer_;
}
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 destroy_discarded_resources();
void memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb) const;
void debug_print();

View File

@@ -77,7 +77,7 @@ VKImageView::~VKImageView()
{
if (vk_image_view_ != VK_NULL_HANDLE) {
VKDevice &device = VKBackend::get().device;
device.discard_image_view(vk_image_view_);
device.discard_pool_for_current_thread().discard_image_view(vk_image_view_);
vk_image_view_ = VK_NULL_HANDLE;
}
vk_format_ = VK_FORMAT_UNDEFINED;

View File

@@ -0,0 +1,114 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_resource_pool.hh"
#include "vk_backend.hh"
#include "vk_memory.hh"
namespace blender::gpu {
void VKResourcePool::init(VKDevice &device)
{
descriptor_pools.init(device);
}
void VKResourcePool::deinit(VKDevice &device)
{
discard_pool.deinit(device);
}
void VKResourcePool::reset()
{
descriptor_pools.reset();
}
void VKDiscardPool::deinit(VKDevice &device)
{
destroy_discarded_resources(device);
}
void VKDiscardPool::move_data(VKDiscardPool &src_pool)
{
std::scoped_lock mutex(mutex_);
std::scoped_lock mutex_src(src_pool.mutex_);
buffers_.extend(src_pool.buffers_);
image_views_.extend(src_pool.image_views_);
images_.extend(src_pool.images_);
shader_modules_.extend(src_pool.shader_modules_);
pipeline_layouts_.extend(src_pool.pipeline_layouts_);
src_pool.buffers_.clear();
src_pool.image_views_.clear();
src_pool.images_.clear();
src_pool.shader_modules_.clear();
src_pool.pipeline_layouts_.clear();
}
void VKDiscardPool::discard_image(VkImage vk_image, VmaAllocation vma_allocation)
{
std::scoped_lock mutex(mutex_);
images_.append(std::pair(vk_image, vma_allocation));
}
void VKDiscardPool::discard_image_view(VkImageView vk_image_view)
{
std::scoped_lock mutex(mutex_);
image_views_.append(vk_image_view);
}
void VKDiscardPool::discard_buffer(VkBuffer vk_buffer, VmaAllocation vma_allocation)
{
std::scoped_lock mutex(mutex_);
buffers_.append(std::pair(vk_buffer, vma_allocation));
}
void VKDiscardPool::discard_shader_module(VkShaderModule vk_shader_module)
{
std::scoped_lock mutex(mutex_);
shader_modules_.append(vk_shader_module);
}
void VKDiscardPool::discard_pipeline_layout(VkPipelineLayout vk_pipeline_layout)
{
std::scoped_lock mutex(mutex_);
pipeline_layouts_.append(vk_pipeline_layout);
}
void VKDiscardPool::destroy_discarded_resources(VKDevice &device)
{
std::scoped_lock mutex(mutex_);
VK_ALLOCATION_CALLBACKS
while (!image_views_.is_empty()) {
VkImageView vk_image_view = image_views_.pop_last();
vkDestroyImageView(device.vk_handle(), vk_image_view, vk_allocation_callbacks);
}
while (!images_.is_empty()) {
std::pair<VkImage, VmaAllocation> image_allocation = images_.pop_last();
device.resources.remove_image(image_allocation.first);
vmaDestroyImage(device.mem_allocator_get(), image_allocation.first, image_allocation.second);
}
while (!buffers_.is_empty()) {
std::pair<VkBuffer, VmaAllocation> buffer_allocation = buffers_.pop_last();
device.resources.remove_buffer(buffer_allocation.first);
vmaDestroyBuffer(
device.mem_allocator_get(), buffer_allocation.first, buffer_allocation.second);
}
while (!pipeline_layouts_.is_empty()) {
VkPipelineLayout vk_pipeline_layout = pipeline_layouts_.pop_last();
vkDestroyPipelineLayout(device.vk_handle(), vk_pipeline_layout, vk_allocation_callbacks);
}
while (!shader_modules_.is_empty()) {
VkShaderModule vk_shader_module = shader_modules_.pop_last();
vkDestroyShaderModule(device.vk_handle(), vk_shader_module, vk_allocation_callbacks);
}
}
} // namespace blender::gpu

View File

@@ -0,0 +1,66 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_common.hh"
#include "vk_descriptor_pools.hh"
namespace blender::gpu {
/**
* 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 {
private:
Vector<std::pair<VkImage, VmaAllocation>> images_;
Vector<std::pair<VkBuffer, VmaAllocation>> buffers_;
Vector<VkImageView> image_views_;
Vector<VkShaderModule> shader_modules_;
Vector<VkPipelineLayout> pipeline_layouts_;
std::mutex mutex_;
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_shader_module(VkShaderModule vk_shader_module);
void discard_pipeline_layout(VkPipelineLayout vk_pipeline_layout);
/**
* 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.
*/
void move_data(VKDiscardPool &src_pool);
void destroy_discarded_resources(VKDevice &device);
};
class VKResourcePool {
public:
VKDescriptorPools descriptor_pools;
VKDescriptorSetTracker descriptor_set;
VKDiscardPool discard_pool;
void init(VKDevice &device);
void deinit(VKDevice &device);
void reset();
};
} // namespace blender::gpu

View File

@@ -580,29 +580,30 @@ void VKShader::init(const shader::ShaderCreateInfo &info, bool /*is_batch_compil
VKShader::~VKShader()
{
VK_ALLOCATION_CALLBACKS
const VKDevice &device = VKBackend::get().device;
VKDevice &device = VKBackend::get().device;
VKDiscardPool &discard_pool = device.discard_pool_for_current_thread();
if (vertex_module_ != VK_NULL_HANDLE) {
vkDestroyShaderModule(device.vk_handle(), vertex_module_, vk_allocation_callbacks);
discard_pool.discard_shader_module(vertex_module_);
vertex_module_ = VK_NULL_HANDLE;
}
if (geometry_module_ != VK_NULL_HANDLE) {
vkDestroyShaderModule(device.vk_handle(), geometry_module_, vk_allocation_callbacks);
discard_pool.discard_shader_module(geometry_module_);
geometry_module_ = VK_NULL_HANDLE;
}
if (fragment_module_ != VK_NULL_HANDLE) {
vkDestroyShaderModule(device.vk_handle(), fragment_module_, vk_allocation_callbacks);
discard_pool.discard_shader_module(fragment_module_);
fragment_module_ = VK_NULL_HANDLE;
}
if (compute_module_ != VK_NULL_HANDLE) {
vkDestroyShaderModule(device.vk_handle(), compute_module_, vk_allocation_callbacks);
discard_pool.discard_shader_module(compute_module_);
compute_module_ = VK_NULL_HANDLE;
}
if (vk_pipeline_layout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(device.vk_handle(), vk_pipeline_layout, vk_allocation_callbacks);
discard_pool.discard_pipeline_layout(vk_pipeline_layout);
vk_pipeline_layout = VK_NULL_HANDLE;
}
/* Reset not owning handles. */
/* Unset not owning handles. */
vk_descriptor_set_layout_ = VK_NULL_HANDLE;
}

View File

@@ -41,8 +41,7 @@ VKTexture::~VKTexture()
{
if (vk_image_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE) {
VKDevice &device = VKBackend::get().device;
device.discard_image(vk_image_, allocation_);
device.discard_pool_for_current_thread().discard_image(vk_image_, allocation_);
vk_image_ = VK_NULL_HANDLE;
allocation_ = VK_NULL_HANDLE;
}