diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index c18c850d9f..dd915f648c 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -52,7 +52,8 @@ void DeviceMemoryReport::SetDeviceMemoryProperties(VkDevice device, const VkPhys void DeviceMemoryReport::SetAllocationMemoryProperties(uint64_t memory_handle, VkMemoryPropertyFlags property_flags) { std::lock_guard lock(counter_mutex_); - auto& allocation = memory_allocations_[memory_handle]; + const AllocationKey key = AllocationKey::App(memory_handle); + auto& allocation = memory_allocations_[key]; allocation.mem_flags = property_flags; // An allocation with total_size > 0 represents an active physical memory allocation that // has already been recorded and contributed to Perfetto counter tracks. @@ -63,7 +64,7 @@ void DeviceMemoryReport::SetAllocationMemoryProperties(uint64_t memory_handle, V // If total_size == 0, the allocation has not yet been instantiated (or is being configured // prior to allocation), so no counter bytes have been emitted to Perfetto yet. if (allocation.total_size > 0) { - UpdateAllocationUnboundCounter(memory_handle); + UpdateAllocationUnboundCounter(key); } } @@ -172,8 +173,8 @@ void DeviceMemoryReport::SubtractCounterBytes(const std::string& track, uint64_t TRACE_COUNTER("VulkanDeviceMemoryReport", GetCounterTrack(track), bytes); } -void DeviceMemoryReport::UpdateAllocationUnboundCounter(uint64_t memory_handle) { - auto allocation_it = memory_allocations_.find(memory_handle); +void DeviceMemoryReport::UpdateAllocationUnboundCounter(const AllocationKey& key) { + auto allocation_it = memory_allocations_.find(key); if (allocation_it == memory_allocations_.end()) return; auto& allocation = allocation_it->second; @@ -209,13 +210,14 @@ void DeviceMemoryReport::UpdateAllocationUnboundCounter(uint64_t memory_handle) uint64_t new_unbound = (allocation.total_size > bound_size) ? (allocation.total_size - bound_size) : 0; - const char* cluster_name = "unbound_memory"; + const char* cluster_name = allocation.is_driver ? "driver" : "unbound_memory"; if (allocation.is_driver && (allocation.object_type == VK_OBJECT_TYPE_IMAGE || allocation.object_type == VK_OBJECT_TYPE_BUFFER)) { 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()) { + const bool expected_is_image = (allocation.object_type == VK_OBJECT_TYPE_IMAGE); + // If the memory object has an associated resource of the matching type, use it as the track name. + if (res_it != resources_.end() && res_it->second.is_image == expected_is_image) { cluster_name = res_it->second.GetCluster(allocation.mem_flags); } } @@ -286,7 +288,7 @@ 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); + auto allocation_it = memory_allocations_.find(AllocationKey::App(memory_handle)); if (allocation_it == memory_allocations_.end()) return; auto& allocation = allocation_it->second; @@ -305,7 +307,7 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { VkDeviceSize suballocation_offset = it->offset; SubtractCounterBytes(it->usage_track, suballocation_size); suballocations.erase(it); - UpdateAllocationUnboundCounter(memory_handle); + UpdateAllocationUnboundCounter(AllocationKey::App(memory_handle)); EmitAllocationTraceEvent({ .operation = "DESTROY", @@ -329,7 +331,8 @@ void DeviceMemoryReport::BindResourceMemory(uint64_t resource_handle, uint64_t m // If the same resource handle is passed more than once, remove stale bindings first. RemoveResourceBinding(resource_handle); - auto& allocation = memory_allocations_[memory_handle]; + const AllocationKey key = AllocationKey::App(memory_handle); + auto& allocation = memory_allocations_[key]; VkDeviceSize res_size = res_it->second.size; std::string new_usage_track = GetUsageTrackName(allocation.is_driver, res_it->second.GetCluster(allocation.mem_flags)); @@ -341,7 +344,7 @@ 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); + UpdateAllocationUnboundCounter(key); bool is_image = res_it->second.is_image; const char* cluster_name = res_it->second.GetCluster(allocation.mem_flags); @@ -364,8 +367,8 @@ void DeviceMemoryReport::OnBindImageMemory(uint64_t image_handle, uint64_t memor BindResourceMemory(image_handle, memory_handle, memory_offset); } -void DeviceMemoryReport::RemoveAllocationTracking(uint64_t memory_handle) { - auto allocation_it = memory_allocations_.find(memory_handle); +void DeviceMemoryReport::RemoveAllocationTracking(const AllocationKey& key) { + auto allocation_it = memory_allocations_.find(key); if (allocation_it == memory_allocations_.end()) return; auto& allocation = allocation_it->second; @@ -379,7 +382,7 @@ void DeviceMemoryReport::RemoveAllocationTracking(uint64_t memory_handle) { EmitAllocationTraceEvent({ .operation = "DESTROY", .source = is_image ? "IMAGE" : "BUFFER", - .memory_object_id = memory_handle, + .memory_object_id = key.id, .size = suballocation.size, .offset = suballocation.offset, .object_handle = suballocation.resource_handle, @@ -421,16 +424,48 @@ void DeviceMemoryReport::Reset() { usage_memory_bytes_.clear(); debug_object_names_.clear(); } + +void DeviceMemoryReport::ReattributeDriverAllocations(uint64_t object_handle, VkObjectType object_type) { + for (auto& pair : memory_allocations_) { + const AllocationKey& key = pair.first; + MemoryAllocation& allocation = pair.second; + if (!key.is_driver || allocation.object_type != object_type || allocation.object_handle != object_handle) { + continue; + } + const char* old_cluster_name = allocation.cluster_name; + UpdateAllocationUnboundCounter(key); + if (allocation.total_size == 0 || std::string_view(old_cluster_name) == allocation.cluster_name) { + continue; + } + // Re-attribution is published as DESTROY (old cluster) + CREATE (new cluster) so that + // consumers summing CREATE/DESTROY sizes per memory_type stay balanced. + EmitAllocationTraceEvent({ + .operation = "DESTROY", + .source = "DRIVER", + .memory_object_id = key.id, + .size = allocation.total_size, + .offset = 0, + .object_handle = allocation.object_handle, + .memory_type = old_cluster_name, + }); + EmitAllocationTraceEvent({ + .operation = "CREATE", + .source = "DRIVER", + .memory_object_id = key.id, + .size = allocation.total_size, + .offset = 0, + .object_handle = allocation.object_handle, + .memory_type = allocation.cluster_name, + }); + } +} + void DeviceMemoryReport::OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage) { std::lock_guard lock(counter_mutex_); auto& res = resources_[image_handle]; res.is_image = true; res.image_usage = usage; - for (const auto& pair : memory_allocations_) { - if (pair.second.is_driver && pair.second.object_type == VK_OBJECT_TYPE_IMAGE && pair.second.object_handle == image_handle) { - UpdateAllocationUnboundCounter(pair.first); - } - } + ReattributeDriverAllocations(image_handle, VK_OBJECT_TYPE_IMAGE); } void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage, VkDeviceSize size) { @@ -439,11 +474,7 @@ void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFla res.is_image = false; res.buffer_usage = usage; res.size = size; - for (const auto& pair : memory_allocations_) { - if (pair.second.is_driver && pair.second.object_type == VK_OBJECT_TYPE_BUFFER && pair.second.object_handle == buffer_handle) { - UpdateAllocationUnboundCounter(pair.first); - } - } + ReattributeDriverAllocations(buffer_handle, VK_OBJECT_TYPE_BUFFER); } void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle, VkObjectType object_type) { @@ -528,7 +559,7 @@ void DeviceMemoryReport::DumpCurrentCountersAndAllocations() { } for (const auto& pair : memory_allocations_) { - uint64_t memory_handle = pair.first; + uint64_t memory_handle = pair.first.id; const auto& allocation = pair.second; if (allocation.total_size == 0) continue; @@ -570,19 +601,47 @@ void DeviceMemoryReport::DumpCurrentCountersAndAllocations() { 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. - // 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. - bool is_driver = (pCallbackData->flags & VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT) != 0; - uint64_t key = (!is_driver && pCallbackData->objectType == VK_OBJECT_TYPE_DEVICE_MEMORY) - ? pCallbackData->objectHandle - : pCallbackData->memoryObjectId; + // Per the VK_EXT_device_memory_report specification, VkDeviceMemoryReportFlagsEXT is reserved and always 0. + // Explicit application allocations (vkAllocateMemory / memory imports) report VK_OBJECT_TYPE_DEVICE_MEMORY + // with objectHandle set to the VkDeviceMemory handle, which we use as the key for compatibility with Bind*Memory. + // Driver-internal allocations report other objectTypes (or VK_OBJECT_TYPE_UNKNOWN) and a single object + // (e.g. VkImage) may have multiple distinct memory allocations, so we use memoryObjectId as the key. + const bool is_driver = pCallbackData->objectType != VK_OBJECT_TYPE_DEVICE_MEMORY; + const AllocationKey key = is_driver ? AllocationKey::Driver(pCallbackData->memoryObjectId) + : AllocationKey::App(pCallbackData->objectHandle); + const char* source = is_driver ? "DRIVER" : "DEVICE_MEMORY"; const char* operation_name = nullptr; const char* memory_type = "unbound_memory"; VkDeviceSize event_size = pCallbackData->size; + uint64_t event_object_handle = pCallbackData->objectHandle; if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT) { + // An entry with total_size == 0 is a placeholder created ahead of the allocation (e.g. by + // SetAllocationMemoryProperties or BindResourceMemory) and must be kept so its mem_flags and + // bindings carry over. An entry with total_size > 0 is a live allocation for which no + // FREE/UNIMPORT arrived; retire it (counters and trace) so its bytes do not leak. + auto existing_it = memory_allocations_.find(key); + if (existing_it != memory_allocations_.end() && existing_it->second.total_size > 0) { + const MemoryAllocation& stale = existing_it->second; + const char* stale_memory_type = is_driver ? stale.cluster_name : "unbound_memory"; + const VkDeviceSize stale_size = stale.total_size; + const uint64_t stale_object_handle = stale.object_handle; + RemoveAllocationTracking(key); + EmitAllocationTraceEvent({ + .operation = "DESTROY", + .source = source, + .memory_object_id = key.id, + .size = stale_size, + .offset = 0, + .object_handle = stale_object_handle, + .memory_type = stale_memory_type, + }); + if (!is_driver && + debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, key.id)) > 0) { + EmitDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, key.id, ""); + } + } auto& allocation = memory_allocations_[key]; allocation.total_size = pCallbackData->size; allocation.is_driver = is_driver; @@ -597,6 +656,7 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD if (allocation_iterator != memory_allocations_.end()) { memory_type = is_driver ? allocation_iterator->second.cluster_name : "unbound_memory"; event_size = allocation_iterator->second.total_size; + event_object_handle = allocation_iterator->second.object_handle; RemoveAllocationTracking(key); operation_name = "DESTROY"; } @@ -605,20 +665,20 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD if (operation_name != nullptr) { EmitAllocationTraceEvent({ .operation = operation_name, - .source = is_driver ? "DRIVER" : "DEVICE_MEMORY", - .memory_object_id = key, + .source = source, + .memory_object_id = key.id, .size = event_size, .offset = 0, - .object_handle = pCallbackData->objectHandle, + .object_handle = event_object_handle, .memory_type = memory_type, }); } const bool is_free = pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT; - if (is_free && !is_driver && pCallbackData->objectType == VK_OBJECT_TYPE_DEVICE_MEMORY && - debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, key)) > 0) { - EmitDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, key, ""); + if (is_free && !is_driver && + debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, key.id)) > 0) { + EmitDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, key.id, ""); } } @@ -630,7 +690,8 @@ void DeviceMemoryReport::SetHasMemoryReportCallback(VkDevice device, bool has_ca void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size, uint32_t memory_type_index, VkMemoryPropertyFlags property_flags) { std::lock_guard lock(counter_mutex_); uint64_t handle = reinterpret_cast(memory); - auto& allocation = memory_allocations_[handle]; + const AllocationKey key = AllocationKey::App(handle); + auto& allocation = memory_allocations_[key]; VkMemoryPropertyFlags mem_flags = property_flags; if (mem_flags == 0 && memory_type_index != UINT32_MAX) { @@ -644,14 +705,14 @@ void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory if (has_callback_map_[device]) { if (mem_flags_changed && allocation.total_size > 0) { - UpdateAllocationUnboundCounter(handle); + UpdateAllocationUnboundCounter(key); } } else { allocation.total_size = size; allocation.is_driver = false; allocation.object_type = VK_OBJECT_TYPE_DEVICE_MEMORY; allocation.object_handle = handle; - UpdateAllocationUnboundCounter(handle); + UpdateAllocationUnboundCounter(key); EmitAllocationTraceEvent({ .operation = "CREATE", @@ -670,10 +731,11 @@ void DeviceMemoryReport::OnFreeMemory(VkDevice device, VkDeviceMemory memory) { if (has_callback_map_[device]) return; uint64_t handle = reinterpret_cast(memory); - auto allocation_iterator = memory_allocations_.find(handle); + const AllocationKey key = AllocationKey::App(handle); + auto allocation_iterator = memory_allocations_.find(key); if (allocation_iterator != memory_allocations_.end()) { VkDeviceSize freed_size = allocation_iterator->second.total_size; - RemoveAllocationTracking(handle); + RemoveAllocationTracking(key); EmitAllocationTraceEvent({ .operation = "DESTROY", diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 8e7efba775..be446f1e72 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include #include @@ -23,10 +24,6 @@ #include #include -#ifndef VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT -#define VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT 0x00000001 -#endif - /** * @brief Categorizes buffer usage flags and memory property flags into a cluster category name. * @param usage The Vulkan buffer usage flags. @@ -279,6 +276,30 @@ class DeviceMemoryReport { const char* cluster_name = "unbound_memory"; }; + /** + * @brief Key identifying an entry in memory_allocations_. + * + * Application allocations are keyed by their VkDeviceMemory handle (so vkBind*Memory can find + * them), while driver-internal allocations are keyed by the driver-reported memoryObjectId. + * These are independent namespaces whose values can numerically collide, so is_driver is part + * of the key to keep a driver allocation from clobbering a live application allocation. + */ + struct AllocationKey { + bool is_driver = false; + uint64_t id = 0; + + static AllocationKey App(uint64_t memory_handle) { return {false, memory_handle}; } + static AllocationKey Driver(uint64_t memory_object_id) { return {true, memory_object_id}; } + + bool operator==(const AllocationKey& other) const { return is_driver == other.is_driver && id == other.id; } + }; + + struct AllocationKeyHash { + size_t operator()(const AllocationKey& key) const { + return std::hash{}(key.id) ^ (key.is_driver ? static_cast(0x9e3779b97f4a7c15ull) : 0); + } + }; + /** * @brief Tracks metadata for a virtual resource (buffer or image). */ @@ -305,7 +326,7 @@ class DeviceMemoryReport { * @brief Updates unbound memory category counter for a physical memory slab. * Unbound memory is allocated device memory not currently bound to any active resource (e.g. buffer or image). */ - void UpdateAllocationUnboundCounter(uint64_t memory_handle); + void UpdateAllocationUnboundCounter(const AllocationKey& key); /** * @brief Removes a sub-allocation of a resource from a physical memory slab if bound. @@ -315,7 +336,19 @@ class DeviceMemoryReport { /** * @brief Removes all sub-allocations and tracking for a memory slab being freed. */ - void RemoveAllocationTracking(uint64_t memory_handle); + void RemoveAllocationTracking(const AllocationKey& key); + + /** + * @brief Re-attributes driver allocations associated with a newly created image or buffer. + * + * Driver-internal allocations for a VkImage/VkBuffer are reported during the create call, + * before the layer's post-call hook has recorded the resource's usage. Once the usage is + * known, each matching allocation is moved to its resource cluster. When the cluster changes, + * a DESTROY event for the old cluster followed by a CREATE event for the new cluster is + * emitted so trace consumers can keep per-cluster accounting consistent. + * Called while counter_mutex_ is held. + */ + void ReattributeDriverAllocations(uint64_t object_handle, VkObjectType object_type); /** * @brief Republishes every known object name. Called while counter_mutex_ is held. @@ -368,9 +401,10 @@ class DeviceMemoryReport { std::unordered_map resource_to_memory_map_; /** - * @brief Maps a physical memory handle to its allocation details and sub-allocations. + * @brief Maps an allocation key (application VkDeviceMemory handle or driver memoryObjectId) + * to its allocation details and sub-allocations. */ - std::unordered_map memory_allocations_; + std::unordered_map memory_allocations_; /** * @brief Maps a usage track name to its current total memory usage in bytes. diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index b9385b7fe3..e2d9615dce 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -130,29 +130,34 @@ TEST_F(DeviceMemoryReportTests, EmitEventsAndSubCounters) { VkDeviceMemoryReportCallbackDataEXT cb_data = {}; cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; - cb_data.flags = 0; // Application memory + cb_data.flags = 0; cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; cb_data.memoryObjectId = 0x1000; cb_data.size = 1024 * 1024; - cb_data.objectType = VK_OBJECT_TYPE_IMAGE; + cb_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; // Application memory cb_data.objectHandle = 0x5000; DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.unbound_memory"), 1024u * 1024u); cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; cb_data.size = 512 * 1024; DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.unbound_memory"), 0u); - cb_data.flags = VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT; // Driver memory + // VkDeviceMemoryReportFlagsEXT is reserved and always 0; any objectType other than + // VK_OBJECT_TYPE_DEVICE_MEMORY identifies driver-internal memory. + cb_data.flags = 0; cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; cb_data.objectType = VK_OBJECT_TYPE_BUFFER; cb_data.size = 2048; DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 2048u); // Free the driver allocation cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.unbound_memory"), 0u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 0u); // Test direct allocate/free fallbacks VkDevice dummy_device = reinterpret_cast(0x1234); @@ -439,7 +444,7 @@ TEST_F(DeviceMemoryReportTests, StaticCounterTrackLookup) { EXPECT_EQ(track_storage.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); EXPECT_NE(track_storage.uuid, 0u); - perfetto::CounterTrack track_unbound = GetCounterTrack("vulkan.mem.driver.usage.unbound_memory"); + perfetto::CounterTrack track_unbound = GetCounterTrack("vulkan.mem.driver.usage.driver"); EXPECT_EQ(track_unbound.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); EXPECT_NE(track_unbound.uuid, 0u); @@ -469,7 +474,7 @@ TEST_F(DeviceMemoryReportTests, CounterTracksAreScopedToProcessTrack) { "vulkan.mem.app.usage.color_render_target", "vulkan.mem.app.usage.unbound_memory", "vulkan.mem.driver.usage.static_texture", - "vulkan.mem.driver.usage.unbound_memory", + "vulkan.mem.driver.usage.driver", "vulkan.mem.app.usage.brand_new_category", }; @@ -523,10 +528,10 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { DeviceMemoryReport::Get().OnCreateImage(shared_handle, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); DeviceMemoryReport::Get().OnRecordResourceSize(shared_handle, 4096); - // Case 1: Driver allocation where objectHandle is the virtual resource handle + // Case 1: Driver allocation where objectHandle is the virtual resource handle (flags is always 0 per spec) VkDeviceMemoryReportCallbackDataEXT driver_callback_data = {}; driver_callback_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; - driver_callback_data.flags = VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT; // Driver allocation + driver_callback_data.flags = 0; driver_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; driver_callback_data.memoryObjectId = 0x8001; driver_callback_data.size = 4096; @@ -536,7 +541,7 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { // Driver allocation with image handle should be attributed to the color_render_target track EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.color_render_target"), 4096u); - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.unbound_memory"), 0u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 0u); // Clean up driver allocation driver_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; @@ -546,7 +551,7 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { // Case 2: Application allocation where VkDeviceMemory handle happens to have the same integer value as shared_handle VkDeviceMemoryReportCallbackDataEXT application_callback_data = {}; application_callback_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; - application_callback_data.flags = 0; // Application allocation + application_callback_data.flags = 0; // Application allocation (VK_OBJECT_TYPE_DEVICE_MEMORY) application_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; application_callback_data.memoryObjectId = 0x8002; application_callback_data.size = 8192; @@ -569,7 +574,7 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { uint64_t buffer_handle = 0xF002; VkDeviceMemoryReportCallbackDataEXT buffer_callback_data = {}; buffer_callback_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; - buffer_callback_data.flags = VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT; + buffer_callback_data.flags = 0; buffer_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; buffer_callback_data.memoryObjectId = 0x8003; buffer_callback_data.size = 4096; @@ -577,20 +582,175 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { buffer_callback_data.objectHandle = buffer_handle; DeviceMemoryReport::MemoryReportCallback(&buffer_callback_data, nullptr); - // Prior to OnCreateBuffer, the driver allocation is counted under unbound_memory - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.unbound_memory"), 4096u); + // Prior to OnCreateBuffer, the driver allocation is counted under driver + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 4096u); EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.geometry_mesh"), 0u); // When the buffer is created, the allocation is re-attributed to the geometry_mesh cluster DeviceMemoryReport::Get().OnCreateBuffer(buffer_handle, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 4096); - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.unbound_memory"), 0u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 0u); EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.geometry_mesh"), 4096u); + auto reattributed_buf_alloc = DeviceMemoryReportTestPeer::FindDriverAllocation(0x8003); + ASSERT_TRUE(reattributed_buf_alloc.has_value()); + EXPECT_STREQ(reattributed_buf_alloc->cluster_name, "geometry_mesh"); // Clean up buffer_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; DeviceMemoryReport::MemoryReportCallback(&buffer_callback_data, nullptr); EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.geometry_mesh"), 0u); DeviceMemoryReport::Get().OnDestroyObject(buffer_handle, VK_OBJECT_TYPE_BUFFER); + + // Case 4: Driver allocation arrives before OnCreateImage (tests re-attribution) + uint64_t image_handle = 0xF003; + VkDeviceMemoryReportCallbackDataEXT image_callback_data = {}; + image_callback_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + image_callback_data.flags = 0; + image_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + image_callback_data.memoryObjectId = 0x8004; + image_callback_data.size = 16384; + image_callback_data.objectType = VK_OBJECT_TYPE_IMAGE; + image_callback_data.objectHandle = image_handle; + DeviceMemoryReport::MemoryReportCallback(&image_callback_data, nullptr); + + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 16384u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.static_texture"), 0u); + + DeviceMemoryReport::Get().OnCreateImage(image_handle, VK_IMAGE_USAGE_SAMPLED_BIT); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 0u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.static_texture"), 16384u); + auto reattributed_img_alloc = DeviceMemoryReportTestPeer::FindDriverAllocation(0x8004); + ASSERT_TRUE(reattributed_img_alloc.has_value()); + EXPECT_STREQ(reattributed_img_alloc->cluster_name, "static_texture"); + + image_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&image_callback_data, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.static_texture"), 0u); + DeviceMemoryReport::Get().OnDestroyObject(image_handle, VK_OBJECT_TYPE_IMAGE); + + // Case 5: Duplicate ALLOCATE/IMPORT on an existing active key replaces tracking cleanly without leaking bytes + VkDeviceMemoryReportCallbackDataEXT dup_cb = {}; + dup_cb.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + dup_cb.flags = 0; + dup_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + dup_cb.memoryObjectId = 0x8005; + dup_cb.size = 4096; + dup_cb.objectType = VK_OBJECT_TYPE_COMMAND_POOL; + dup_cb.objectHandle = 0xF004; + DeviceMemoryReport::MemoryReportCallback(&dup_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 4096u); + + dup_cb.size = 2048; + DeviceMemoryReport::MemoryReportCallback(&dup_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 2048u); + + dup_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&dup_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 0u); + + // Case 6: Driver-internal allocation with VK_OBJECT_TYPE_UNKNOWN and objectHandle = 0 + VkDeviceMemoryReportCallbackDataEXT unknown_cb = {}; + unknown_cb.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + unknown_cb.flags = 0; + unknown_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + unknown_cb.memoryObjectId = 0x8006; + unknown_cb.size = 1024; + unknown_cb.objectType = VK_OBJECT_TYPE_UNKNOWN; + unknown_cb.objectHandle = 0; + DeviceMemoryReport::MemoryReportCallback(&unknown_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 1024u); + auto unknown_alloc = DeviceMemoryReportTestPeer::FindDriverAllocation(0x8006); + ASSERT_TRUE(unknown_alloc.has_value()); + EXPECT_TRUE(unknown_alloc->is_driver); + EXPECT_EQ(unknown_alloc->object_type, VK_OBJECT_TYPE_UNKNOWN); + EXPECT_STREQ(unknown_alloc->cluster_name, "driver"); + + unknown_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&unknown_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 0u); + + // Case 7: Driver VK_OBJECT_TYPE_IMAGE allocation whose objectHandle collides with an existing VkBuffer + // before OnCreateImage must not inherit the buffer's usage cluster. + uint64_t cross_type_handle = 0xF005; + DeviceMemoryReport::Get().OnCreateBuffer(cross_type_handle, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 4096); + VkDeviceMemoryReportCallbackDataEXT cross_type_cb = {}; + cross_type_cb.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + cross_type_cb.flags = 0; + cross_type_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + cross_type_cb.memoryObjectId = 0x8007; + cross_type_cb.size = 2048; + cross_type_cb.objectType = VK_OBJECT_TYPE_IMAGE; + cross_type_cb.objectHandle = cross_type_handle; + DeviceMemoryReport::MemoryReportCallback(&cross_type_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 2048u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.geometry_mesh"), 0u); + + cross_type_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&cross_type_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 0u); + DeviceMemoryReport::Get().OnDestroyObject(cross_type_handle, VK_OBJECT_TYPE_BUFFER); +} + +TEST_F(DeviceMemoryReportTests, DriverMemoryObjectIdCollidingWithAppHandle) { + TEST_DESCRIPTION("Test that a driver memoryObjectId numerically equal to a live VkDeviceMemory handle does not disturb it"); + + InitializeDeviceMemoryReportPerfetto(); + + // VkDeviceMemory handles and driver memoryObjectIds are independent namespaces. + const uint64_t colliding_value = 0xF101; + const uint64_t buffer_handle = 0xF102; + + VkDeviceMemoryReportCallbackDataEXT app_cb = {}; + app_cb.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + app_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + app_cb.memoryObjectId = 0x9101; + app_cb.size = 8192; + app_cb.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + app_cb.objectHandle = colliding_value; + DeviceMemoryReport::MemoryReportCallback(&app_cb, nullptr); + + DeviceMemoryReport::Get().OnCreateBuffer(buffer_handle, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 4096); + DeviceMemoryReport::Get().OnBindBufferMemory(buffer_handle, colliding_value, 0); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.geometry_mesh"), 4096u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.unbound_memory"), 4096u); + + // Driver allocation whose memoryObjectId equals the live VkDeviceMemory handle. + VkDeviceMemoryReportCallbackDataEXT driver_cb = {}; + driver_cb.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + driver_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + driver_cb.memoryObjectId = colliding_value; + driver_cb.size = 1024; + driver_cb.objectType = VK_OBJECT_TYPE_COMMAND_POOL; + driver_cb.objectHandle = 0xF103; + DeviceMemoryReport::MemoryReportCallback(&driver_cb, nullptr); + + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 1024u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.geometry_mesh"), 4096u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.unbound_memory"), 4096u); + auto app_alloc = DeviceMemoryReportTestPeer::FindAllocation(colliding_value); + ASSERT_TRUE(app_alloc.has_value()); + EXPECT_FALSE(app_alloc->is_driver); + EXPECT_EQ(app_alloc->total_size, 8192u); + EXPECT_EQ(app_alloc->sub_allocations.size(), 1u); + auto driver_alloc = DeviceMemoryReportTestPeer::FindDriverAllocation(colliding_value); + ASSERT_TRUE(driver_alloc.has_value()); + EXPECT_TRUE(driver_alloc->is_driver); + EXPECT_EQ(driver_alloc->total_size, 1024u); + EXPECT_STREQ(driver_alloc->cluster_name, "driver"); + + // Freeing the driver allocation leaves the application allocation intact. + driver_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&driver_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.driver"), 0u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.geometry_mesh"), 4096u); + EXPECT_TRUE(DeviceMemoryReportTestPeer::FindAllocation(colliding_value).has_value()); + EXPECT_FALSE(DeviceMemoryReportTestPeer::FindDriverAllocation(colliding_value).has_value()); + + // Freeing the application allocation releases its bindings and unbound bytes. + app_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&app_cb, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.geometry_mesh"), 0u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.unbound_memory"), 0u); + DeviceMemoryReport::Get().OnDestroyObject(buffer_handle, VK_OBJECT_TYPE_BUFFER); } TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { @@ -760,6 +920,28 @@ TEST_F(DeviceMemoryReportTests, DebugObjectNamesDestroyedOnObjectDestroy) { free_cb.objectHandle = callback_mem_handle; DeviceMemoryReport::MemoryReportCallback(&free_cb, nullptr); EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, callback_mem_handle), ""); + + // Retiring a stale active VkDeviceMemory allocation via a duplicate ALLOCATE_EXT (without an + // intervening FREE_EXT) also clears its debug name so the replacement does not inherit it. + const uint64_t recycled_mem_handle = 0xE205; + VkDeviceMemoryReportCallbackDataEXT alloc_cb = {}; + alloc_cb.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + alloc_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + alloc_cb.memoryObjectId = 0x9002; + alloc_cb.size = 2048; + alloc_cb.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + alloc_cb.objectHandle = recycled_mem_handle; + DeviceMemoryReport::MemoryReportCallback(&alloc_cb, nullptr); + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, recycled_mem_handle, "old_heap_name"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, recycled_mem_handle), "old_heap_name"); + + alloc_cb.memoryObjectId = 0x9003; + alloc_cb.size = 4096; + DeviceMemoryReport::MemoryReportCallback(&alloc_cb, nullptr); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, recycled_mem_handle), ""); + + alloc_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&alloc_cb, nullptr); } TEST_F(DeviceMemoryReportTests, DebugObjectNameClearOnlyAffectsItsOwnType) { @@ -784,11 +966,11 @@ TEST_F(DeviceMemoryReportTests, DebugObjectNameClearOnlyAffectsItsOwnType) { // erase the live VkDeviceMemory's debug name. VkDeviceMemoryReportCallbackDataEXT driver_cb = {}; driver_cb.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; - driver_cb.flags = VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT; + driver_cb.flags = 0; driver_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; driver_cb.memoryObjectId = shared_handle; driver_cb.size = 4096; - driver_cb.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + driver_cb.objectType = VK_OBJECT_TYPE_COMMAND_BUFFER; driver_cb.objectHandle = 0x9999; DeviceMemoryReport::MemoryReportCallback(&driver_cb, nullptr); driver_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; diff --git a/layersvt/test/test_devicememoryreport_peer.h b/layersvt/test/test_devicememoryreport_peer.h index 425abadb70..f2d6fd9885 100644 --- a/layersvt/test/test_devicememoryreport_peer.h +++ b/layersvt/test/test_devicememoryreport_peer.h @@ -27,14 +27,14 @@ class DeviceMemoryReportTestPeer { public: + // Looks up an application allocation by its VkDeviceMemory handle. 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; + return FindAllocationByKey(DeviceMemoryReport::AllocationKey::App(memory_handle)); + } + + // Looks up a driver-internal allocation by its memoryObjectId. + static std::optional FindDriverAllocation(uint64_t memory_object_id) { + return FindAllocationByKey(DeviceMemoryReport::AllocationKey::Driver(memory_object_id)); } static std::optional FindResource(uint64_t resource_handle) { @@ -53,4 +53,15 @@ class DeviceMemoryReportTestPeer { auto it = report.debug_object_names_.find(std::make_pair(object_type, object_handle)); return it != report.debug_object_names_.end() ? it->second : std::string(); } + + private: + static std::optional FindAllocationByKey(const DeviceMemoryReport::AllocationKey& key) { + auto& report = DeviceMemoryReport::Get(); + std::lock_guard lock(report.counter_mutex_); + auto it = report.memory_allocations_.find(key); + if (it == report.memory_allocations_.end()) { + return std::nullopt; + } + return it->second; + } };