From 1c253ee2714e654e4e54fe0352fb5d915f13e177 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 6 Apr 2018 21:14:53 -0700 Subject: [PATCH] Vulkan: Add code to track allocator usage info. This way we can potentially debug leaks, if any. --- Common/Vulkan/VulkanImage.cpp | 8 +++++++- Common/Vulkan/VulkanImage.h | 9 +++++++++ Common/Vulkan/VulkanMemory.cpp | 32 ++++++++++++++++++++++++++++---- Common/Vulkan/VulkanMemory.h | 20 ++++++++++++++++++-- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/Common/Vulkan/VulkanImage.cpp b/Common/Vulkan/VulkanImage.cpp index 4e8b841517..956dc02142 100644 --- a/Common/Vulkan/VulkanImage.cpp +++ b/Common/Vulkan/VulkanImage.cpp @@ -74,7 +74,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips, vkGetImageMemoryRequirements(vulkan_->GetDevice(), image_, &mem_reqs); if (allocator_) { - offset_ = allocator_->Allocate(mem_reqs, &mem_); + offset_ = allocator_->Allocate(mem_reqs, &mem_, Tag()); if (offset_ == VulkanDeviceAllocator::ALLOCATE_FAILED) { return false; } @@ -207,6 +207,12 @@ void VulkanTexture::EndCreate(VkCommandBuffer cmd, bool vertexTexture) { VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); } +void VulkanTexture::Touch() { + if (allocator_ && mem_ != VK_NULL_HANDLE) { + allocator_->Touch(mem_, offset_); + } +} + void VulkanTexture::Destroy() { if (view_ != VK_NULL_HANDLE) { vulkan_->Delete().QueueDeleteImageView(view_); diff --git a/Common/Vulkan/VulkanImage.h b/Common/Vulkan/VulkanImage.h index 14c347078c..9a23d91784 100644 --- a/Common/Vulkan/VulkanImage.h +++ b/Common/Vulkan/VulkanImage.h @@ -25,6 +25,14 @@ public: void Destroy(); + void SetTag(const std::string &tag) { + tag_ = tag; + } + std::string Tag() const { + return tag_; + } + void Touch(); + // Used in image copies, etc. VkImage GetImage() const { return image_; } @@ -49,4 +57,5 @@ private: VkFormat format_ = VK_FORMAT_UNDEFINED; VulkanDeviceAllocator *allocator_; size_t offset_ = 0; + std::string tag_; }; diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 4107089463..9bd3d36452 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -20,6 +20,7 @@ #include "Common/Log.h" #include "Common/Vulkan/VulkanMemory.h" +#include "base/timeutil.h" #include "math/math_util.h" VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, size_t size) : device_(vulkan->GetDevice()), buf_(0), offset_(0), size_(size), writePtr_(nullptr) { @@ -186,7 +187,7 @@ void VulkanDeviceAllocator::Destroy() { destroyed_ = true; } -size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory) { +size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory, const std::string &tag) { assert(!destroyed_); uint32_t memoryTypeIndex; bool pass = vulkan_->MemoryTypeFromProperties(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memoryTypeIndex); @@ -216,7 +217,7 @@ size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDevic while (start < slab.usage.size()) { start = (start + align - 1) & ~(align - 1); - if (AllocateFromSlab(slab, start, blocks)) { + if (AllocateFromSlab(slab, start, blocks, tag)) { // Allocated? Great, let's return right away. *deviceMemory = slab.deviceMemory; lastSlab_ = actualSlab; @@ -233,7 +234,7 @@ size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDevic // Guaranteed to be the last one, unless it failed to allocate. Slab &slab = slabs_[slabs_.size() - 1]; size_t start = 0; - if (AllocateFromSlab(slab, start, blocks)) { + if (AllocateFromSlab(slab, start, blocks, tag)) { *deviceMemory = slab.deviceMemory; lastSlab_ = slabs_.size() - 1; return start << SLAB_GRAIN_SHIFT; @@ -243,7 +244,7 @@ size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDevic return ALLOCATE_FAILED; } -bool VulkanDeviceAllocator::AllocateFromSlab(Slab &slab, size_t &start, size_t blocks) { +bool VulkanDeviceAllocator::AllocateFromSlab(Slab &slab, size_t &start, size_t blocks, const std::string &tag) { assert(!destroyed_); bool matched = true; @@ -277,6 +278,7 @@ bool VulkanDeviceAllocator::AllocateFromSlab(Slab &slab, size_t &start, size_t b // Remember the size so we can free. slab.allocSizes[start] = blocks; + slab.tags[start] = { tag, time_now(), 0.0f }; return true; } @@ -299,6 +301,24 @@ std::vector VulkanDeviceAllocator::GetSlabUsage(int slabIndex) const { return slab.usage; } +void VulkanDeviceAllocator::DoTouch(VkDeviceMemory deviceMemory, size_t offset) { + size_t start = offset >> SLAB_GRAIN_SHIFT; + bool found = false; + for (Slab &slab : slabs_) { + if (slab.deviceMemory != deviceMemory) { + continue; + } + + auto it = slab.tags.find(start); + if (it != slab.tags.end()) { + it->second.touched = time_now(); + found = true; + } + } + + _assert_msg_(G3D, found, "Failed to find allocation to touch - use after free?"); +} + void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) { assert(!destroyed_); @@ -361,6 +381,10 @@ void VulkanDeviceAllocator::ExecuteFree(FreeInfo *userdata) { // Ack, a double free? _assert_msg_(G3D, false, "Double free? Block missing at offset %d", (int)userdata->offset); } + auto itTag = slab.tags.find(start); + if (itTag != slab.tags.end()) { + slab.tags.erase(itTag); + } found = true; break; } diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index 1a74cbceb0..1a53c28504 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -139,12 +139,20 @@ public: } // May return ALLOCATE_FAILED if the allocation fails. - size_t Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory); + size_t Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory, const std::string &tag); // Crashes on a double or misfree. void Free(VkDeviceMemory deviceMemory, size_t offset); + inline void Touch(VkDeviceMemory deviceMemory, size_t offset) { + if (TRACK_TOUCH) { + DoTouch(deviceMemory, offset); + } + } + static const size_t ALLOCATE_FAILED = -1; + // Set to true to report potential leaks / long held textures. + static const bool TRACK_TOUCH = false; int GetSlabCount() const { return (int)slabs_.size(); } int GetMinSlabSize() const { return (int)minSlabSize_; } @@ -158,10 +166,17 @@ private: static const uint8_t SLAB_GRAIN_SHIFT = 10; static const uint32_t UNDEFINED_MEMORY_TYPE = -1; + struct UsageInfo { + std::string tag; + float created; + float touched; + }; + struct Slab { VkDeviceMemory deviceMemory; std::vector usage; std::unordered_map allocSizes; + std::unordered_map tags; size_t nextFree; size_t Size() { @@ -185,8 +200,9 @@ private: } bool AllocateSlab(VkDeviceSize minBytes); - bool AllocateFromSlab(Slab &slab, size_t &start, size_t blocks); + bool AllocateFromSlab(Slab &slab, size_t &start, size_t blocks, const std::string &tag); void Decimate(); + void DoTouch(VkDeviceMemory deviceMemory, size_t offset); void ExecuteFree(FreeInfo *userdata); VulkanContext *const vulkan_;