From 1b4db0f2884117175ee786182fcfedceec3445b2 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Mon, 14 Sep 2026 08:05:06 +0000 Subject: [PATCH 01/19] device_memory_report: emit instant events for snapshot report Emit events on trace session start and live events. Allows tracking of memory usage over time. BUG=b/559839199 --- .../device_memory_report.cpp | 147 +++++++++++++++++- .../device_memory_report.h | 7 + .../device_memory_report_perfetto.cpp | 11 ++ layersvt/test/test_devicememoryreport.cpp | 42 +++++ 4 files changed, 204 insertions(+), 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 68fbd77cdf..56e997d0f5 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -239,13 +239,25 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { uint64_t memory_handle = mem_it->second; resource_to_memory_map_.erase(mem_it); + auto res_it = resources_.find(resource_handle); + bool is_img = (res_it != resources_.end()) ? res_it->second.is_image : false; + VkDeviceSize sub_size = (res_it != resources_.end()) ? res_it->second.size : 0; + VkDeviceSize sub_offset = 0; + std::string cluster_name = "unbound_memory"; + auto allocation_it = memory_allocations_.find(memory_handle); if (allocation_it != memory_allocations_.end()) { - auto& suballocations = allocation_it->second.sub_allocations; + auto& alloc = allocation_it->second; + if (res_it != resources_.end()) { + cluster_name = res_it->second.GetCluster(alloc.mem_flags); + } + auto& suballocations = alloc.sub_allocations; // Search by resource handle to identify which specific suballocation to remove, // since a single memory block can have multiple resources bound to it. for (auto it = suballocations.begin(); it != suballocations.end(); ++it) { if (it->resource_handle == resource_handle) { + sub_size = it->size; + sub_offset = it->offset; SubtractCounterBytes(it->usage_track, it->size); suballocations.erase(it); break; @@ -253,6 +265,15 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { } UpdateAllocationUnboundCounter(memory_handle); } + + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", "DESTROY", + "source", is_img ? "IMAGE" : "BUFFER", + "memory_object_id", memory_handle, + "size", static_cast(sub_size), + "offset", static_cast(sub_offset), + "object_handle", resource_handle, + "memory_type", cluster_name); } void DeviceMemoryReport::BindResourceMemory(uint64_t resource_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { @@ -276,6 +297,17 @@ void DeviceMemoryReport::BindResourceMemory(uint64_t resource_handle, uint64_t m // Each distinct virtual resource handle adds its virtual size to its specific category track upon binding. AddCounterBytes(new_usage_track, res_size); UpdateAllocationUnboundCounter(memory_handle); + + bool is_img = res_it->second.is_image; + std::string cluster_name = res_it->second.GetCluster(allocation.mem_flags); + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", "BIND", + "source", is_img ? "IMAGE" : "BUFFER", + "memory_object_id", memory_handle, + "size", static_cast(res_size), + "offset", static_cast(memory_offset), + "object_handle", resource_handle, + "memory_type", cluster_name); } void DeviceMemoryReport::OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { @@ -343,6 +375,64 @@ void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle) { resources_.erase(object_handle); } +void DeviceMemoryReport::DumpCurrentCountersAndAllocations() { + std::lock_guard lock(counter_mutex_); + for (const auto& pair : usage_memory_bytes_) { + if (pair.second > 0) { + TRACE_COUNTER("VulkanDeviceMemoryReport", GetCounterTrack(pair.first), pair.second); + } + } + + for (const auto& pair : memory_allocations_) { + uint64_t mem_handle = pair.first; + const auto& alloc = pair.second; + if (alloc.total_size == 0) continue; + + std::string mem_type = "unbound_memory"; + if (alloc.is_driver) { + auto res_it = resources_.find(alloc.object_handle); + if (res_it != resources_.end()) { + mem_type = res_it->second.GetCluster(alloc.mem_flags); + } + } + + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", "CREATE", + "source", alloc.is_driver ? "DRIVER" : "DEVICE_MEMORY", + "memory_object_id", mem_handle, + "size", static_cast(alloc.total_size), + "offset", static_cast(0), + "object_handle", alloc.object_handle, + "memory_type", mem_type); + + for (const auto& sub : alloc.sub_allocations) { + auto res_it = resources_.find(sub.resource_handle); + bool is_img = (res_it != resources_.end()) ? res_it->second.is_image : false; + std::string cluster_name = (res_it != resources_.end()) ? res_it->second.GetCluster(alloc.mem_flags) : "unbound_memory"; + + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", "BIND", + "source", is_img ? "IMAGE" : "BUFFER", + "memory_object_id", mem_handle, + "size", static_cast(sub.size), + "offset", static_cast(sub.offset), + "object_handle", sub.resource_handle, + "memory_type", cluster_name); + } + + if (alloc.applied_unbound_bytes > 0 && !alloc.sub_allocations.empty()) { + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", "BIND", + "source", alloc.is_driver ? "DRIVER" : "DEVICE_MEMORY", + "memory_object_id", mem_handle, + "size", static_cast(alloc.applied_unbound_bytes), + "offset", static_cast(0), + "object_handle", mem_handle, + "memory_type", "unbound_memory"); + } + } +} + void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData) { std::lock_guard lock(counter_mutex_); // For internal driver allocations, a single object (e.g. VkImage) might have multiple distinct memory allocations. @@ -350,16 +440,46 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD // For device memory allocations, objectHandle is the VkDeviceMemory handle, which we use as the key for compatibility. uint64_t key = (pCallbackData->objectType == VK_OBJECT_TYPE_DEVICE_MEMORY) ? pCallbackData->objectHandle : pCallbackData->memoryObjectId; + bool is_driver = (pCallbackData->flags & VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT) != 0; + const char* op_str = nullptr; if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT) { auto& allocation = memory_allocations_[key]; allocation.total_size = pCallbackData->size; - allocation.is_driver = (pCallbackData->flags & VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT) != 0; + allocation.is_driver = is_driver; allocation.object_handle = pCallbackData->objectHandle; + allocation.heap_index = pCallbackData->heapIndex; UpdateAllocationUnboundCounter(key); + op_str = "CREATE"; } else if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT) { RemoveAllocationTracking(key); + op_str = "DESTROY"; + } + + if (op_str != nullptr) { + uint64_t mem_obj_id = (pCallbackData->objectType == VK_OBJECT_TYPE_DEVICE_MEMORY) ? pCallbackData->objectHandle : pCallbackData->memoryObjectId; + VkDeviceSize mem_size = pCallbackData->size; + uint64_t obj_handle = pCallbackData->objectHandle; + uint32_t heap_idx = pCallbackData->heapIndex; + + const char* src_str = is_driver ? "DRIVER" : (pCallbackData->objectType == VK_OBJECT_TYPE_BUFFER ? "BUFFER" : (pCallbackData->objectType == VK_OBJECT_TYPE_IMAGE ? "IMAGE" : "DEVICE_MEMORY")); + std::string mem_type = "unbound_memory"; + if (is_driver) { + auto res_it = resources_.find(obj_handle); + if (res_it != resources_.end()) { + mem_type = res_it->second.GetCluster(0); + } + } + + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", op_str, + "source", src_str, + "memory_object_id", mem_obj_id, + "size", static_cast(mem_size), + "offset", static_cast(0), + "object_handle", obj_handle, + "memory_type", mem_type); } } @@ -390,12 +510,33 @@ void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory } else { allocation.total_size = size; allocation.is_driver = false; + allocation.object_handle = handle; + allocation.heap_index = (memory_type_index != UINT32_MAX) ? memory_type_index : 0; UpdateAllocationUnboundCounter(handle); + + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", "CREATE", + "source", "DEVICE_MEMORY", + "memory_object_id", handle, + "size", static_cast(size), + "offset", static_cast(0), + "object_handle", handle, + "memory_type", "unbound_memory"); } } void DeviceMemoryReport::OnFreeMemory(VkDevice device, VkDeviceMemory memory) { std::lock_guard lock(counter_mutex_); if (has_callback_map_[device]) return; - RemoveAllocationTracking(reinterpret_cast(memory)); + uint64_t handle = reinterpret_cast(memory); + RemoveAllocationTracking(handle); + + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", "DESTROY", + "source", "DEVICE_MEMORY", + "memory_object_id", handle, + "size", static_cast(0), + "offset", static_cast(0), + "object_handle", handle, + "memory_type", "unbound_memory"); } diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 22514516aa..0c51630b14 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -185,6 +185,12 @@ class DeviceMemoryReport { */ void OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage, VkDeviceSize size); + /** + * @brief Dumps the current state of counters and allocations to Perfetto. + * Invoked when a new Perfetto tracing session starts. + */ + void DumpCurrentCountersAndAllocations(); + /** * @brief Handles destruction of a Vulkan object, cleaning up tracked usage state. * @param object_handle The 64-bit handle of the destroyed Vulkan object. @@ -213,6 +219,7 @@ class DeviceMemoryReport { std::vector sub_allocations; std::string unbound_usage_track; uint64_t object_handle = 0; + uint32_t heap_index = 0; }; /** diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index 01b4dc258e..c3ff51c64f 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -14,6 +14,7 @@ */ #include "device_memory_report_perfetto.h" +#include "device_memory_report.h" #include #include #include @@ -22,6 +23,15 @@ PERFETTO_TRACK_EVENT_STATIC_STORAGE(); +class DeviceMemoryReportSessionObserver : public perfetto::TrackEventSessionObserver { + public: + void OnStart(const perfetto::DataSourceBase::StartArgs&) override { + DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); + } +}; + +static DeviceMemoryReportSessionObserver g_session_observer; + void InitializeDeviceMemoryReportPerfetto() { static std::once_flag init_flag; std::call_once(init_flag, []() { @@ -29,6 +39,7 @@ void InitializeDeviceMemoryReportPerfetto() { args.backends = perfetto::kSystemBackend; perfetto::Tracing::Initialize(args); perfetto::TrackEvent::Register(); + perfetto::TrackEvent::AddSessionObserver(&g_session_observer); }); } diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 6fc6c639b9..fc10605896 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -497,3 +497,45 @@ TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { vkDestroyBuffer(device, buffer, nullptr); vkDestroyDevice(device, nullptr); } + +TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { + TEST_DESCRIPTION("Test DumpCurrentCountersAndAllocations state dump and instant event emissions when a trace session begins"); + + InitializeDeviceMemoryReportPerfetto(); + + uint64_t mem_handle = 0xE001; + uint64_t buffer_handle = 0xE101; + uint64_t image_handle = 0xE102; + + // Allocate physical memory + VkDeviceMemoryReportCallbackDataEXT cb_data = {}; + cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + cb_data.memoryObjectId = 0x7000; + cb_data.size = 16384; + cb_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + cb_data.objectHandle = mem_handle; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + // Bind a buffer and an image sub-allocation + DeviceMemoryReport::Get().OnCreateBuffer(buffer_handle, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 4096); + DeviceMemoryReport::Get().OnRecordResourceSize(buffer_handle, 4096); + DeviceMemoryReport::Get().OnBindBufferMemory(buffer_handle, mem_handle, 0); + + DeviceMemoryReport::Get().OnCreateImage(image_handle, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + DeviceMemoryReport::Get().OnRecordResourceSize(image_handle, 4096); + DeviceMemoryReport::Get().OnBindImageMemory(image_handle, mem_handle, 4096); + + // Test dumping the current snapshot of counters, allocations, suballocations, and unbound memory + DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); + + // Verify cleanup + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + DeviceMemoryReport::Get().OnDestroyObject(buffer_handle); + DeviceMemoryReport::Get().OnDestroyObject(image_handle); + + EXPECT_TRUE(true); +} + From 743ae9b8b28a6a0dcbfc67d4682ba2739a767c42 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 16:20:27 +0000 Subject: [PATCH 02/19] device_memory_report: remove session observer on destruction --- .../device_memory_report_perfetto.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index c3ff51c64f..ab383fd4dd 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -23,14 +23,22 @@ PERFETTO_TRACK_EVENT_STATIC_STORAGE(); +namespace { + class DeviceMemoryReportSessionObserver : public perfetto::TrackEventSessionObserver { - public: - void OnStart(const perfetto::DataSourceBase::StartArgs&) override { - DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); - } +public: + ~DeviceMemoryReportSessionObserver() override { + perfetto::TrackEvent::RemoveSessionObserver(this); + } + + void OnStart(const perfetto::DataSourceBase::StartArgs&) override { + DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); + } }; -static DeviceMemoryReportSessionObserver g_session_observer; +DeviceMemoryReportSessionObserver g_session_observer; + +} // namespace void InitializeDeviceMemoryReportPerfetto() { static std::once_flag init_flag; From 9637937fb51cecdd694525055cd8c68344ef1b21 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 16:20:39 +0000 Subject: [PATCH 03/19] device_memory_report: dump snapshot if tracing category is already enabled --- .../device_memory_report/device_memory_report_perfetto.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index ab383fd4dd..41f2b7995c 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -48,6 +48,10 @@ void InitializeDeviceMemoryReportPerfetto() { perfetto::Tracing::Initialize(args); perfetto::TrackEvent::Register(); perfetto::TrackEvent::AddSessionObserver(&g_session_observer); + + if (TRACE_EVENT_CATEGORY_ENABLED("VulkanDeviceMemoryReport")) { + DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); + } }); } From 11f182c4114290a4efaa3396bf5352209ec7823f Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 16:21:07 +0000 Subject: [PATCH 04/19] device_memory_report: clean up Doxygen comment format --- layersvt/device_memory_report/device_memory_report.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 0c51630b14..3beb15b869 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -186,7 +186,7 @@ class DeviceMemoryReport { void OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage, VkDeviceSize size); /** - * @brief Dumps the current state of counters and allocations to Perfetto. + * Dumps the current state of counters and allocations to Perfetto. * Invoked when a new Perfetto tracing session starts. */ void DumpCurrentCountersAndAllocations(); From 69ab498b458ac6e72c5ac50bf3a3a7ca9de3a890 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 16:22:46 +0000 Subject: [PATCH 05/19] device_memory_report: remove unused heap_index member and assignments --- layersvt/device_memory_report/device_memory_report.cpp | 2 -- layersvt/device_memory_report/device_memory_report.h | 1 - 2 files changed, 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 56e997d0f5..f35cd63a2b 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -448,7 +448,6 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD allocation.total_size = pCallbackData->size; allocation.is_driver = is_driver; allocation.object_handle = pCallbackData->objectHandle; - allocation.heap_index = pCallbackData->heapIndex; UpdateAllocationUnboundCounter(key); op_str = "CREATE"; } else if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || @@ -511,7 +510,6 @@ void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory allocation.total_size = size; allocation.is_driver = false; allocation.object_handle = handle; - allocation.heap_index = (memory_type_index != UINT32_MAX) ? memory_type_index : 0; UpdateAllocationUnboundCounter(handle); TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 3beb15b869..5cfaf60fa4 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -219,7 +219,6 @@ class DeviceMemoryReport { std::vector sub_allocations; std::string unbound_usage_track; uint64_t object_handle = 0; - uint32_t heap_index = 0; }; /** From 7d233be8480ab4b3dd92f9265dfbe518853223a6 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 16:23:11 +0000 Subject: [PATCH 06/19] device_memory_report: avoid string allocation and expand variable names in RemoveResourceBinding --- .../device_memory_report.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index f35cd63a2b..5dbb9f4787 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -239,25 +239,25 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { uint64_t memory_handle = mem_it->second; resource_to_memory_map_.erase(mem_it); - auto res_it = resources_.find(resource_handle); - bool is_img = (res_it != resources_.end()) ? res_it->second.is_image : false; - VkDeviceSize sub_size = (res_it != resources_.end()) ? res_it->second.size : 0; - VkDeviceSize sub_offset = 0; - std::string cluster_name = "unbound_memory"; + auto resource_iterator = resources_.find(resource_handle); + bool is_image = (resource_iterator != resources_.end()) ? resource_iterator->second.is_image : false; + VkDeviceSize suballocation_size = (resource_iterator != resources_.end()) ? resource_iterator->second.size : 0; + VkDeviceSize suballocation_offset = 0; + const char* cluster_name = "unbound_memory"; auto allocation_it = memory_allocations_.find(memory_handle); if (allocation_it != memory_allocations_.end()) { auto& alloc = allocation_it->second; - if (res_it != resources_.end()) { - cluster_name = res_it->second.GetCluster(alloc.mem_flags); + if (resource_iterator != resources_.end()) { + cluster_name = resource_iterator->second.GetCluster(alloc.mem_flags); } auto& suballocations = alloc.sub_allocations; // Search by resource handle to identify which specific suballocation to remove, // since a single memory block can have multiple resources bound to it. for (auto it = suballocations.begin(); it != suballocations.end(); ++it) { if (it->resource_handle == resource_handle) { - sub_size = it->size; - sub_offset = it->offset; + suballocation_size = it->size; + suballocation_offset = it->offset; SubtractCounterBytes(it->usage_track, it->size); suballocations.erase(it); break; @@ -268,10 +268,10 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", "operation", "DESTROY", - "source", is_img ? "IMAGE" : "BUFFER", + "source", is_image ? "IMAGE" : "BUFFER", "memory_object_id", memory_handle, - "size", static_cast(sub_size), - "offset", static_cast(sub_offset), + "size", static_cast(suballocation_size), + "offset", static_cast(suballocation_offset), "object_handle", resource_handle, "memory_type", cluster_name); } From 8e9f450c65ddd355e1273b2255fe172833020221 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 16:24:12 +0000 Subject: [PATCH 07/19] device_memory_report: clean up OnMemoryReportEvent key, source, and cluster flags --- .../device_memory_report.cpp | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 5dbb9f4787..4b957a7b29 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -442,6 +442,7 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD bool is_driver = (pCallbackData->flags & VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT) != 0; const char* op_str = nullptr; + VkMemoryPropertyFlags mem_flags = 0; if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT) { auto& allocation = memory_allocations_[key]; @@ -450,34 +451,38 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD allocation.object_handle = pCallbackData->objectHandle; UpdateAllocationUnboundCounter(key); op_str = "CREATE"; + mem_flags = allocation.mem_flags; } else if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT) { + auto alloc_it = memory_allocations_.find(key); + if (alloc_it != memory_allocations_.end()) { + mem_flags = alloc_it->second.mem_flags; + } RemoveAllocationTracking(key); op_str = "DESTROY"; } if (op_str != nullptr) { - uint64_t mem_obj_id = (pCallbackData->objectType == VK_OBJECT_TYPE_DEVICE_MEMORY) ? pCallbackData->objectHandle : pCallbackData->memoryObjectId; - VkDeviceSize mem_size = pCallbackData->size; - uint64_t obj_handle = pCallbackData->objectHandle; - uint32_t heap_idx = pCallbackData->heapIndex; + uint64_t memory_object_id = key; + VkDeviceSize memory_size = pCallbackData->size; + uint64_t object_handle = pCallbackData->objectHandle; - const char* src_str = is_driver ? "DRIVER" : (pCallbackData->objectType == VK_OBJECT_TYPE_BUFFER ? "BUFFER" : (pCallbackData->objectType == VK_OBJECT_TYPE_IMAGE ? "IMAGE" : "DEVICE_MEMORY")); - std::string mem_type = "unbound_memory"; + const char* source_name = is_driver ? "DRIVER" : "DEVICE_MEMORY"; + const char* mem_type = "unbound_memory"; if (is_driver) { - auto res_it = resources_.find(obj_handle); + auto res_it = resources_.find(object_handle); if (res_it != resources_.end()) { - mem_type = res_it->second.GetCluster(0); + mem_type = res_it->second.GetCluster(mem_flags); } } TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", "operation", op_str, - "source", src_str, - "memory_object_id", mem_obj_id, - "size", static_cast(mem_size), + "source", source_name, + "memory_object_id", memory_object_id, + "size", static_cast(memory_size), "offset", static_cast(0), - "object_handle", obj_handle, + "object_handle", object_handle, "memory_type", mem_type); } } From 6c6a0e4f9edf47d9703880023f03629b61b3a9bd Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 16:24:35 +0000 Subject: [PATCH 08/19] device_memory_report: add test peer and verify snapshot state in test --- .../device_memory_report.h | 2 ++ layersvt/test/test_devicememoryreport.cpp | 32 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 5cfaf60fa4..6a05b6cd8c 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -198,6 +198,8 @@ class DeviceMemoryReport { void OnDestroyObject(uint64_t object_handle); private: + friend class DeviceMemoryReportTestPeer; + /** * @brief Represents a sub-allocation of a Vulkan resource (buffer or image) bound within a physical memory allocation. */ diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index fc10605896..1ca47db440 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -498,6 +498,27 @@ TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { vkDestroyDevice(device, nullptr); } +class DeviceMemoryReportTestPeer { +public: + static size_t GetAllocationCount() { + return DeviceMemoryReport::Get().memory_allocations_.size(); + } + + static const DeviceMemoryReport::MemoryAllocation* FindAllocation(uint64_t memory_handle) { + auto it = DeviceMemoryReport::Get().memory_allocations_.find(memory_handle); + return (it != DeviceMemoryReport::Get().memory_allocations_.end()) ? &it->second : nullptr; + } + + static const DeviceMemoryReport::Resource* FindResource(uint64_t resource_handle) { + auto it = DeviceMemoryReport::Get().resources_.find(resource_handle); + return (it != DeviceMemoryReport::Get().resources_.end()) ? &it->second : nullptr; + } + + static size_t GetResourceCount() { + return DeviceMemoryReport::Get().resources_.size(); + } +}; + TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { TEST_DESCRIPTION("Test DumpCurrentCountersAndAllocations state dump and instant event emissions when a trace session begins"); @@ -526,6 +547,12 @@ TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { DeviceMemoryReport::Get().OnRecordResourceSize(image_handle, 4096); DeviceMemoryReport::Get().OnBindImageMemory(image_handle, mem_handle, 4096); + const auto* allocation = DeviceMemoryReportTestPeer::FindAllocation(mem_handle); + ASSERT_NE(allocation, nullptr); + EXPECT_EQ(allocation->total_size, 16384u); + EXPECT_EQ(allocation->sub_allocations.size(), 2u); + EXPECT_EQ(allocation->applied_unbound_bytes, 8192u); + // Test dumping the current snapshot of counters, allocations, suballocations, and unbound memory DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); @@ -536,6 +563,9 @@ TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { DeviceMemoryReport::Get().OnDestroyObject(buffer_handle); DeviceMemoryReport::Get().OnDestroyObject(image_handle); - EXPECT_TRUE(true); + // Verify post-destruction state + EXPECT_EQ(DeviceMemoryReportTestPeer::FindAllocation(mem_handle), nullptr); + EXPECT_EQ(DeviceMemoryReportTestPeer::FindResource(buffer_handle), nullptr); + EXPECT_EQ(DeviceMemoryReportTestPeer::FindResource(image_handle), nullptr); } From 62c14e418f31b1614ea429400961ccd0d3d03158 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 12:34:54 +0000 Subject: [PATCH 09/19] device_memory_report: fix static destruction order of session observer --- .../device_memory_report_perfetto.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index 41f2b7995c..54c8a1bd9d 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -27,6 +27,12 @@ namespace { class DeviceMemoryReportSessionObserver : public perfetto::TrackEventSessionObserver { public: + DeviceMemoryReportSessionObserver() { + // Touch the singleton during observer construction so DeviceMemoryReport + // completes construction first and is destroyed after this observer unregisters. + (void)DeviceMemoryReport::Get(); + } + ~DeviceMemoryReportSessionObserver() override { perfetto::TrackEvent::RemoveSessionObserver(this); } @@ -36,18 +42,17 @@ class DeviceMemoryReportSessionObserver : public perfetto::TrackEventSessionObse } }; -DeviceMemoryReportSessionObserver g_session_observer; - } // namespace void InitializeDeviceMemoryReportPerfetto() { static std::once_flag init_flag; std::call_once(init_flag, []() { + static DeviceMemoryReportSessionObserver session_observer; perfetto::TracingInitArgs args; args.backends = perfetto::kSystemBackend; perfetto::Tracing::Initialize(args); perfetto::TrackEvent::Register(); - perfetto::TrackEvent::AddSessionObserver(&g_session_observer); + perfetto::TrackEvent::AddSessionObserver(&session_observer); if (TRACE_EVENT_CATEGORY_ENABLED("VulkanDeviceMemoryReport")) { DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); From 3647fdff3621db7c30c12a28c568512c8754ebc2 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 12:42:59 +0000 Subject: [PATCH 10/19] device_memory_report: cache cluster_name on MemoryAllocation --- layersvt/device_memory_report/device_memory_report.cpp | 7 ++++--- layersvt/device_memory_report/device_memory_report.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 4b957a7b29..7d89c32f79 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -209,13 +209,14 @@ void DeviceMemoryReport::UpdateAllocationUnboundCounter(uint64_t memory_handle) uint64_t new_unbound = (allocation.total_size > bound_size) ? (allocation.total_size - bound_size) : 0; - std::string track_name = "unbound_memory"; + const char* cluster_name = "unbound_memory"; auto res_it = resources_.find(allocation.object_handle); // If the memory object has an associated resource with a specific usage, use it as the track name. if (res_it != resources_.end()) { - track_name = res_it->second.GetCluster(allocation.mem_flags); + cluster_name = res_it->second.GetCluster(allocation.mem_flags); } - std::string new_unbound_track = GetUsageTrackName(allocation.is_driver, track_name); + allocation.cluster_name = cluster_name; + std::string new_unbound_track = GetUsageTrackName(allocation.is_driver, cluster_name); // If the unbound memory usage track name or the number of unbound bytes has changed, // update the global counters by subtracting the old bytes from the old track diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 6a05b6cd8c..8d9a568a5c 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -221,6 +221,7 @@ class DeviceMemoryReport { std::vector sub_allocations; std::string unbound_usage_track; uint64_t object_handle = 0; + const char* cluster_name = "unbound_memory"; }; /** From 8de673337b07d7cee83682f8e5b4e8c706092c74 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 12:45:59 +0000 Subject: [PATCH 11/19] device_memory_report: add EmitAllocationTraceEvent helper and emit suballocation DESTROY events --- .../device_memory_report.cpp | 116 ++++++++++++------ 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 7d89c32f79..450165cae8 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -233,6 +233,31 @@ void DeviceMemoryReport::UpdateAllocationUnboundCounter(uint64_t memory_handle) allocation.applied_unbound_bytes = new_unbound; } +namespace { + +struct AllocationTraceEvent { + const char* operation; + const char* source; + uint64_t memory_object_id; + VkDeviceSize size; + VkDeviceSize offset; + uint64_t object_handle; + const char* memory_type; +}; + +void EmitAllocationTraceEvent(const AllocationTraceEvent& event) { + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", + "operation", event.operation, + "source", event.source, + "memory_object_id", event.memory_object_id, + "size", static_cast(event.size), + "offset", static_cast(event.offset), + "object_handle", event.object_handle, + "memory_type", event.memory_type); +} + +} // namespace + void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { auto mem_it = resource_to_memory_map_.find(resource_handle); if (mem_it == resource_to_memory_map_.end()) return; @@ -240,41 +265,39 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { uint64_t memory_handle = mem_it->second; resource_to_memory_map_.erase(mem_it); + auto allocation_it = memory_allocations_.find(memory_handle); + if (allocation_it == memory_allocations_.end()) return; + + auto& allocation = allocation_it->second; auto resource_iterator = resources_.find(resource_handle); bool is_image = (resource_iterator != resources_.end()) ? resource_iterator->second.is_image : false; - VkDeviceSize suballocation_size = (resource_iterator != resources_.end()) ? resource_iterator->second.size : 0; - VkDeviceSize suballocation_offset = 0; - const char* cluster_name = "unbound_memory"; - - auto allocation_it = memory_allocations_.find(memory_handle); - if (allocation_it != memory_allocations_.end()) { - auto& alloc = allocation_it->second; - if (resource_iterator != resources_.end()) { - cluster_name = resource_iterator->second.GetCluster(alloc.mem_flags); + const char* cluster_name = (resource_iterator != resources_.end()) + ? resource_iterator->second.GetCluster(allocation.mem_flags) + : "unbound_memory"; + + auto& suballocations = allocation.sub_allocations; + // Search by resource handle to identify which specific suballocation to remove, + // since a single memory block can have multiple resources bound to it. + for (auto it = suballocations.begin(); it != suballocations.end(); ++it) { + if (it->resource_handle == resource_handle) { + VkDeviceSize suballocation_size = it->size; + VkDeviceSize suballocation_offset = it->offset; + SubtractCounterBytes(it->usage_track, suballocation_size); + suballocations.erase(it); + UpdateAllocationUnboundCounter(memory_handle); + + EmitAllocationTraceEvent({ + .operation = "DESTROY", + .source = is_image ? "IMAGE" : "BUFFER", + .memory_object_id = memory_handle, + .size = suballocation_size, + .offset = suballocation_offset, + .object_handle = resource_handle, + .memory_type = cluster_name, + }); + break; } - auto& suballocations = alloc.sub_allocations; - // Search by resource handle to identify which specific suballocation to remove, - // since a single memory block can have multiple resources bound to it. - for (auto it = suballocations.begin(); it != suballocations.end(); ++it) { - if (it->resource_handle == resource_handle) { - suballocation_size = it->size; - suballocation_offset = it->offset; - SubtractCounterBytes(it->usage_track, it->size); - suballocations.erase(it); - break; - } - } - UpdateAllocationUnboundCounter(memory_handle); } - - TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", - "operation", "DESTROY", - "source", is_image ? "IMAGE" : "BUFFER", - "memory_object_id", memory_handle, - "size", static_cast(suballocation_size), - "offset", static_cast(suballocation_offset), - "object_handle", resource_handle, - "memory_type", cluster_name); } void DeviceMemoryReport::BindResourceMemory(uint64_t resource_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { @@ -326,6 +349,20 @@ void DeviceMemoryReport::RemoveAllocationTracking(uint64_t memory_handle) { auto& allocation = allocation_it->second; for (const auto& suballocation : allocation.sub_allocations) { SubtractCounterBytes(suballocation.usage_track, suballocation.size); + auto resource_iterator = resources_.find(suballocation.resource_handle); + bool is_image = (resource_iterator != resources_.end()) ? resource_iterator->second.is_image : false; + const char* cluster_name = (resource_iterator != resources_.end()) + ? resource_iterator->second.GetCluster(allocation.mem_flags) + : "unbound_memory"; + EmitAllocationTraceEvent({ + .operation = "DESTROY", + .source = is_image ? "IMAGE" : "BUFFER", + .memory_object_id = memory_handle, + .size = suballocation.size, + .offset = suballocation.offset, + .object_handle = suballocation.resource_handle, + .memory_type = cluster_name, + }); resource_to_memory_map_.erase(suballocation.resource_handle); } if (allocation.applied_unbound_bytes > 0) { @@ -518,14 +555,15 @@ void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory allocation.object_handle = handle; UpdateAllocationUnboundCounter(handle); - TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", - "operation", "CREATE", - "source", "DEVICE_MEMORY", - "memory_object_id", handle, - "size", static_cast(size), - "offset", static_cast(0), - "object_handle", handle, - "memory_type", "unbound_memory"); + EmitAllocationTraceEvent({ + .operation = "CREATE", + .source = "DEVICE_MEMORY", + .memory_object_id = handle, + .size = size, + .offset = 0, + .object_handle = handle, + .memory_type = "unbound_memory", + }); } } From d213e9be1d0806719308655e40e1114f2c5c273a Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 12:47:44 +0000 Subject: [PATCH 12/19] device_memory_report: avoid string allocation and expand variable name in BindResourceMemory --- .../device_memory_report.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 450165cae8..a4b5f0c62a 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -322,16 +322,17 @@ void DeviceMemoryReport::BindResourceMemory(uint64_t resource_handle, uint64_t m AddCounterBytes(new_usage_track, res_size); UpdateAllocationUnboundCounter(memory_handle); - bool is_img = res_it->second.is_image; - std::string cluster_name = res_it->second.GetCluster(allocation.mem_flags); - TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", - "operation", "BIND", - "source", is_img ? "IMAGE" : "BUFFER", - "memory_object_id", memory_handle, - "size", static_cast(res_size), - "offset", static_cast(memory_offset), - "object_handle", resource_handle, - "memory_type", cluster_name); + bool is_image = res_it->second.is_image; + const char* cluster_name = res_it->second.GetCluster(allocation.mem_flags); + EmitAllocationTraceEvent({ + .operation = "BIND", + .source = is_image ? "IMAGE" : "BUFFER", + .memory_object_id = memory_handle, + .size = res_size, + .offset = memory_offset, + .object_handle = resource_handle, + .memory_type = cluster_name, + }); } void DeviceMemoryReport::OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { From 91d221c428fe48b37eff7ea72500f52c641299ac Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 12:48:27 +0000 Subject: [PATCH 13/19] device_memory_report: remove synthetic unbound BIND event and string copies in snapshot dump --- .../device_memory_report.cpp | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index a4b5f0c62a..433fc9e5e3 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -423,51 +423,37 @@ void DeviceMemoryReport::DumpCurrentCountersAndAllocations() { } for (const auto& pair : memory_allocations_) { - uint64_t mem_handle = pair.first; - const auto& alloc = pair.second; - if (alloc.total_size == 0) continue; + uint64_t memory_handle = pair.first; + const auto& allocation = pair.second; + if (allocation.total_size == 0) continue; - std::string mem_type = "unbound_memory"; - if (alloc.is_driver) { - auto res_it = resources_.find(alloc.object_handle); - if (res_it != resources_.end()) { - mem_type = res_it->second.GetCluster(alloc.mem_flags); - } - } - - TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", - "operation", "CREATE", - "source", alloc.is_driver ? "DRIVER" : "DEVICE_MEMORY", - "memory_object_id", mem_handle, - "size", static_cast(alloc.total_size), - "offset", static_cast(0), - "object_handle", alloc.object_handle, - "memory_type", mem_type); + const char* memory_type = allocation.is_driver ? allocation.cluster_name : "unbound_memory"; + EmitAllocationTraceEvent({ + .operation = "CREATE", + .source = allocation.is_driver ? "DRIVER" : "DEVICE_MEMORY", + .memory_object_id = memory_handle, + .size = allocation.total_size, + .offset = 0, + .object_handle = allocation.object_handle, + .memory_type = memory_type, + }); - for (const auto& sub : alloc.sub_allocations) { - auto res_it = resources_.find(sub.resource_handle); - bool is_img = (res_it != resources_.end()) ? res_it->second.is_image : false; - std::string cluster_name = (res_it != resources_.end()) ? res_it->second.GetCluster(alloc.mem_flags) : "unbound_memory"; - - TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", - "operation", "BIND", - "source", is_img ? "IMAGE" : "BUFFER", - "memory_object_id", mem_handle, - "size", static_cast(sub.size), - "offset", static_cast(sub.offset), - "object_handle", sub.resource_handle, - "memory_type", cluster_name); - } + for (const auto& suballocation : allocation.sub_allocations) { + auto resource_iterator = resources_.find(suballocation.resource_handle); + bool is_image = (resource_iterator != resources_.end()) ? resource_iterator->second.is_image : false; + const char* cluster_name = (resource_iterator != resources_.end()) + ? resource_iterator->second.GetCluster(allocation.mem_flags) + : "unbound_memory"; - if (alloc.applied_unbound_bytes > 0 && !alloc.sub_allocations.empty()) { - TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", - "operation", "BIND", - "source", alloc.is_driver ? "DRIVER" : "DEVICE_MEMORY", - "memory_object_id", mem_handle, - "size", static_cast(alloc.applied_unbound_bytes), - "offset", static_cast(0), - "object_handle", mem_handle, - "memory_type", "unbound_memory"); + EmitAllocationTraceEvent({ + .operation = "BIND", + .source = is_image ? "IMAGE" : "BUFFER", + .memory_object_id = memory_handle, + .size = suballocation.size, + .offset = suballocation.offset, + .object_handle = suballocation.resource_handle, + .memory_type = cluster_name, + }); } } } From 888957aef78cac3aa50259455fc33aebde12e0ba Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 13:11:02 +0000 Subject: [PATCH 14/19] device_memory_report: align key with non-driver check and use cached cluster_name in OnMemoryReportEvent --- .../device_memory_report.cpp | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 433fc9e5e3..c00c88c07f 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -463,11 +463,13 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD // For internal driver allocations, a single object (e.g. VkImage) might have multiple distinct memory allocations. // We must use memoryObjectId as the key so each allocation is tracked separately and can be individually freed. // For device memory allocations, objectHandle is the VkDeviceMemory handle, which we use as the key for compatibility. - uint64_t key = (pCallbackData->objectType == VK_OBJECT_TYPE_DEVICE_MEMORY) ? pCallbackData->objectHandle : pCallbackData->memoryObjectId; - bool is_driver = (pCallbackData->flags & VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT) != 0; - const char* op_str = nullptr; - VkMemoryPropertyFlags mem_flags = 0; + uint64_t key = (!is_driver && pCallbackData->objectType == VK_OBJECT_TYPE_DEVICE_MEMORY) + ? pCallbackData->objectHandle + : pCallbackData->memoryObjectId; + + const char* operation_name = nullptr; + const char* memory_type = "unbound_memory"; if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT) { auto& allocation = memory_allocations_[key]; @@ -475,40 +477,28 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD allocation.is_driver = is_driver; allocation.object_handle = pCallbackData->objectHandle; UpdateAllocationUnboundCounter(key); - op_str = "CREATE"; - mem_flags = allocation.mem_flags; + operation_name = "CREATE"; + memory_type = is_driver ? allocation.cluster_name : "unbound_memory"; } else if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT) { - auto alloc_it = memory_allocations_.find(key); - if (alloc_it != memory_allocations_.end()) { - mem_flags = alloc_it->second.mem_flags; + auto allocation_iterator = memory_allocations_.find(key); + if (allocation_iterator != memory_allocations_.end()) { + memory_type = is_driver ? allocation_iterator->second.cluster_name : "unbound_memory"; } RemoveAllocationTracking(key); - op_str = "DESTROY"; + operation_name = "DESTROY"; } - if (op_str != nullptr) { - uint64_t memory_object_id = key; - VkDeviceSize memory_size = pCallbackData->size; - uint64_t object_handle = pCallbackData->objectHandle; - - const char* source_name = is_driver ? "DRIVER" : "DEVICE_MEMORY"; - const char* mem_type = "unbound_memory"; - if (is_driver) { - auto res_it = resources_.find(object_handle); - if (res_it != resources_.end()) { - mem_type = res_it->second.GetCluster(mem_flags); - } - } - - TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", - "operation", op_str, - "source", source_name, - "memory_object_id", memory_object_id, - "size", static_cast(memory_size), - "offset", static_cast(0), - "object_handle", object_handle, - "memory_type", mem_type); + if (operation_name != nullptr) { + EmitAllocationTraceEvent({ + .operation = operation_name, + .source = is_driver ? "DRIVER" : "DEVICE_MEMORY", + .memory_object_id = key, + .size = pCallbackData->size, + .offset = 0, + .object_handle = pCallbackData->objectHandle, + .memory_type = memory_type, + }); } } From 630018836318a016bd6d4d6980296a7b6377f284 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 13:12:36 +0000 Subject: [PATCH 15/19] device_memory_report: look up freed size and suppress phantom DESTROY in OnFreeMemory --- .../device_memory_report.cpp | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index c00c88c07f..194e80c8f0 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -548,14 +548,19 @@ void DeviceMemoryReport::OnFreeMemory(VkDevice device, VkDeviceMemory memory) { std::lock_guard lock(counter_mutex_); if (has_callback_map_[device]) return; uint64_t handle = reinterpret_cast(memory); + auto allocation_iterator = memory_allocations_.find(handle); + if (allocation_iterator == memory_allocations_.end()) return; + + VkDeviceSize freed_size = allocation_iterator->second.total_size; RemoveAllocationTracking(handle); - TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanMemoryAllocation", - "operation", "DESTROY", - "source", "DEVICE_MEMORY", - "memory_object_id", handle, - "size", static_cast(0), - "offset", static_cast(0), - "object_handle", handle, - "memory_type", "unbound_memory"); + EmitAllocationTraceEvent({ + .operation = "DESTROY", + .source = "DEVICE_MEMORY", + .memory_object_id = handle, + .size = freed_size, + .offset = 0, + .object_handle = handle, + .memory_type = "unbound_memory", + }); } From f7120310bacef8f61eabde7990a585e80030eaa2 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 13:14:52 +0000 Subject: [PATCH 16/19] device_memory_report: improve thread safety and post-dump assertions in test --- layersvt/test/test_devicememoryreport.cpp | 77 +++++++++++++---------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 1ca47db440..28904c4eae 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -20,6 +20,7 @@ #include #include +#include static const char* kLayerName = "VK_LAYER_GOOGLE_DeviceMemoryReport"; @@ -500,22 +501,24 @@ TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { class DeviceMemoryReportTestPeer { public: - static size_t GetAllocationCount() { - return DeviceMemoryReport::Get().memory_allocations_.size(); - } - - static const DeviceMemoryReport::MemoryAllocation* FindAllocation(uint64_t memory_handle) { - auto it = DeviceMemoryReport::Get().memory_allocations_.find(memory_handle); - return (it != DeviceMemoryReport::Get().memory_allocations_.end()) ? &it->second : nullptr; - } - - static const DeviceMemoryReport::Resource* FindResource(uint64_t resource_handle) { - auto it = DeviceMemoryReport::Get().resources_.find(resource_handle); - return (it != DeviceMemoryReport::Get().resources_.end()) ? &it->second : nullptr; + static std::optional FindAllocation(uint64_t memory_handle) { + auto& report = DeviceMemoryReport::Get(); + std::lock_guard lock(report.counter_mutex_); + auto it = report.memory_allocations_.find(memory_handle); + if (it == report.memory_allocations_.end()) { + return std::nullopt; + } + return it->second; } - static size_t GetResourceCount() { - return DeviceMemoryReport::Get().resources_.size(); + static std::optional FindResource(uint64_t resource_handle) { + auto& report = DeviceMemoryReport::Get(); + std::lock_guard lock(report.counter_mutex_); + auto it = report.resources_.find(resource_handle); + if (it == report.resources_.end()) { + return std::nullopt; + } + return it->second; } }; @@ -524,48 +527,56 @@ TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { InitializeDeviceMemoryReportPerfetto(); - uint64_t mem_handle = 0xE001; + uint64_t memory_handle = 0xE001; uint64_t buffer_handle = 0xE101; uint64_t image_handle = 0xE102; // Allocate physical memory - VkDeviceMemoryReportCallbackDataEXT cb_data = {}; - cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; - cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; - cb_data.memoryObjectId = 0x7000; - cb_data.size = 16384; - cb_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; - cb_data.objectHandle = mem_handle; - DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + VkDeviceMemoryReportCallbackDataEXT callback_data = {}; + callback_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + callback_data.memoryObjectId = 0x7000; + callback_data.size = 16384; + callback_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + callback_data.objectHandle = memory_handle; + DeviceMemoryReport::MemoryReportCallback(&callback_data, nullptr); // Bind a buffer and an image sub-allocation DeviceMemoryReport::Get().OnCreateBuffer(buffer_handle, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 4096); DeviceMemoryReport::Get().OnRecordResourceSize(buffer_handle, 4096); - DeviceMemoryReport::Get().OnBindBufferMemory(buffer_handle, mem_handle, 0); + DeviceMemoryReport::Get().OnBindBufferMemory(buffer_handle, memory_handle, 0); DeviceMemoryReport::Get().OnCreateImage(image_handle, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); DeviceMemoryReport::Get().OnRecordResourceSize(image_handle, 4096); - DeviceMemoryReport::Get().OnBindImageMemory(image_handle, mem_handle, 4096); + DeviceMemoryReport::Get().OnBindImageMemory(image_handle, memory_handle, 4096); - const auto* allocation = DeviceMemoryReportTestPeer::FindAllocation(mem_handle); - ASSERT_NE(allocation, nullptr); + auto allocation = DeviceMemoryReportTestPeer::FindAllocation(memory_handle); + ASSERT_TRUE(allocation.has_value()); EXPECT_EQ(allocation->total_size, 16384u); EXPECT_EQ(allocation->sub_allocations.size(), 2u); EXPECT_EQ(allocation->applied_unbound_bytes, 8192u); - // Test dumping the current snapshot of counters, allocations, suballocations, and unbound memory + // Test dumping the current snapshot of counters, allocations, and suballocations DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); + // Verify that dumping state is non-destructive and preserves allocation invariants + auto post_dump_allocation = DeviceMemoryReportTestPeer::FindAllocation(memory_handle); + ASSERT_TRUE(post_dump_allocation.has_value()); + EXPECT_EQ(post_dump_allocation->total_size, 16384u); + EXPECT_EQ(post_dump_allocation->sub_allocations.size(), 2u); + EXPECT_EQ(post_dump_allocation->applied_unbound_bytes, 8192u); + // Verify cleanup - cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; - DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&callback_data, nullptr); DeviceMemoryReport::Get().OnDestroyObject(buffer_handle); DeviceMemoryReport::Get().OnDestroyObject(image_handle); // Verify post-destruction state - EXPECT_EQ(DeviceMemoryReportTestPeer::FindAllocation(mem_handle), nullptr); - EXPECT_EQ(DeviceMemoryReportTestPeer::FindResource(buffer_handle), nullptr); - EXPECT_EQ(DeviceMemoryReportTestPeer::FindResource(image_handle), nullptr); + EXPECT_FALSE(DeviceMemoryReportTestPeer::FindAllocation(memory_handle).has_value()); + EXPECT_FALSE(DeviceMemoryReportTestPeer::FindResource(buffer_handle).has_value()); + EXPECT_FALSE(DeviceMemoryReportTestPeer::FindResource(image_handle).has_value()); } + From e642ff857cad3736bdb3ab0e485b5e9e8de30be8 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 20:36:55 +0000 Subject: [PATCH 17/19] device_memory_report: suppress phantom DESTROY in OnMemoryReportEvent Return early when handling FREE_EXT or UNIMPORT_EXT for allocations that are not tracked in memory_allocations_, preventing phantom DESTROY trace events from being emitted. --- layersvt/device_memory_report/device_memory_report.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 194e80c8f0..30b39619d4 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -482,9 +482,9 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD } else if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT) { auto allocation_iterator = memory_allocations_.find(key); - if (allocation_iterator != memory_allocations_.end()) { - memory_type = is_driver ? allocation_iterator->second.cluster_name : "unbound_memory"; - } + if (allocation_iterator == memory_allocations_.end()) return; + + memory_type = is_driver ? allocation_iterator->second.cluster_name : "unbound_memory"; RemoveAllocationTracking(key); operation_name = "DESTROY"; } From f14a2a3ed10ad58923823be6385a1aa32a93551c Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 17 Sep 2026 14:04:36 +0000 Subject: [PATCH 18/19] device_memory_report: document DumpCurrentCountersAndAllocations locking contract --- layersvt/device_memory_report/device_memory_report.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 3dcb790658..236a9a5c35 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -193,8 +193,9 @@ class DeviceMemoryReport { void OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage, VkDeviceSize size); /** - * Dumps the current state of counters and allocations to Perfetto. - * Invoked when a new Perfetto tracing session starts. + * @brief Dumps the current state of counters and allocations to Perfetto. + * Invoked when a new Perfetto tracing session starts. Acquires counter_mutex_, + * so it must not be called while that lock is held. */ void DumpCurrentCountersAndAllocations(); From d2fc2d68f8c7b2a060e20726e8a0680440f61407 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 17 Sep 2026 14:05:51 +0000 Subject: [PATCH 19/19] device_memory_report: use tracked total_size for DESTROY event in OnMemoryReportEvent --- layersvt/device_memory_report/device_memory_report.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 547220c815..cac4cea1ee 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -480,6 +480,7 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD const char* operation_name = nullptr; const char* memory_type = "unbound_memory"; + VkDeviceSize event_size = pCallbackData->size; if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT) { auto& allocation = memory_allocations_[key]; @@ -496,6 +497,7 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD if (allocation_iterator == memory_allocations_.end()) return; memory_type = is_driver ? allocation_iterator->second.cluster_name : "unbound_memory"; + event_size = allocation_iterator->second.total_size; RemoveAllocationTracking(key); operation_name = "DESTROY"; } @@ -505,7 +507,7 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD .operation = operation_name, .source = is_driver ? "DRIVER" : "DEVICE_MEMORY", .memory_object_id = key, - .size = pCallbackData->size, + .size = event_size, .offset = 0, .object_handle = pCallbackData->objectHandle, .memory_type = memory_type,