Vulkan: Fix Promise leak and unguarded array write in DestroyVariants

The loop queue-deleted the VkPipeline and nulled the slot without deleting the
Promise the array owns - DestroyVariantsInstant right below it shows the intended
ownership. That's one leaked Promise per destroyed variant per cached pipeline,
on every MSAA or resolution change.

It also wrote pipeline[] without taking mutex_, which the header documents as
protecting that array and which the render thread holds while reading and
replacing the same slots in PerformRenderPass. The two have to be fixed together:
the missing delete was the only thing keeping this a leak rather than a
use-after-free.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vd8ntC2brCUtCrDJMqLbs8
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-08-29 11:42:47 +02:00
1 parent 0655fcc711
commit b2c74e205a
1 file changed
+9
@@ -168,6 +168,12 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR
}
void VKRGraphicsPipeline::DestroyVariants(VulkanContext *vulkan, bool msaaOnly) {
// Called from InvalidateMSAAPipelines on the main thread, mid-frame, while the render thread may be
// reading and replacing these same slots in PerformRenderPass - so take the lock that's documented
// as protecting the array. It also has to be held across the delete below, or the render thread can
// be left holding a freed Promise.
std::lock_guard<std::mutex> lock(mutex_);
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
if (!this->pipeline[i])
continue;
@@ -179,6 +185,9 @@ void VKRGraphicsPipeline::DestroyVariants(VulkanContext *vulkan, bool msaaOnly)
if (pipeline) {
vulkan->Delete().QueueDeletePipeline(pipeline);
}
// The array owns the Promise - DestroyVariantsInstant deletes it too. Forgetting it here leaked
// one per destroyed variant on every MSAA or resolution change.
delete this->pipeline[i];
this->pipeline[i] = nullptr;
}
sampleCount_ = VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM;