diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 68fbd77cdf..95f20a2712 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -210,10 +210,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"; - 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); + 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()) { + track_name = res_it->second.GetCluster(allocation.mem_flags); + } } std::string new_unbound_track = GetUsageTrackName(allocation.is_driver, track_name); @@ -312,13 +316,19 @@ VkDeviceSize DeviceMemoryReport::GetRecordedResourceSize(uint64_t resource_handl return it != resources_.end() ? it->second.size : 0; } +uint64_t DeviceMemoryReport::GetUsageCounterBytes(const std::string& track) { + std::lock_guard lock(counter_mutex_); + auto it = usage_memory_bytes_.find(track); + return it != usage_memory_bytes_.end() ? it->second : 0; +} + 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.object_handle == image_handle) { + if (pair.second.is_driver && pair.second.object_type == VK_OBJECT_TYPE_IMAGE && pair.second.object_handle == image_handle) { UpdateAllocationUnboundCounter(pair.first); } } @@ -331,7 +341,7 @@ void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFla res.buffer_usage = usage; res.size = size; for (const auto& pair : memory_allocations_) { - if (pair.second.object_handle == buffer_handle) { + if (pair.second.is_driver && pair.second.object_type == VK_OBJECT_TYPE_BUFFER && pair.second.object_handle == buffer_handle) { UpdateAllocationUnboundCounter(pair.first); } } @@ -355,6 +365,7 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD 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.object_type = pCallbackData->objectType; allocation.object_handle = pCallbackData->objectHandle; UpdateAllocationUnboundCounter(key); } else if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 22514516aa..8a30d438a6 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -170,6 +170,13 @@ class DeviceMemoryReport { */ VkDeviceSize GetRecordedResourceSize(uint64_t resource_handle); + /** + * @brief Retrieves the current total memory bytes recorded on a usage track (for testing). + * @param track The full usage track name (e.g. "vulkan.mem.app.usage.unbound_memory"). + * @return Number of bytes currently recorded on the track, or 0 if the track has no counter. + */ + uint64_t GetUsageCounterBytes(const std::string& track); + /** * @brief Tracks creation of a Vulkan image and its usage flags. * @param image_handle The 64-bit handle of the Vulkan image. @@ -209,6 +216,7 @@ class DeviceMemoryReport { VkDeviceSize total_size = 0; VkDeviceSize applied_unbound_bytes = 0; bool is_driver = false; + VkObjectType object_type = VK_OBJECT_TYPE_UNKNOWN; VkMemoryPropertyFlags mem_flags = 0; std::vector sub_allocations; std::string unbound_usage_track; diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 6fc6c639b9..5efa12044c 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -123,6 +123,11 @@ TEST_F(DeviceMemoryReportTests, EmitEventsAndSubCounters) { cb_data.size = 2048; DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + // 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); + // Test direct allocate/free fallbacks VkDevice dummy_device = reinterpret_cast(0x1234); VkDeviceMemory dummy_memory = reinterpret_cast(0x5678); @@ -422,6 +427,87 @@ TEST_F(DeviceMemoryReportTests, StaticCounterTrackLookup) { EXPECT_EQ(dynamic_track_again.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); } +TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { + TEST_DESCRIPTION("Test that application unbound memory is not misclassified when object handle matches an existing resource handle"); + + InitializeDeviceMemoryReportPerfetto(); + + uint64_t shared_handle = 0xF001; + + // Register a virtual image resource with handle shared_handle + 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 + 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.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + driver_callback_data.memoryObjectId = 0x8001; + driver_callback_data.size = 4096; + driver_callback_data.objectType = VK_OBJECT_TYPE_IMAGE; + driver_callback_data.objectHandle = shared_handle; + DeviceMemoryReport::MemoryReportCallback(&driver_callback_data, nullptr); + + // 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); + + // Clean up driver allocation + driver_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&driver_callback_data, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.driver.usage.color_render_target"), 0u); + + // 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.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + application_callback_data.memoryObjectId = 0x8002; + application_callback_data.size = 8192; + application_callback_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + application_callback_data.objectHandle = shared_handle; + DeviceMemoryReport::MemoryReportCallback(&application_callback_data, nullptr); + + // Application allocation should be classified as unbound_memory, NOT color_render_target + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.unbound_memory"), 8192u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.color_render_target"), 0u); + + // Clean up application allocation and resource + application_callback_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&application_callback_data, nullptr); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.unbound_memory"), 0u); + + DeviceMemoryReport::Get().OnDestroyObject(shared_handle); + + // Case 3: Driver allocation arrives before OnCreateBuffer (tests re-attribution) + 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.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + buffer_callback_data.memoryObjectId = 0x8003; + buffer_callback_data.size = 4096; + buffer_callback_data.objectType = VK_OBJECT_TYPE_BUFFER; + 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); + 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.geometry_mesh"), 4096u); + + // 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); +} + TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { TEST_DESCRIPTION("Test that the layer proactively queries memory requirements when creating images and buffers");