Fix #128258: Vulkan: Memory leak preview job rendering

When performing preview job rendering the memory wasn't recycled leading
to a memory leak. For background rendering we already recycled memory in
a correct way. This change enables the same branch during preview
rendering.

Also adds a better `VKDevice::debug_print` to see the resources being
tracked by the different threads and resource pools.

Pull Request: https://projects.blender.org/blender/blender/pulls/128377
This commit is contained in:
Jeroen Bakker
2024-10-01 09:09:42 +02:00
parent 729004390d
commit 0eff22dd2a
5 changed files with 71 additions and 6 deletions

View File

@@ -466,10 +466,15 @@ void VKBackend::render_end()
thread_data.rendering_depth -= 1;
BLI_assert_msg(thread_data.rendering_depth >= 0, "Unbalanced `GPU_render_begin/end`");
if (G.background) {
if (G.background || !BLI_thread_is_main()) {
/* When **not** running on the main thread (or doing background rendering) we assume that there
* is no swap chain in play. Rendering happens on a single thread and when finished all the
* resources have been used and are in a state that they can be discarded. It can still be that
* a non-main thread discards a resource that is in use by another thread. We move discarded
* resources to a device global discard pool (`device.orphaned_data`). The next time the main
* thread goes to the next swap chain image the device global discard pool will be added to the
* discard pool of the new swap chain image.*/
if (thread_data.rendering_depth == 0) {
thread_data.resource_pool_next();
VKResourcePool &resource_pool = thread_data.resource_pool_get();
resource_pool.discard_pool.destroy_discarded_resources(device);
resource_pool.reset();

View File

@@ -124,7 +124,14 @@ void VKContext::deactivate()
void VKContext::begin_frame() {}
void VKContext::end_frame() {}
void VKContext::end_frame()
{
/* Enable this to track how resources are managed per thread and resource pool. */
#if 0
VKDevice &device = VKBackend::get().device;
device.debug_print();
#endif
}
void VKContext::flush() {}

View File

@@ -435,8 +435,38 @@ void VKDevice::memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb) co
/** \name Debugging/statistics
* \{ */
void VKDevice::debug_print(std::ostream &os, const VKDiscardPool &discard_pool)
{
if (discard_pool.images_.is_empty() && discard_pool.buffers_.is_empty() &&
discard_pool.image_views_.is_empty() && discard_pool.shader_modules_.is_empty() &&
discard_pool.pipeline_layouts_.is_empty())
{
return;
}
os << " Discardable resources: ";
if (!discard_pool.images_.is_empty()) {
os << "VkImage=" << discard_pool.images_.size() << " ";
}
if (!discard_pool.image_views_.is_empty()) {
os << "VkImageView=" << discard_pool.image_views_.size() << " ";
}
if (!discard_pool.buffers_.is_empty()) {
os << "VkBuffer=" << discard_pool.buffers_.size() << " ";
}
if (!discard_pool.shader_modules_.is_empty()) {
os << "VkShaderModule=" << discard_pool.shader_modules_.size() << " ";
}
if (!discard_pool.pipeline_layouts_.is_empty()) {
os << "VkPipelineLayout=" << discard_pool.pipeline_layouts_.size();
}
os << "\n";
}
void VKDevice::debug_print()
{
BLI_assert_msg(BLI_thread_is_main(),
"VKDevice::debug_print can only be called from the main thread.");
std::ostream &os = std::cout;
os << "Pipelines\n";
@@ -444,6 +474,23 @@ void VKDevice::debug_print()
os << " Compute: " << pipelines.compute_pipelines_.size() << "\n";
os << "Descriptor sets\n";
os << " VkDescriptorSetLayouts: " << descriptor_set_layouts_.size() << "\n";
for (const VKThreadData *thread_data : thread_data_) {
/* NOTE: Assumption that this is always called form the main thread. This could be solved by
* keeping track of the main thread inside the thread data.*/
const bool is_main = pthread_equal(thread_data->thread_id, pthread_self());
os << "ThreadData" << (is_main ? " (main-thread)" : "") << ")\n";
os << " Rendering_depth: " << thread_data->rendering_depth << "\n";
os << " Number of contexts: " << thread_data->num_contexts << "\n";
for (int resource_pool_index : IndexRange(thread_data->resource_pools.size())) {
const VKResourcePool &resource_pool = thread_data->resource_pools[resource_pool_index];
const bool is_active = thread_data->resource_pool_index == resource_pool_index;
os << " Resource Pool (index=" << resource_pool_index << (is_active ? " active" : "")
<< ")\n";
debug_print(os, resource_pool.discard_pool);
}
}
os << "Orphaned data\n";
debug_print(os, orphaned_data);
os << "\n";
}

View File

@@ -59,6 +59,8 @@ struct VKWorkarounds {
* Shared resources between contexts that run in the same thread.
*/
class VKThreadData : public NonCopyable, NonMovable {
static constexpr uint32_t resource_pools_count = 3;
public:
/** Thread ID this instance belongs to. */
pthread_t thread_id;
@@ -70,7 +72,7 @@ class VKThreadData : public NonCopyable, NonMovable {
* NOTE: Initialized to `UINT32_MAX` to detect first change.
*/
uint32_t resource_pool_index = UINT32_MAX;
std::array<VKResourcePool, 5> resource_pools;
std::array<VKResourcePool, resource_pools_count> resource_pools;
/**
* The current rendering depth.
@@ -112,7 +114,7 @@ class VKThreadData : public NonCopyable, NonMovable {
resource_pool_index = 1;
}
else {
resource_pool_index = (resource_pool_index + 1) % 5;
resource_pool_index = (resource_pool_index + 1) % resource_pools_count;
}
}
};
@@ -309,6 +311,7 @@ class VKDevice : public NonCopyable {
Span<std::reference_wrapper<VKContext>> contexts_get() const;
void memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb) const;
static void debug_print(std::ostream &os, const VKDiscardPool &discard_pool);
void debug_print();
/** \} */

View File

@@ -14,6 +14,7 @@
#include "vk_immediate.hh"
namespace blender::gpu {
class VKDevice;
/**
* Pool of resources that are discarded, but can still be in used and cannot be destroyed.
@@ -26,6 +27,8 @@ namespace blender::gpu {
* screen.
*/
class VKDiscardPool {
friend class VKDevice;
private:
Vector<std::pair<VkImage, VmaAllocation>> images_;
Vector<std::pair<VkBuffer, VmaAllocation>> buffers_;