From 2dfaa2a537384a6137c69eb3dec60595c3f90a13 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Mon, 21 Sep 2026 11:10:50 +0000 Subject: [PATCH 01/22] device_memory_report: publish object names from the memory layer The memory view labels allocations with the debug names an application gives its objects. Those names were only available from VK_LAYER_GOOGLE_DebugMarker, so Sherlock had to load that layer whenever the memory report was enabled, paying for full API event tracing to get a handful of names. Intercept vkSetDebugUtilsObjectNameEXT and vkDebugMarkerSetObjectNameEXT here instead and publish the names as VulkanObjectName instant events under the VulkanDeviceMemoryReport category, alongside the events they annotate. Names are replayed from DumpCurrentCountersAndAllocations so sessions that attach after the application named its objects still see them, and repeated naming of an unchanged name is dropped because applications re-apply names routinely. Only buffers, images and device memory are tracked, since the memory view cannot attribute memory to anything else. Applications that need every object named, to label GPU render stages for example, are still served by the debug marker layer. The layer does not advertise VK_EXT_debug_marker the way the debug marker layer does, so it stays passive: vkGetDeviceProcAddr only hands out these intercepts when the layer below implements them, and an extension never appears available because this layer is loaded. BUG=b/559839199 --- .../device_memory_report.cpp | 98 ++++++++++- .../device_memory_report.h | 59 ++++++- ...ice_memory_report_handwritten_dispatch.cpp | 2 + ...vice_memory_report_handwritten_functions.h | 33 +++- layersvt/test/test_devicememoryreport.cpp | 165 +++++++++++++++++- 5 files changed, 349 insertions(+), 8 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 2f797f42cb..4d4f747d53 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -260,6 +260,23 @@ void EmitAllocationTraceEvent(const AllocationTraceEvent& event) { "memory_type", event.memory_type); } +/** + * @brief Writes one object name to the trace, for consumers to join onto memory events by handle. + * + * Emits a "VulkanObjectName" instant event under the "VulkanDeviceMemoryReport" category with + * three debug annotations: + * - "object_type" (int32_t): the VkObjectType value of the named object. + * - "object_handle" (uint64_t): the raw Vulkan handle of the object. + * - "object_name" (string): the debug name assigned to the object, or an empty string when a + * previously assigned name has been cleared. + */ +void EmitDebugObjectName(VkObjectType object_type, uint64_t object_handle, std::string_view name) { + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanObjectName", + "object_type", static_cast(object_type), + "object_handle", object_handle, + "object_name", name); +} + } // namespace void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { @@ -352,6 +369,9 @@ void DeviceMemoryReport::RemoveAllocationTracking(uint64_t memory_handle) { if (allocation_it == memory_allocations_.end()) return; auto& allocation = allocation_it->second; + if (!allocation.is_driver && allocation.object_type == VK_OBJECT_TYPE_DEVICE_MEMORY) { + debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle)); + } for (const auto& suballocation : allocation.sub_allocations) { SubtractCounterBytes(suballocation.usage_track, suballocation.size); auto resource_iterator = resources_.find(suballocation.resource_handle); @@ -402,6 +422,7 @@ void DeviceMemoryReport::Reset() { resource_to_memory_map_.clear(); memory_allocations_.clear(); usage_memory_bytes_.clear(); + debug_object_names_.clear(); } void DeviceMemoryReport::OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage) { std::lock_guard lock(counter_mutex_); @@ -428,10 +449,76 @@ void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFla } } -void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle) { +void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle, VkObjectType object_type) { std::lock_guard lock(counter_mutex_); RemoveResourceBinding(object_handle); resources_.erase(object_handle); + if (object_type != VK_OBJECT_TYPE_UNKNOWN) { + debug_object_names_.erase(std::make_pair(object_type, object_handle)); + } +} + +void DeviceMemoryReport::SetDebugObjectName(VkObjectType object_type, uint64_t object_handle, const char* name) { + // Other types would be trace volume that nothing reads; VK_LAYER_GOOGLE_DebugMarker names them + // all for consumers that need it. + switch (object_type) { + case VK_OBJECT_TYPE_BUFFER: + case VK_OBJECT_TYPE_IMAGE: + case VK_OBJECT_TYPE_DEVICE_MEMORY: + break; + default: + return; + } + + // A null or empty name clears the name, and the clear still has to be published. A view, not a + // string: the overwhelmingly common call is an application re-applying a name that has not + // changed, some do it every frame, and that path must not allocate. + const std::string_view new_name = name ? std::string_view(name) : std::string_view(); + const auto key = std::make_pair(object_type, object_handle); + + std::lock_guard lock(counter_mutex_); + + auto existing = debug_object_names_.find(key); + if (existing == debug_object_names_.end()) { + // Nothing stored and nothing to store: no state change to publish. + if (new_name.empty()) return; + debug_object_names_.emplace(key, new_name); + } else if (existing->second == new_name) { + // Applications re-apply the same name routinely, so only a change is worth publishing. + return; + } else if (new_name.empty()) { + debug_object_names_.erase(existing); + } else { + // Assigning in place reuses the capacity already allocated for the previous name. + existing->second.assign(new_name); + } + EmitDebugObjectName(object_type, object_handle, new_name); +} + +void DeviceMemoryReport::SetDebugObjectName(VkDebugReportObjectTypeEXT object_type, uint64_t object_handle, const char* name) { + // VK_EXT_debug_marker names objects with the legacy VkDebugReportObjectTypeEXT enum. Only the + // types this layer attributes memory to are mapped. + VkObjectType mapped_type = VK_OBJECT_TYPE_UNKNOWN; + switch (object_type) { + case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT: + mapped_type = VK_OBJECT_TYPE_BUFFER; + break; + case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT: + mapped_type = VK_OBJECT_TYPE_IMAGE; + break; + case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT: + mapped_type = VK_OBJECT_TYPE_DEVICE_MEMORY; + break; + default: + return; + } + SetDebugObjectName(mapped_type, object_handle, name); +} + +void DeviceMemoryReport::EmitAllDebugObjectNames() { + for (const auto& entry : debug_object_names_) { + EmitDebugObjectName(entry.first.first, entry.first.second, entry.second); + } } void DeviceMemoryReport::DumpCurrentCountersAndAllocations() { @@ -476,6 +563,11 @@ void DeviceMemoryReport::DumpCurrentCountersAndAllocations() { }); } } + + // Names are replayed after the allocations for the same reason they are replayed at all: a + // session that attaches mid-run never saw the naming calls, and a name with no allocation to + // attach to is meaningless. + EmitAllDebugObjectNames(); } void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData) { @@ -552,6 +644,7 @@ void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory } else { allocation.total_size = size; allocation.is_driver = false; + allocation.object_type = VK_OBJECT_TYPE_DEVICE_MEMORY; allocation.object_handle = handle; UpdateAllocationUnboundCounter(handle); @@ -569,8 +662,9 @@ void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory 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); + debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, handle)); + if (has_callback_map_[device]) return; auto allocation_iterator = memory_allocations_.find(handle); if (allocation_iterator == memory_allocations_.end()) return; diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 43890fe195..a3a6a31100 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -16,9 +16,11 @@ #pragma once #include +#include #include #include #include +#include #include #ifndef VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT @@ -58,6 +60,8 @@ const char* GetImageCluster(VkImageUsageFlags usage, VkMemoryPropertyFlags memFl * Memory usage counters are reported to Perfetto under: * - Driver vs Application allocations (e.g., vulkan.mem.driver.* vs vulkan.mem.app.*) * - Usages (vulkan.mem.*.usage.) + * - Instant events under the "VulkanDeviceMemoryReport" category ("VulkanMemoryAllocation" and + * "VulkanObjectName") * * This class is a singleton and provides thread-safe access to its state. */ @@ -205,10 +209,43 @@ class DeviceMemoryReport { void DumpCurrentCountersAndAllocations(); /** - * @brief Handles destruction of a Vulkan object, cleaning up tracked usage state. + * @brief Handles destruction of a Vulkan object, cleaning up tracked usage state and any + * recorded debug name. + * + * Handles are only unique within an object type, so @p object_type is required to clear the + * destroyed object's name without dropping the name of an unrelated live object that shares + * the handle value. + * * @param object_handle The 64-bit handle of the destroyed Vulkan object. + * @param object_type The type of the destroyed object, as a VkObjectType. */ - void OnDestroyObject(uint64_t object_handle); + void OnDestroyObject(uint64_t object_handle, VkObjectType object_type = VK_OBJECT_TYPE_UNKNOWN); + + /** + * @brief Records the debug name an application gave to a Vulkan object and publishes it. + * + * Names arrive through VK_EXT_debug_utils or VK_EXT_debug_marker, typically well after the + * object was created, so they are published as their own event stream rather than attached to + * the memory events. Only the object types this layer attributes memory to are kept. + * + * @param object_type The type of the object, as a VkObjectType. + * @param object_handle The 64-bit handle of the object. + * @param name The name given by the application. A null or empty name clears the stored name. + */ + void SetDebugObjectName(VkObjectType object_type, uint64_t object_handle, const char* name); + + /** + * @brief Records the debug name an application gave to a Vulkan object via the legacy + * VK_EXT_debug_marker extension and publishes it. + * + * Maps the legacy VkDebugReportObjectTypeEXT enum to VkObjectType for the object types this + * layer attributes memory to, and ignores all other types. + * + * @param object_type The type of the object, as a VkDebugReportObjectTypeEXT. + * @param object_handle The 64-bit handle of the object. + * @param name The name given by the application. A null or empty name clears the stored name. + */ + void SetDebugObjectName(VkDebugReportObjectTypeEXT object_type, uint64_t object_handle, const char* name); private: friend class DeviceMemoryReportTestPeer; @@ -276,6 +313,11 @@ class DeviceMemoryReport { */ void RemoveAllocationTracking(uint64_t memory_handle); + /** + * @brief Republishes every known object name. Called while counter_mutex_ is held. + */ + void EmitAllDebugObjectNames(); + /** * @brief Increments trace counter for a memory track. */ @@ -330,4 +372,17 @@ class DeviceMemoryReport { * @brief Maps a usage track name to its current total memory usage in bytes. */ std::unordered_map usage_memory_bytes_; + + /** + * @brief Maps a pair of (object_type, object_handle) to the name the application gave the object. + * + * Handles are only unique within an object type, hence the pair key. Like resources_ and + * memory_allocations_, keys are not scoped by VkDevice, and entries persist across + * OnDestroyDevice until the object itself is destroyed or Reset() is called. Objects not + * destroyed through vkDestroyImage, vkDestroyBuffer, or vkFreeMemory (such as presentable + * VkImages owned by a VkSwapchainKHR) retain their entries until Reset() or until a recycled + * handle is renamed. Guarded by counter_mutex_. std::map is used instead of unordered_map for + * pair key support and deterministic replay order. + */ + std::map, std::string> debug_object_names_; }; diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp index aa7ca0b2fc..64c84783f4 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -61,6 +61,8 @@ static PFN_vkVoidFunction devmemreport_known_device_extension_functions(const ch if (strcmp(pName, "vkBindImageMemory2KHR") == 0) return reinterpret_cast(vkBindImageMemory2KHR); if (strcmp(pName, "vkGetImageMemoryRequirements2KHR") == 0) return reinterpret_cast(vkGetImageMemoryRequirements2KHR); if (strcmp(pName, "vkGetBufferMemoryRequirements2KHR") == 0) return reinterpret_cast(vkGetBufferMemoryRequirements2KHR); + if (strcmp(pName, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectNameEXT); + if (strcmp(pName, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectNameEXT); return nullptr; } diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 62ab0c7517..db969d0546 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -400,7 +400,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage(VkDevice device, const VkImageCreat // Intercept image destruction to clean up tracked handle state. VKAPI_ATTR void VKAPI_CALL vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator) { if (image != VK_NULL_HANDLE) { - DeviceMemoryReport::Get().OnDestroyObject(reinterpret_cast(image)); + DeviceMemoryReport::Get().OnDestroyObject(reinterpret_cast(image), VK_OBJECT_TYPE_IMAGE); } PFN_vkDestroyImage fpDestroyImage = (PFN_vkDestroyImage)device_dispatch_table(device)->DestroyImage; if (fpDestroyImage != NULL) { @@ -429,7 +429,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(VkDevice device, const VkBufferCre // Intercept buffer destruction to clean up tracked handle state. VKAPI_ATTR void VKAPI_CALL vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator) { if (buffer != VK_NULL_HANDLE) { - DeviceMemoryReport::Get().OnDestroyObject(reinterpret_cast(buffer)); + DeviceMemoryReport::Get().OnDestroyObject(reinterpret_cast(buffer), VK_OBJECT_TYPE_BUFFER); } PFN_vkDestroyBuffer fpDestroyBuffer = (PFN_vkDestroyBuffer)device_dispatch_table(device)->DestroyBuffer; if (fpDestroyBuffer != NULL) { @@ -501,4 +501,33 @@ VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements2KHR(VkDevice device, co } } +// Object naming from VK_EXT_debug_utils. +VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { + if (pNameInfo == nullptr) return VK_SUCCESS; + auto* table = device_dispatch_table(device); + // Naming is informational, so a driver that does not implement it is not an error. + VkResult result = (table != nullptr && table->SetDebugUtilsObjectNameEXT != nullptr) + ? table->SetDebugUtilsObjectNameEXT(device, pNameInfo) + : VK_SUCCESS; + if (result == VK_SUCCESS) { + DeviceMemoryReport::Get().SetDebugObjectName(pNameInfo->objectType, pNameInfo->objectHandle, + pNameInfo->pObjectName); + } + return result; +} + +// Object naming from VK_EXT_debug_marker, the predecessor of VK_EXT_debug_utils. +VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo) { + if (pNameInfo == nullptr) return VK_SUCCESS; + auto* table = device_dispatch_table(device); + VkResult result = (table != nullptr && table->DebugMarkerSetObjectNameEXT != nullptr) + ? table->DebugMarkerSetObjectNameEXT(device, pNameInfo) + : VK_SUCCESS; + if (result == VK_SUCCESS) { + DeviceMemoryReport::Get().SetDebugObjectName(pNameInfo->objectType, pNameInfo->object, + pNameInfo->pObjectName); + } + return result; +} + } // extern "C" diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index a27f174821..1ba849929a 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -665,6 +665,13 @@ class DeviceMemoryReportTestPeer { } return it->second; } + + static std::string GetDebugObjectName(VkObjectType object_type, uint64_t object_handle) { + auto& report = DeviceMemoryReport::Get(); + std::lock_guard lock(report.counter_mutex_); + 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(); + } }; TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { @@ -715,8 +722,8 @@ TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { 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); + DeviceMemoryReport::Get().OnDestroyObject(buffer_handle, VK_OBJECT_TYPE_BUFFER); + DeviceMemoryReport::Get().OnDestroyObject(image_handle, VK_OBJECT_TYPE_IMAGE); // Verify post-destruction state EXPECT_FALSE(DeviceMemoryReportTestPeer::FindAllocation(memory_handle).has_value()); @@ -725,3 +732,157 @@ TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { } +TEST_F(DeviceMemoryReportTests, DebugObjectNames) { + TEST_DESCRIPTION("Test that object names are recorded for the object types the layer attributes memory to"); + + InitializeDeviceMemoryReportPerfetto(); + + const uint64_t buffer_handle = 0xE001; + const uint64_t image_handle = 0xE002; + const uint64_t memory_handle = 0xE003; + + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle, "vertex_buffer"); + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_IMAGE, image_handle, "albedo_texture"); + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle, "scene_heap"); + + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), "vertex_buffer"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, image_handle), "albedo_texture"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle), "scene_heap"); + + // Handles are only unique within an object type, so the same handle can carry a different name + // for a different type. + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_IMAGE, buffer_handle, "shadow_map"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), "vertex_buffer"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, buffer_handle), "shadow_map"); + + // Renaming replaces the stored name. + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle, "index_buffer"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), "index_buffer"); + + // Re-applying the same name preserves state and suppresses a duplicate trace emission (the + // TRACE_EVENT_INSTANT output is consumed by Perfetto and cannot be counted directly here). + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle, "index_buffer"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), "index_buffer"); + + // A null name clears the name rather than recording a placeholder, and clearing an already + // unnamed object is a no-op. + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle, nullptr); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), ""); + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle, ""); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), ""); + + // Object types the memory view cannot attribute memory to are not tracked at all. + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_PIPELINE, 0xE004, "lighting_pipeline"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_PIPELINE, 0xE004), ""); + + // Unnamed objects report no name. + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0xE005), ""); +} + +TEST_F(DeviceMemoryReportTests, DebugObjectNamesSurviveSnapshotDump) { + TEST_DESCRIPTION("Test that a snapshot dump replays object names for sessions that attach late"); + + InitializeDeviceMemoryReportPerfetto(); + + const uint64_t buffer_handle = 0xE101; + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle, "persistent_buffer"); + + DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); + + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), "persistent_buffer"); +} + +TEST_F(DeviceMemoryReportTests, DebugObjectNamesDestroyedOnObjectDestroy) { + TEST_DESCRIPTION("Test that debug names are cleared when objects are destroyed or freed"); + + InitializeDeviceMemoryReportPerfetto(); + + const uint64_t buffer_handle = 0xE201; + const uint64_t image_handle = 0xE202; + const uint64_t memory_handle = 0xE203; + + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle, "temp_buffer"); + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_IMAGE, image_handle, "temp_image"); + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle, "temp_memory"); + + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), "temp_buffer"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, image_handle), "temp_image"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle), "temp_memory"); + + DeviceMemoryReport::Get().OnDestroyObject(buffer_handle, VK_OBJECT_TYPE_BUFFER); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), ""); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, image_handle), "temp_image"); + + DeviceMemoryReport::Get().OnDestroyObject(image_handle, VK_OBJECT_TYPE_IMAGE); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, image_handle), ""); + + VkDevice dummy_device = reinterpret_cast(0xD001); + VkDeviceMemory dummy_memory = reinterpret_cast(memory_handle); + DeviceMemoryReport::Get().OnAllocateMemory(dummy_device, dummy_memory, 1024, 0, 0); + DeviceMemoryReport::Get().OnFreeMemory(dummy_device, dummy_memory); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle), ""); +} + +TEST_F(DeviceMemoryReportTests, DebugObjectNameClearOnlyAffectsItsOwnType) { + TEST_DESCRIPTION("Test that clearing the name of a destroyed object spares a same-numbered object of another type"); + + InitializeDeviceMemoryReportPerfetto(); + + // Handles are only unique within an object type, so a buffer, an image, a device memory + // allocation, and a driver memoryObjectId can legitimately carry the same numeric value. + const uint64_t shared_handle = 0xE301; + + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_BUFFER, shared_handle, "collided_buffer"); + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_IMAGE, shared_handle, "collided_image"); + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, shared_handle, "collided_memory"); + + DeviceMemoryReport::Get().OnDestroyObject(shared_handle, VK_OBJECT_TYPE_BUFFER); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, shared_handle), ""); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, shared_handle), "collided_image"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, shared_handle), "collided_memory"); + + // Freeing a driver-internal allocation whose memoryObjectId matches shared_handle must not + // 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.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.objectHandle = 0x9999; + DeviceMemoryReport::MemoryReportCallback(&driver_cb, nullptr); + driver_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&driver_cb, nullptr); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, shared_handle), "collided_memory"); + + DeviceMemoryReport::Get().OnDestroyObject(shared_handle, VK_OBJECT_TYPE_IMAGE); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, shared_handle), ""); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, shared_handle), "collided_memory"); +} + +TEST_F(DeviceMemoryReportTests, DebugObjectNamesLegacyDebugReportTypes) { + TEST_DESCRIPTION("Test that VK_EXT_debug_marker object types map to their VkObjectType counterparts"); + + InitializeDeviceMemoryReportPerfetto(); + + const uint64_t buffer_handle = 0xE401; + const uint64_t image_handle = 0xE402; + const uint64_t memory_handle = 0xE403; + + DeviceMemoryReport::Get().SetDebugObjectName(VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, buffer_handle, "marker_buffer"); + DeviceMemoryReport::Get().SetDebugObjectName(VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, image_handle, "marker_image"); + DeviceMemoryReport::Get().SetDebugObjectName(VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, memory_handle, "marker_memory"); + + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), "marker_buffer"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, image_handle), "marker_image"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle), "marker_memory"); + + // Clearing through the legacy overload clears the underlying VkObjectType entry. + DeviceMemoryReport::Get().SetDebugObjectName(VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, buffer_handle, nullptr); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, buffer_handle), ""); + + // Untracked legacy object types are ignored. + DeviceMemoryReport::Get().SetDebugObjectName(VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, 0xE404, "marker_pipeline"); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_PIPELINE, 0xE404), ""); +} From f9e551b0b6799a8b99a3504f2e104255d4edf6d7 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 13:17:10 +0000 Subject: [PATCH 02/22] device_memory_report: require VkObjectType in OnDestroyObject --- .../device_memory_report.cpp | 4 +-- .../device_memory_report.h | 2 +- layersvt/test/test_devicememoryreport.cpp | 34 +++++++++---------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 4d4f747d53..6f857c1845 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -453,9 +453,7 @@ void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle, VkObjectType ob std::lock_guard lock(counter_mutex_); RemoveResourceBinding(object_handle); resources_.erase(object_handle); - if (object_type != VK_OBJECT_TYPE_UNKNOWN) { - debug_object_names_.erase(std::make_pair(object_type, object_handle)); - } + debug_object_names_.erase(std::make_pair(object_type, object_handle)); } void DeviceMemoryReport::SetDebugObjectName(VkObjectType object_type, uint64_t object_handle, const char* name) { diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index a3a6a31100..5643559b93 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -219,7 +219,7 @@ class DeviceMemoryReport { * @param object_handle The 64-bit handle of the destroyed Vulkan object. * @param object_type The type of the destroyed object, as a VkObjectType. */ - void OnDestroyObject(uint64_t object_handle, VkObjectType object_type = VK_OBJECT_TYPE_UNKNOWN); + void OnDestroyObject(uint64_t object_handle, VkObjectType object_type); /** * @brief Records the debug name an application gave to a Vulkan object and publishes it. diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 1ba849929a..6f2abaa96b 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -254,18 +254,18 @@ TEST_F(DeviceMemoryReportTests, UsageTypeBreakdown) { DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); // Clean up objects - DeviceMemoryReport::Get().OnDestroyObject(color_img); - DeviceMemoryReport::Get().OnDestroyObject(depth_img); - DeviceMemoryReport::Get().OnDestroyObject(sampled_img); - DeviceMemoryReport::Get().OnDestroyObject(storage_img); - DeviceMemoryReport::Get().OnDestroyObject(transient_img); - - DeviceMemoryReport::Get().OnDestroyObject(vtx_buf); - DeviceMemoryReport::Get().OnDestroyObject(idx_buf); - DeviceMemoryReport::Get().OnDestroyObject(ubo_buf); - DeviceMemoryReport::Get().OnDestroyObject(staging_buf); - DeviceMemoryReport::Get().OnDestroyObject(storage_buf); - DeviceMemoryReport::Get().OnDestroyObject(indirect_buf); + DeviceMemoryReport::Get().OnDestroyObject(color_img, VK_OBJECT_TYPE_IMAGE); + DeviceMemoryReport::Get().OnDestroyObject(depth_img, VK_OBJECT_TYPE_IMAGE); + DeviceMemoryReport::Get().OnDestroyObject(sampled_img, VK_OBJECT_TYPE_IMAGE); + DeviceMemoryReport::Get().OnDestroyObject(storage_img, VK_OBJECT_TYPE_IMAGE); + DeviceMemoryReport::Get().OnDestroyObject(transient_img, VK_OBJECT_TYPE_IMAGE); + + DeviceMemoryReport::Get().OnDestroyObject(vtx_buf, VK_OBJECT_TYPE_BUFFER); + DeviceMemoryReport::Get().OnDestroyObject(idx_buf, VK_OBJECT_TYPE_BUFFER); + DeviceMemoryReport::Get().OnDestroyObject(ubo_buf, VK_OBJECT_TYPE_BUFFER); + DeviceMemoryReport::Get().OnDestroyObject(staging_buf, VK_OBJECT_TYPE_BUFFER); + DeviceMemoryReport::Get().OnDestroyObject(storage_buf, VK_OBJECT_TYPE_BUFFER); + DeviceMemoryReport::Get().OnDestroyObject(indirect_buf, VK_OBJECT_TYPE_BUFFER); EXPECT_TRUE(true); } @@ -321,7 +321,7 @@ TEST_F(DeviceMemoryReportTests, MemoryAliasingAndOverlap) { // - Interval [0, 4000) is removed. Remaining intervals: [2000, 6000) U [8000, 9500). // - Recalculated bound_size = 4,000 + 1,500 = 5,500 B. // - Updated unbound headroom: unbound_memory = 10,000 - 5,500 = 4,500 B. - DeviceMemoryReport::Get().OnDestroyObject(image_a); + DeviceMemoryReport::Get().OnDestroyObject(image_a, VK_OBJECT_TYPE_IMAGE); // Step 6: Free physical memory slab. // - All remaining sub-allocations on this slab are cleaned up and unbound counter is reset. @@ -329,8 +329,8 @@ TEST_F(DeviceMemoryReportTests, MemoryAliasingAndOverlap) { DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); // Step 7: Clean up remaining virtual resource object handles. - DeviceMemoryReport::Get().OnDestroyObject(image_b); - DeviceMemoryReport::Get().OnDestroyObject(buffer_c); + DeviceMemoryReport::Get().OnDestroyObject(image_b, VK_OBJECT_TYPE_IMAGE); + DeviceMemoryReport::Get().OnDestroyObject(buffer_c, VK_OBJECT_TYPE_BUFFER); EXPECT_TRUE(true); } @@ -538,7 +538,7 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { DeviceMemoryReport::MemoryReportCallback(&application_callback_data, nullptr); EXPECT_EQ(DeviceMemoryReport::Get().GetUsageCounterBytes("vulkan.mem.app.usage.unbound_memory"), 0u); - DeviceMemoryReport::Get().OnDestroyObject(shared_handle); + DeviceMemoryReport::Get().OnDestroyObject(shared_handle, VK_OBJECT_TYPE_IMAGE); // Case 3: Driver allocation arrives before OnCreateBuffer (tests re-attribution) uint64_t buffer_handle = 0xF002; @@ -565,7 +565,7 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { 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); + DeviceMemoryReport::Get().OnDestroyObject(buffer_handle, VK_OBJECT_TYPE_BUFFER); } TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { From 984a1c4f7f95e38f9794c84ab38af2a99797c214 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 13:19:10 +0000 Subject: [PATCH 03/22] device_memory_report: remove redundant name cleanup in RemoveAllocationTracking --- layersvt/device_memory_report/device_memory_report.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 6f857c1845..2194e050c2 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -369,9 +369,6 @@ void DeviceMemoryReport::RemoveAllocationTracking(uint64_t memory_handle) { if (allocation_it == memory_allocations_.end()) return; auto& allocation = allocation_it->second; - if (!allocation.is_driver && allocation.object_type == VK_OBJECT_TYPE_DEVICE_MEMORY) { - debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle)); - } for (const auto& suballocation : allocation.sub_allocations) { SubtractCounterBytes(suballocation.usage_track, suballocation.size); auto resource_iterator = resources_.find(suballocation.resource_handle); From 0042524e954d65f4a7ed85cb27cbda0b38b2c6f0 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 13:23:03 +0000 Subject: [PATCH 04/22] device_memory_report: use structured bindings in EmitAllDebugObjectNames --- layersvt/device_memory_report/device_memory_report.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 2194e050c2..25dec2ec72 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -511,8 +511,9 @@ void DeviceMemoryReport::SetDebugObjectName(VkDebugReportObjectTypeEXT object_ty } void DeviceMemoryReport::EmitAllDebugObjectNames() { - for (const auto& entry : debug_object_names_) { - EmitDebugObjectName(entry.first.first, entry.first.second, entry.second); + for (const auto& [key, name] : debug_object_names_) { + const auto& [object_type, object_handle] = key; + EmitDebugObjectName(object_type, object_handle, name); } } From 41130cba039dff353accf531b352c1d7cb677ea8 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 13:23:04 +0000 Subject: [PATCH 05/22] device_memory_report: assert non-null pNameInfo in object naming intercepts --- .../device_memory_report_handwritten_functions.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index db969d0546..de721f92cc 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -503,10 +503,10 @@ VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements2KHR(VkDevice device, co // Object naming from VK_EXT_debug_utils. VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { - if (pNameInfo == nullptr) return VK_SUCCESS; + assert(pNameInfo != nullptr); auto* table = device_dispatch_table(device); // Naming is informational, so a driver that does not implement it is not an error. - VkResult result = (table != nullptr && table->SetDebugUtilsObjectNameEXT != nullptr) + VkResult result = (table->SetDebugUtilsObjectNameEXT != nullptr) ? table->SetDebugUtilsObjectNameEXT(device, pNameInfo) : VK_SUCCESS; if (result == VK_SUCCESS) { @@ -518,9 +518,9 @@ VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(VkDevice device, con // Object naming from VK_EXT_debug_marker, the predecessor of VK_EXT_debug_utils. VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo) { - if (pNameInfo == nullptr) return VK_SUCCESS; + assert(pNameInfo != nullptr); auto* table = device_dispatch_table(device); - VkResult result = (table != nullptr && table->DebugMarkerSetObjectNameEXT != nullptr) + VkResult result = (table->DebugMarkerSetObjectNameEXT != nullptr) ? table->DebugMarkerSetObjectNameEXT(device, pNameInfo) : VK_SUCCESS; if (result == VK_SUCCESS) { From be5c52bba65da037a8806635d58ba09be16bd541 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 13:41:12 +0000 Subject: [PATCH 06/22] device_memory_report: support standalone VK_EXT_debug_utils and VK_EXT_debug_marker --- .../VkLayer_DeviceMemoryReport.json.in | 10 + ...ice_memory_report_handwritten_dispatch.cpp | 58 +++++- ...vice_memory_report_handwritten_functions.h | 189 +++++++++++++++++- 3 files changed, 245 insertions(+), 12 deletions(-) diff --git a/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in b/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in index 9cbbf16db0..d95e30b04a 100644 --- a/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in +++ b/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in @@ -7,10 +7,20 @@ "api_version": "@JSON_VERSION@", "implementation_version": "1", "description": "Vulkan Device Memory Report Layer", + "instance_extensions": [ + { + "name": "VK_EXT_debug_utils", + "spec_version": "2" + } + ], "device_extensions": [ { "name": "VK_EXT_device_memory_report", "spec_version": "2" + }, + { + "name": "VK_EXT_debug_marker", + "spec_version": "4" } ] } diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp index 64c84783f4..f6f1ada48a 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -31,6 +31,12 @@ static PFN_vkVoidFunction devmemreport_known_instance_functions(const char* pNam if (strcmp(pName, "vkDestroyInstance") == 0) return reinterpret_cast(vkDestroyInstance); if (strcmp(pName, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast(vkEnumeratePhysicalDevices); if (strcmp(pName, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast(vkEnumeratePhysicalDeviceGroups); +#ifdef __ANDROID__ + if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); +#endif + if (strcmp(pName, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); + if (strcmp(pName, "vkDestroyDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkDestroyDebugUtilsMessengerEXT); + if (strcmp(pName, "vkSubmitDebugUtilsMessageEXT") == 0) return reinterpret_cast(vkSubmitDebugUtilsMessageEXT); return nullptr; } @@ -39,6 +45,9 @@ static PFN_vkVoidFunction devmemreport_known_core_device_functions(const char* p if (strcmp(pName, "vkCreateDevice") == 0) return reinterpret_cast(vkCreateDevice); if (strcmp(pName, "vkDestroyDevice") == 0) return reinterpret_cast(vkDestroyDevice); if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast(vkEnumerateDeviceLayerProperties); +#ifdef __ANDROID__ + if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); +#endif if (strcmp(pName, "vkAllocateMemory") == 0) return reinterpret_cast(vkAllocateMemory); if (strcmp(pName, "vkFreeMemory") == 0) return reinterpret_cast(vkFreeMemory); if (strcmp(pName, "vkBindBufferMemory") == 0) return reinterpret_cast(vkBindBufferMemory); @@ -56,13 +65,34 @@ static PFN_vkVoidFunction devmemreport_known_core_device_functions(const char* p return nullptr; } +// Device functions for extensions provided directly by this layer (VK_EXT_debug_utils and +// VK_EXT_debug_marker). These are returned unconditionally so the layer functions standalone +// even when neither the underlying driver nor VK_LAYER_GOOGLE_DebugMarker implements them. +static PFN_vkVoidFunction devmemreport_known_layer_device_extension_functions(const char* pName) { + // VK_EXT_debug_utils + if (strcmp(pName, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectNameEXT); + if (strcmp(pName, "vkSetDebugUtilsObjectTagEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectTagEXT); + if (strcmp(pName, "vkCmdBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdBeginDebugUtilsLabelEXT); + if (strcmp(pName, "vkCmdEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdEndDebugUtilsLabelEXT); + if (strcmp(pName, "vkCmdInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdInsertDebugUtilsLabelEXT); + if (strcmp(pName, "vkQueueBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueBeginDebugUtilsLabelEXT); + if (strcmp(pName, "vkQueueEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueEndDebugUtilsLabelEXT); + if (strcmp(pName, "vkQueueInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueInsertDebugUtilsLabelEXT); + + // VK_EXT_debug_marker + if (strcmp(pName, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectNameEXT); + if (strcmp(pName, "vkDebugMarkerSetObjectTagEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectTagEXT); + if (strcmp(pName, "vkCmdDebugMarkerBeginEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerBeginEXT); + if (strcmp(pName, "vkCmdDebugMarkerEndEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerEndEXT); + if (strcmp(pName, "vkCmdDebugMarkerInsertEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerInsertEXT); + return nullptr; +} + static PFN_vkVoidFunction devmemreport_known_device_extension_functions(const char* pName) { if (strcmp(pName, "vkBindBufferMemory2KHR") == 0) return reinterpret_cast(vkBindBufferMemory2KHR); if (strcmp(pName, "vkBindImageMemory2KHR") == 0) return reinterpret_cast(vkBindImageMemory2KHR); if (strcmp(pName, "vkGetImageMemoryRequirements2KHR") == 0) return reinterpret_cast(vkGetImageMemoryRequirements2KHR); if (strcmp(pName, "vkGetBufferMemoryRequirements2KHR") == 0) return reinterpret_cast(vkGetBufferMemoryRequirements2KHR); - if (strcmp(pName, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectNameEXT); - if (strcmp(pName, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectNameEXT); return nullptr; } @@ -81,6 +111,14 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(V } if (instance == nullptr) { +#ifdef __ANDROID__ + if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) { + return reinterpret_cast(vkEnumerateDeviceLayerProperties); + } + if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) { + return reinterpret_cast(vkEnumerateDeviceExtensionProperties); + } +#endif return nullptr; } @@ -89,18 +127,22 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(V return func; } - // Core device functions can be returned directly from GIPA. + // Core device functions and layer-provided debug extension functions can be returned directly from GIPA. func = devmemreport_known_core_device_functions(pName); if (func) { return func; } + func = devmemreport_known_layer_device_extension_functions(pName); + if (func) { + return func; + } auto table = instance_dispatch_table(instance); if (table == NULL || table->GetInstanceProcAddr == NULL) { return nullptr; } - // For extension device commands, verify the underlying chain supports them before returning an interceptor. + // For driver-dependent extension device commands, verify the underlying chain supports them before returning an interceptor. PFN_vkVoidFunction down_func = table->GetInstanceProcAddr(instance, pName); if (down_func == nullptr) { return nullptr; @@ -115,10 +157,16 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(V } EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char* pName) { - if (device == nullptr) { + if (device == nullptr || pName == nullptr) { return nullptr; } + // Extensions provided by this layer itself are always available regardless of underlying driver support. + PFN_vkVoidFunction layer_ext_func = devmemreport_known_layer_device_extension_functions(pName); + if (layer_ext_func) { + return layer_ext_func; + } + if (device_dispatch_table(device)->GetDeviceProcAddr == NULL) { return nullptr; } diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index de721f92cc..7761650b9b 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -134,8 +134,9 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c // Call the function and create the dispatch table chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; - // Check if the underlying driver supports VK_EXT_device_memory_report + // Check if the underlying driver supports VK_EXT_device_memory_report or VK_EXT_debug_marker. bool supports_memory_report = false; + bool supports_debug_marker = false; uint32_t ext_count = 0; if (instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties) { if (instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, nullptr) == VK_SUCCESS && ext_count > 0) { @@ -144,18 +145,27 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c for (const auto& ext : exts) { if (strcmp(ext.extensionName, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { supports_memory_report = true; - break; + } else if (strcmp(ext.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) { + supports_debug_marker = true; } } } } } - // If supported, inject VK_EXT_device_memory_report callback into pNext chain + // Strip layer-advertised device extensions if the underlying driver does not natively support + // them, and inject VK_EXT_device_memory_report callback registration when supported. VkDeviceCreateInfo modified_create_info = *pCreateInfo; std::vector enabled_extensions; for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i) { - enabled_extensions.push_back(pCreateInfo->ppEnabledExtensionNames[i]); + const char* name = pCreateInfo->ppEnabledExtensionNames[i]; + if (!supports_debug_marker && strcmp(name, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) { + continue; + } + if (!supports_memory_report && strcmp(name, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { + continue; + } + enabled_extensions.push_back(name); } VkDeviceDeviceMemoryReportCreateInfoEXT memory_report_ci = {}; @@ -170,8 +180,6 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c if (!already_enabled) { enabled_extensions.push_back(VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME); } - modified_create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); - modified_create_info.ppEnabledExtensionNames = enabled_extensions.data(); memory_report_ci.sType = VK_STRUCTURE_TYPE_DEVICE_DEVICE_MEMORY_REPORT_CREATE_INFO_EXT; memory_report_ci.pfnUserCallback = DeviceMemoryReport::MemoryReportCallback; @@ -180,7 +188,10 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c modified_create_info.pNext = &memory_report_ci; } - VkResult result = fpCreateDevice(physicalDevice, supports_memory_report ? &modified_create_info : pCreateInfo, pAllocator, pDevice); + modified_create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); + modified_create_info.ppEnabledExtensionNames = enabled_extensions.empty() ? nullptr : enabled_extensions.data(); + + VkResult result = fpCreateDevice(physicalDevice, &modified_create_info, pAllocator, pDevice); if (result == VK_SUCCESS) { initDeviceTable(*pDevice, fpGetDeviceProcAddr); DeviceMemoryReport::Get().SetHasMemoryReportCallback(*pDevice, supports_memory_report); @@ -227,6 +238,14 @@ VKAPI_ATTR void VKAPI_CALL vkFreeMemory(VkDevice device, VkDeviceMemory memory, EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { + static const VkExtensionProperties instanceExtensions[] = { + {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, + }; + + if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { + return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); + } + return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); } @@ -255,6 +274,73 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); } +#ifdef __ANDROID__ +EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { + static const VkExtensionProperties deviceExtensions[] = { + {VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, VK_EXT_DEVICE_MEMORY_REPORT_SPEC_VERSION}, + {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, + }; + + if (pLayerName != nullptr) { + if (strcmp(pLayerName, LAYER_NAME) == 0) { + return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); + } + if (physicalDevice != VK_NULL_HANDLE && instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties) { + return instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); + } + return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); + } + + if (physicalDevice == VK_NULL_HANDLE || instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties == nullptr) { + return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); + } + + // Manually append device extensions when pLayerName == nullptr because the Android Vulkan + // loader does not expose device extensions from implicit layers (b/143293104). + if (pProperties == nullptr) { + VkResult res = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, pPropertyCount, nullptr); + if (res == VK_SUCCESS && pPropertyCount != nullptr) { + (*pPropertyCount) += ARRAY_SIZE(deviceExtensions); + } + return res; + } + + if (pPropertyCount != nullptr && *pPropertyCount > 0) { + uint32_t requestedCount = *pPropertyCount; + VkResult res = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, pPropertyCount, pProperties); + if (res == VK_SUCCESS || res == VK_INCOMPLETE) { + uint32_t originalCount = *pPropertyCount; + uint32_t additionalCount = 0; + for (uint32_t i = 0; i < ARRAY_SIZE(deviceExtensions); ++i) { + bool found = false; + for (uint32_t j = 0; j < originalCount; ++j) { + if (strcmp(pProperties[j].extensionName, deviceExtensions[i].extensionName) == 0) { + found = true; + break; + } + } + if (!found) { + if (originalCount + additionalCount < requestedCount) { + pProperties[originalCount + additionalCount] = deviceExtensions[i]; + } else { + res = VK_INCOMPLETE; + } + additionalCount++; + } + } + *pPropertyCount = (originalCount + additionalCount > requestedCount) + ? requestedCount + : (originalCount + additionalCount); + } + return res; + } + return VK_SUCCESS; +} +#endif + // Intercept memory binding to correlate buffer object handles with device memory allocations. VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) { @@ -530,4 +616,93 @@ VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(VkDevice device, co return result; } +// Companion passthroughs for VK_EXT_debug_utils and VK_EXT_debug_marker so the layer can +// advertise and support both extensions standalone without VK_LAYER_GOOGLE_DebugMarker. +VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectTagEXT(VkDevice device, const VkDebugUtilsObjectTagInfoEXT* pTagInfo) { + auto* table = device_dispatch_table(device); + return (table->SetDebugUtilsObjectTagEXT != nullptr) ? table->SetDebugUtilsObjectTagEXT(device, pTagInfo) : VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL vkCmdBeginDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo) { + if (device_dispatch_table(commandBuffer)->CmdBeginDebugUtilsLabelEXT) { + device_dispatch_table(commandBuffer)->CmdBeginDebugUtilsLabelEXT(commandBuffer, pLabelInfo); + } +} + +VKAPI_ATTR void VKAPI_CALL vkCmdEndDebugUtilsLabelEXT(VkCommandBuffer commandBuffer) { + if (device_dispatch_table(commandBuffer)->CmdEndDebugUtilsLabelEXT) { + device_dispatch_table(commandBuffer)->CmdEndDebugUtilsLabelEXT(commandBuffer); + } +} + +VKAPI_ATTR void VKAPI_CALL vkCmdInsertDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo) { + if (device_dispatch_table(commandBuffer)->CmdInsertDebugUtilsLabelEXT) { + device_dispatch_table(commandBuffer)->CmdInsertDebugUtilsLabelEXT(commandBuffer, pLabelInfo); + } +} + +VKAPI_ATTR void VKAPI_CALL vkQueueBeginDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo) { + if (device_dispatch_table(queue)->QueueBeginDebugUtilsLabelEXT) { + device_dispatch_table(queue)->QueueBeginDebugUtilsLabelEXT(queue, pLabelInfo); + } +} + +VKAPI_ATTR void VKAPI_CALL vkQueueEndDebugUtilsLabelEXT(VkQueue queue) { + if (device_dispatch_table(queue)->QueueEndDebugUtilsLabelEXT) { + device_dispatch_table(queue)->QueueEndDebugUtilsLabelEXT(queue); + } +} + +VKAPI_ATTR void VKAPI_CALL vkQueueInsertDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo) { + if (device_dispatch_table(queue)->QueueInsertDebugUtilsLabelEXT) { + device_dispatch_table(queue)->QueueInsertDebugUtilsLabelEXT(queue, pLabelInfo); + } +} + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger) { + if (instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT) { + return instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT(instance, pCreateInfo, pAllocator, pMessenger); + } + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger, + const VkAllocationCallbacks* pAllocator) { + if (instance_dispatch_table(instance)->DestroyDebugUtilsMessengerEXT) { + instance_dispatch_table(instance)->DestroyDebugUtilsMessengerEXT(instance, messenger, pAllocator); + } +} + +VKAPI_ATTR void VKAPI_CALL vkSubmitDebugUtilsMessageEXT(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData) { + if (instance_dispatch_table(instance)->SubmitDebugUtilsMessageEXT) { + instance_dispatch_table(instance)->SubmitDebugUtilsMessageEXT(instance, messageSeverity, messageTypes, pCallbackData); + } +} + +VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectTagEXT(VkDevice device, const VkDebugMarkerObjectTagInfoEXT* pTagInfo) { + auto* table = device_dispatch_table(device); + return (table->DebugMarkerSetObjectTagEXT != nullptr) ? table->DebugMarkerSetObjectTagEXT(device, pTagInfo) : VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerBeginEXT(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo) { + if (device_dispatch_table(commandBuffer)->CmdDebugMarkerBeginEXT) { + device_dispatch_table(commandBuffer)->CmdDebugMarkerBeginEXT(commandBuffer, pMarkerInfo); + } +} + +VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerEndEXT(VkCommandBuffer commandBuffer) { + if (device_dispatch_table(commandBuffer)->CmdDebugMarkerEndEXT) { + device_dispatch_table(commandBuffer)->CmdDebugMarkerEndEXT(commandBuffer); + } +} + +VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerInsertEXT(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo) { + if (device_dispatch_table(commandBuffer)->CmdDebugMarkerInsertEXT) { + device_dispatch_table(commandBuffer)->CmdDebugMarkerInsertEXT(commandBuffer, pMarkerInfo); + } +} + } // extern "C" From 6cb9fc30ff70d1bbdfecb4fb43c077563a521087 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 13:41:12 +0000 Subject: [PATCH 07/22] device_memory_report: add dispatch unit tests for debug object naming --- .../device_memory_report.h | 3 + layersvt/test/CMakeLists.txt | 2 +- layersvt/test/test_devicememoryreport.cpp | 28 ++++- .../test/test_devicememoryreport_dispatch.cpp | 100 ++++++++++++++++++ 4 files changed, 130 insertions(+), 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 5643559b93..b740c91d0b 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -71,6 +71,9 @@ class DeviceMemoryReport { * @brief Returns the singleton instance of the DeviceMemoryReport class. * @return Reference to the DeviceMemoryReport singleton. */ +#if defined(__GNUC__) && __GNUC__ >= 4 + __attribute__((visibility("default"))) +#endif static DeviceMemoryReport& Get(); /** diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index cd635acb9e..79a560f826 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -44,7 +44,7 @@ function(LayerTest NAME) target_compile_definitions(${TEST_NAME} PUBLIC LAYER_BINARY_PATH="$") add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) - set_target_properties(${TEST_NAME} PROPERTIES FOLDER "layers/${NAME}/Test") + set_target_properties(${TEST_NAME} PROPERTIES FOLDER "layers/${NAME}/Test" ENABLE_EXPORTS TRUE) if(WIN32 AND (QT_TARGET_TYPE STREQUAL STATIC_LIBRARY)) set_property(TARGET ${TEST_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 6f2abaa96b..ee305e6d44 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -30,6 +30,17 @@ class DeviceMemoryReportTests : public VkTestFramework { static void SetUpTestSuite() {} static void TearDownTestSuite(){}; + + protected: + void SetUp() override { + VkTestFramework::SetUp(); + DeviceMemoryReport::Get().Reset(); + } + + void TearDown() override { + DeviceMemoryReport::Get().Reset(); + VkTestFramework::TearDown(); + } }; TEST_F(DeviceMemoryReportTests, InitLayer) { @@ -75,7 +86,16 @@ TEST_F(DeviceMemoryReportTests, ExtensionProperties) { // Test instance extension properties advertised by the layer uint32_t inst_ext_count = 0; EXPECT_EQ(vkEnumerateInstanceExtensionProperties(kLayerName, &inst_ext_count, nullptr), VK_SUCCESS); - EXPECT_EQ(inst_ext_count, 0u); + EXPECT_EQ(inst_ext_count, 1u); + std::vector inst_exts(inst_ext_count); + EXPECT_EQ(vkEnumerateInstanceExtensionProperties(kLayerName, &inst_ext_count, inst_exts.data()), VK_SUCCESS); + bool found_debug_utils = false; + for (const auto& ext : inst_exts) { + if (strcmp(ext.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) { + found_debug_utils = true; + } + } + EXPECT_TRUE(found_debug_utils); VkPhysicalDevice phys_dev = VK_NULL_HANDLE; inst_builder.GetPhysicalDevice(&phys_dev); @@ -83,16 +103,20 @@ TEST_F(DeviceMemoryReportTests, ExtensionProperties) { // Test device extension properties advertised by the layer uint32_t dev_ext_count = 0; EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, kLayerName, &dev_ext_count, nullptr), VK_SUCCESS); - EXPECT_GE(dev_ext_count, 1u); + EXPECT_GE(dev_ext_count, 2u); std::vector dev_exts(dev_ext_count); EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, kLayerName, &dev_ext_count, dev_exts.data()), VK_SUCCESS); bool found_mem_report = false; + bool found_debug_marker = false; for (const auto& ext : dev_exts) { if (strcmp(ext.extensionName, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { found_mem_report = true; + } else if (strcmp(ext.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) { + found_debug_marker = true; } } EXPECT_TRUE(found_mem_report); + EXPECT_TRUE(found_debug_marker); } inst_builder.Reset(); diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 200117cfbf..6684f03dd0 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -30,6 +30,16 @@ #include #include +class DeviceMemoryReportTestPeer { + public: + static std::string GetDebugObjectName(VkObjectType object_type, uint64_t object_handle) { + auto& report = DeviceMemoryReport::Get(); + std::lock_guard lock(report.counter_mutex_); + 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(); + } +}; + namespace { // Sizes returned by the stub driver's memory requirement queries. @@ -40,6 +50,12 @@ VkDeviceSize g_image_requirements_size = 0; int g_buffer_requirements_queries = 0; int g_image_requirements_queries = 0; +// Controls whether the stub driver implements VK_EXT_debug_utils / VK_EXT_debug_marker naming. +bool g_stub_supports_debug_utils = false; +bool g_stub_supports_debug_marker = false; +int g_set_debug_utils_name_calls = 0; +int g_debug_marker_set_name_calls = 0; + template HandleType MakeHandle(uintptr_t value) { return reinterpret_cast(value); @@ -92,6 +108,16 @@ VKAPI_ATTR void VKAPI_CALL StubGetImageMemoryRequirements(VkDevice, VkImage, VkM pMemoryRequirements->memoryTypeBits = 1; } +VKAPI_ATTR VkResult VKAPI_CALL StubSetDebugUtilsObjectNameEXT(VkDevice, const VkDebugUtilsObjectNameInfoEXT*) { + ++g_set_debug_utils_name_calls; + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL StubDebugMarkerSetObjectNameEXT(VkDevice, const VkDebugMarkerObjectNameInfoEXT*) { + ++g_debug_marker_set_name_calls; + return VK_SUCCESS; +} + VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL StubGetDeviceProcAddr(VkDevice, const char* pName) { if (pName == nullptr) return nullptr; const std::string name(pName); @@ -110,6 +136,12 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL StubGetDeviceProcAddr(VkDevice, const c } if (name == "vkGetBufferMemoryRequirements") return reinterpret_cast(StubGetBufferMemoryRequirements); if (name == "vkGetImageMemoryRequirements") return reinterpret_cast(StubGetImageMemoryRequirements); + if (g_stub_supports_debug_utils && name == "vkSetDebugUtilsObjectNameEXT") { + return reinterpret_cast(StubSetDebugUtilsObjectNameEXT); + } + if (g_stub_supports_debug_marker && name == "vkDebugMarkerSetObjectNameEXT") { + return reinterpret_cast(StubDebugMarkerSetObjectNameEXT); + } // Everything else is not implemented by the stub driver. return nullptr; @@ -144,6 +176,10 @@ class DeviceMemoryReportDispatchTests : public ::testing::Test { g_image_requirements_size = 0; g_buffer_requirements_queries = 0; g_image_requirements_queries = 0; + g_stub_supports_debug_utils = false; + g_stub_supports_debug_marker = false; + g_set_debug_utils_name_calls = 0; + g_debug_marker_set_name_calls = 0; } void TearDown() override { @@ -278,5 +314,69 @@ TEST_F(DeviceMemoryReportDispatchTests, BindImageMemory2SkipsDisjointImagePlaneB EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); } +TEST_F(DeviceMemoryReportDispatchTests, SetDebugUtilsObjectNameStandaloneAndChained) { + // 1. Standalone: driver/lower layers do not implement vkSetDebugUtilsObjectNameEXT. + g_stub_supports_debug_utils = false; + FakeDevice standalone_device; + VkBuffer buffer = MakeHandle(0xB9001); + + auto pfn_set_name = reinterpret_cast( + vkGetDeviceProcAddr(standalone_device.handle(), "vkSetDebugUtilsObjectNameEXT")); + ASSERT_NE(pfn_set_name, nullptr); + + VkDebugUtilsObjectNameInfoEXT name_info = {}; + name_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; + name_info.objectType = VK_OBJECT_TYPE_BUFFER; + name_info.objectHandle = AsObjectHandle(buffer); + name_info.pObjectName = "standalone_buffer"; + + EXPECT_EQ(pfn_set_name(standalone_device.handle(), &name_info), VK_SUCCESS); + EXPECT_EQ(g_set_debug_utils_name_calls, 0); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, AsObjectHandle(buffer)), + "standalone_buffer"); + + // 2. Chained: driver/lower layer implements vkSetDebugUtilsObjectNameEXT. + g_stub_supports_debug_utils = true; + FakeDevice chained_device; + name_info.pObjectName = "chained_buffer"; + + EXPECT_EQ(pfn_set_name(chained_device.handle(), &name_info), VK_SUCCESS); + EXPECT_EQ(g_set_debug_utils_name_calls, 1); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_BUFFER, AsObjectHandle(buffer)), + "chained_buffer"); +} + +TEST_F(DeviceMemoryReportDispatchTests, DebugMarkerSetObjectNameStandaloneAndChained) { + // 1. Standalone: driver/lower layers do not implement vkDebugMarkerSetObjectNameEXT. + g_stub_supports_debug_marker = false; + FakeDevice standalone_device; + VkImage image = MakeHandle(0xB9002); + + auto pfn_marker_set_name = reinterpret_cast( + vkGetDeviceProcAddr(standalone_device.handle(), "vkDebugMarkerSetObjectNameEXT")); + ASSERT_NE(pfn_marker_set_name, nullptr); + + VkDebugMarkerObjectNameInfoEXT marker_info = {}; + marker_info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT; + marker_info.objectType = VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT; + marker_info.object = AsObjectHandle(image); + marker_info.pObjectName = "standalone_image"; + + EXPECT_EQ(pfn_marker_set_name(standalone_device.handle(), &marker_info), VK_SUCCESS); + EXPECT_EQ(g_debug_marker_set_name_calls, 0); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, AsObjectHandle(image)), + "standalone_image"); + + // 2. Chained: driver/lower layer implements vkDebugMarkerSetObjectNameEXT. + g_stub_supports_debug_marker = true; + FakeDevice chained_device; + marker_info.pObjectName = "chained_image"; + + EXPECT_EQ(pfn_marker_set_name(chained_device.handle(), &marker_info), VK_SUCCESS); + EXPECT_EQ(g_debug_marker_set_name_calls, 1); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_IMAGE, AsObjectHandle(image)), + "chained_image"); +} + } // namespace From d48b857b2811eea1b22616df170f3eada7d40255 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 16:04:14 +0000 Subject: [PATCH 08/22] device_memory_report: document standalone debug extension support in headers --- .../device_memory_report/device_memory_report.h | 6 +++++- .../device_memory_report_handwritten_functions.h | 15 ++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index b740c91d0b..b267cae5c2 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -54,7 +54,11 @@ const char* GetImageCluster(VkImageUsageFlags usage, VkMemoryPropertyFlags memFl * The layer intercepts Vulkan memory allocation and object creation events, using either * VK_EXT_device_memory_report callbacks (when supported by the underlying driver) or falling back * to direct allocation intercepts (vkAllocateMemory/vkFreeMemory). - * Object bindings (vkBindBufferMemory, vkBindImageMemory, vkBindBufferMemory2, vkBindImageMemory2) are tracked to attribute memory allocations to usage categories. + * Object bindings (vkBindBufferMemory, vkBindImageMemory, vkBindBufferMemory2, vkBindImageMemory2) + * are tracked to attribute memory allocations to usage categories. The layer also advertises and + * intercepts VK_EXT_debug_utils and VK_EXT_debug_marker (stripping VK_EXT_debug_marker at device + * creation when unsupported downstream) so debug object names for buffers, images, and device + * memory are published standalone without requiring VK_LAYER_GOOGLE_DebugMarker. * * Track Categories: * Memory usage counters are reported to Perfetto under: diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 7761650b9b..0a5050740d 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -31,17 +31,22 @@ // - vkCreateInstance: Initializes Perfetto tracing and the instance dispatch table. // - vkEnumeratePhysicalDevices / vkEnumeratePhysicalDeviceGroups: Tracks the mapping // between physical devices and instances to support dispatch table lookups. -// - vkCreateDevice / vkDestroyDevice: Initializes/destroys device dispatch tables and -// injects VK_EXT_device_memory_report callback registration into device creation. +// - vkCreateDevice / vkDestroyDevice: Initializes/destroys device dispatch tables, strips +// layer-advertised device extensions (VK_EXT_device_memory_report, VK_EXT_debug_marker) when +// unsupported by the underlying driver, and injects VK_EXT_device_memory_report callback +// registration into device creation. // -// Memory tracking & resource tracking intercepts: +// Memory tracking, resource tracking & debug naming intercepts: // - vkAllocateMemory / vkFreeMemory: Tracks direct allocations/frees as fallbacks. // - vkBindBufferMemory* / vkBindImageMemory*: Associates buffer/image handles with memory allocations. // - vkCreateBuffer / vkDestroyBuffer: Tracks buffer creation, usage flags, and requested sizes. // - vkCreateImage / vkDestroyImage: Tracks image creation and usage flags. // - vkGetBufferMemoryRequirements* / vkGetImageMemoryRequirements*: Tracks resource memory requirements. -// - vkEnumerate*ExtensionProperties / vkEnumerate*LayerProperties: Advertises the layer -// and support for the VK_EXT_device_memory_report extension. +// - vkSetDebugUtilsObjectNameEXT / vkDebugMarkerSetObjectNameEXT (and companion passthroughs): +// Records debug object names for buffers, images, and device memory so the layer can operate +// standalone without VK_LAYER_GOOGLE_DebugMarker. +// - vkEnumerate*ExtensionProperties / vkEnumerate*LayerProperties: Advertises the layer and +// support for VK_EXT_device_memory_report, VK_EXT_debug_utils, and VK_EXT_debug_marker. #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) From 77e2dd94fa2d7192603423fe784fc8da10e283b3 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 16:17:48 +0000 Subject: [PATCH 09/22] device_memory_report: deduplicate and unify vkEnumerateDeviceExtensionProperties --- ...ice_memory_report_handwritten_dispatch.cpp | 10 +- ...vice_memory_report_handwritten_functions.h | 104 +++++++++--------- .../test/test_devicememoryreport_dispatch.cpp | 58 ++++++++++ 3 files changed, 113 insertions(+), 59 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp index f6f1ada48a..645a06ddf0 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -31,9 +31,7 @@ static PFN_vkVoidFunction devmemreport_known_instance_functions(const char* pNam if (strcmp(pName, "vkDestroyInstance") == 0) return reinterpret_cast(vkDestroyInstance); if (strcmp(pName, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast(vkEnumeratePhysicalDevices); if (strcmp(pName, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast(vkEnumeratePhysicalDeviceGroups); -#ifdef __ANDROID__ - if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); -#endif + if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(devmemreport_EnumerateDeviceExtensionProperties); if (strcmp(pName, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); if (strcmp(pName, "vkDestroyDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkDestroyDebugUtilsMessengerEXT); if (strcmp(pName, "vkSubmitDebugUtilsMessageEXT") == 0) return reinterpret_cast(vkSubmitDebugUtilsMessageEXT); @@ -111,13 +109,13 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(V } if (instance == nullptr) { + if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) { + return reinterpret_cast(devmemreport_EnumerateDeviceExtensionProperties); + } #ifdef __ANDROID__ if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) { return reinterpret_cast(vkEnumerateDeviceLayerProperties); } - if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) { - return reinterpret_cast(vkEnumerateDeviceExtensionProperties); - } #endif return nullptr; } diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 0a5050740d..022a434124 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -279,72 +279,70 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); } -#ifdef __ANDROID__ -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, - const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - static const VkExtensionProperties deviceExtensions[] = { +static VKAPI_ATTR VkResult VKAPI_CALL devmemreport_EnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { + assert(pPropertyCount != nullptr); + + static const VkExtensionProperties layer_device_extensions[] = { {VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, VK_EXT_DEVICE_MEMORY_REPORT_SPEC_VERSION}, {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, }; - if (pLayerName != nullptr) { - if (strcmp(pLayerName, LAYER_NAME) == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); - } - if (physicalDevice != VK_NULL_HANDLE && instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties) { - return instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - } - return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); + if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { + return util_GetExtensionProperties(ARRAY_SIZE(layer_device_extensions), layer_device_extensions, + pPropertyCount, pProperties); } - if (physicalDevice == VK_NULL_HANDLE || instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties == nullptr) { - return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); + assert(physicalDevice != VK_NULL_HANDLE); + + // Forward queries for other explicit layers downstream unchanged. + if (pLayerName != nullptr) { + return instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( + physicalDevice, pLayerName, pPropertyCount, pProperties); } - // Manually append device extensions when pLayerName == nullptr because the Android Vulkan + // Manually merge device extensions when pLayerName == nullptr because the Android Vulkan // loader does not expose device extensions from implicit layers (b/143293104). - if (pProperties == nullptr) { - VkResult res = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, pPropertyCount, nullptr); - if (res == VK_SUCCESS && pPropertyCount != nullptr) { - (*pPropertyCount) += ARRAY_SIZE(deviceExtensions); - } - return res; - } - - if (pPropertyCount != nullptr && *pPropertyCount > 0) { - uint32_t requestedCount = *pPropertyCount; - VkResult res = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, pPropertyCount, pProperties); - if (res == VK_SUCCESS || res == VK_INCOMPLETE) { - uint32_t originalCount = *pPropertyCount; - uint32_t additionalCount = 0; - for (uint32_t i = 0; i < ARRAY_SIZE(deviceExtensions); ++i) { - bool found = false; - for (uint32_t j = 0; j < originalCount; ++j) { - if (strcmp(pProperties[j].extensionName, deviceExtensions[i].extensionName) == 0) { - found = true; - break; - } - } - if (!found) { - if (originalCount + additionalCount < requestedCount) { - pProperties[originalCount + additionalCount] = deviceExtensions[i]; - } else { - res = VK_INCOMPLETE; - } - additionalCount++; - } + uint32_t downstream_count = 0; + VkResult result = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( + physicalDevice, nullptr, &downstream_count, nullptr); + if (result != VK_SUCCESS) { + return result; + } + + std::vector downstream_extensions(downstream_count); + result = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( + physicalDevice, nullptr, &downstream_count, downstream_extensions.data()); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + + std::vector merged_extensions = std::move(downstream_extensions); + for (const auto& layer_extension : layer_device_extensions) { + bool duplicate = false; + for (const auto& existing : merged_extensions) { + if (strcmp(layer_extension.extensionName, existing.extensionName) == 0) { + duplicate = true; + break; } - *pPropertyCount = (originalCount + additionalCount > requestedCount) - ? requestedCount - : (originalCount + additionalCount); } - return res; + if (!duplicate) { + merged_extensions.push_back(layer_extension); + } } - return VK_SUCCESS; + + return util_GetExtensionProperties(static_cast(merged_extensions.size()), + merged_extensions.data(), pPropertyCount, pProperties); +} + +EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { + return devmemreport_EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); } -#endif // Intercept memory binding to correlate buffer object handles with device memory allocations. diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 6684f03dd0..ed7017338c 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -378,5 +378,63 @@ TEST_F(DeviceMemoryReportDispatchTests, DebugMarkerSetObjectNameStandaloneAndCha "chained_image"); } +VKAPI_ATTR VkResult VKAPI_CALL StubEnumerateDeviceExtensionPropertiesWithOverlap( + VkPhysicalDevice, const char*, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { + static const VkExtensionProperties driver_exts[] = { + {VK_KHR_SWAPCHAIN_EXTENSION_NAME, 70}, + {VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, VK_EXT_DEVICE_MEMORY_REPORT_SPEC_VERSION}, + }; + return util_GetExtensionProperties(2, driver_exts, pPropertyCount, pProperties); +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL StubGetInstanceProcAddr(VkInstance, const char* pName) { + if (pName == nullptr) return nullptr; + if (std::string(pName) == "vkEnumerateDeviceExtensionProperties") { + return reinterpret_cast(StubEnumerateDeviceExtensionPropertiesWithOverlap); + } + return nullptr; +} + +class FakeInstance { + public: + FakeInstance() { + dispatch_key_ = this; + initInstanceTable(handle(), StubGetInstanceProcAddr); + } + ~FakeInstance() { destroy_instance_dispatch_table(get_dispatch_key(handle())); } + + VkInstance handle() { return reinterpret_cast(this); } + VkPhysicalDevice physical_device() { return reinterpret_cast(this); } + + private: + void* dispatch_key_ = nullptr; +}; + +TEST_F(DeviceMemoryReportDispatchTests, EnumerateDeviceExtensionPropertiesDeduplicatesAndHandlesIncomplete) { + FakeInstance instance; + VkPhysicalDevice phys_dev = instance.physical_device(); + + // Downstream exposes VK_KHR_swapchain + VK_EXT_device_memory_report (2 extensions). + // The layer merges VK_EXT_device_memory_report (duplicate) + VK_EXT_debug_marker (new), + // so both the count query and the fill query must report 3 extensions. + uint32_t count = 0; + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, nullptr, &count, nullptr), VK_SUCCESS); + EXPECT_EQ(count, 3u); + + // Passing non-null pProperties with count == 0 or count < 3 must return VK_INCOMPLETE. + std::vector props(3); + uint32_t zero_count = 0; + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, nullptr, &zero_count, props.data()), VK_INCOMPLETE); + EXPECT_EQ(zero_count, 0u); + + uint32_t partial_count = 2; + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, nullptr, &partial_count, props.data()), VK_INCOMPLETE); + EXPECT_EQ(partial_count, 2u); + + uint32_t full_count = 3; + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, nullptr, &full_count, props.data()), VK_SUCCESS); + EXPECT_EQ(full_count, 3u); +} + } // namespace From 0ccafa684549e660eb92d0649e788e1ae223e4fd Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 16:18:55 +0000 Subject: [PATCH 10/22] device_memory_report: initialize *pMessenger in vkCreateDebugUtilsMessengerEXT stub --- .../device_memory_report_handwritten_functions.h | 4 +++- layersvt/test/test_devicememoryreport_dispatch.cpp | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 022a434124..25d8e4b7ea 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -664,9 +664,11 @@ VKAPI_ATTR void VKAPI_CALL vkQueueInsertDebugUtilsLabelEXT(VkQueue queue, const VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger) { - if (instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT) { + assert(pMessenger != nullptr); + if (instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT != nullptr) { return instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT(instance, pCreateInfo, pAllocator, pMessenger); } + *pMessenger = VK_NULL_HANDLE; return VK_SUCCESS; } diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index ed7017338c..08d133b0ac 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -436,5 +436,19 @@ TEST_F(DeviceMemoryReportDispatchTests, EnumerateDeviceExtensionPropertiesDedupl EXPECT_EQ(full_count, 3u); } +TEST_F(DeviceMemoryReportDispatchTests, CreateDebugUtilsMessengerStubInitializesHandle) { + FakeInstance instance; + auto pfn_create_messenger = reinterpret_cast( + vkGetInstanceProcAddr(instance.handle(), "vkCreateDebugUtilsMessengerEXT")); + ASSERT_NE(pfn_create_messenger, nullptr); + + VkDebugUtilsMessengerCreateInfoEXT create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + VkDebugUtilsMessengerEXT messenger = MakeHandle(0xDEADBEEF); + + EXPECT_EQ(pfn_create_messenger(instance.handle(), &create_info, nullptr, &messenger), VK_SUCCESS); + EXPECT_EQ(messenger, static_cast(VK_NULL_HANDLE)); +} + } // namespace From dccb929861fd04b3382896250e9783271714f98a Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 16:19:59 +0000 Subject: [PATCH 11/22] device_memory_report: remove vkEnumerateDeviceExtensionProperties from core device dispatch --- .../device_memory_report_handwritten_dispatch.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp index 645a06ddf0..3fe22e1228 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -43,9 +43,6 @@ static PFN_vkVoidFunction devmemreport_known_core_device_functions(const char* p if (strcmp(pName, "vkCreateDevice") == 0) return reinterpret_cast(vkCreateDevice); if (strcmp(pName, "vkDestroyDevice") == 0) return reinterpret_cast(vkDestroyDevice); if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast(vkEnumerateDeviceLayerProperties); -#ifdef __ANDROID__ - if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); -#endif if (strcmp(pName, "vkAllocateMemory") == 0) return reinterpret_cast(vkAllocateMemory); if (strcmp(pName, "vkFreeMemory") == 0) return reinterpret_cast(vkFreeMemory); if (strcmp(pName, "vkBindBufferMemory") == 0) return reinterpret_cast(vkBindBufferMemory); From 20da836e685ad522f0be6a41bfaa1f3ee59ae875 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 16:35:33 +0000 Subject: [PATCH 12/22] device_memory_report: keep default hidden visibility on DeviceMemoryReport::Get --- .../device_memory_report.h | 3 - layersvt/test/CMakeLists.txt | 2 +- layersvt/test/test_devicememoryreport.cpp | 76 ------------------- .../test/test_devicememoryreport_dispatch.cpp | 49 ++++++++++++ 4 files changed, 50 insertions(+), 80 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index b267cae5c2..8e7efba775 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -75,9 +75,6 @@ class DeviceMemoryReport { * @brief Returns the singleton instance of the DeviceMemoryReport class. * @return Reference to the DeviceMemoryReport singleton. */ -#if defined(__GNUC__) && __GNUC__ >= 4 - __attribute__((visibility("default"))) -#endif static DeviceMemoryReport& Get(); /** diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index 79a560f826..cd635acb9e 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -44,7 +44,7 @@ function(LayerTest NAME) target_compile_definitions(${TEST_NAME} PUBLIC LAYER_BINARY_PATH="$") add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) - set_target_properties(${TEST_NAME} PROPERTIES FOLDER "layers/${NAME}/Test" ENABLE_EXPORTS TRUE) + set_target_properties(${TEST_NAME} PROPERTIES FOLDER "layers/${NAME}/Test") if(WIN32 AND (QT_TARGET_TYPE STREQUAL STATIC_LIBRARY)) set_property(TARGET ${TEST_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index ee305e6d44..f988fd9ce9 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -592,82 +592,6 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { DeviceMemoryReport::Get().OnDestroyObject(buffer_handle, VK_OBJECT_TYPE_BUFFER); } -TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { - TEST_DESCRIPTION("Test that the layer proactively queries memory requirements when creating images and buffers"); - - layer_test::VulkanInstanceBuilder inst_builder; - VkResult err = inst_builder.Init(kLayerName); - EXPECT_EQ(err, VK_SUCCESS); - - VkPhysicalDevice phys_dev = VK_NULL_HANDLE; - inst_builder.GetPhysicalDevice(&phys_dev); - if (phys_dev == VK_NULL_HANDLE) { - GTEST_SKIP() << "No physical device found, skipping test."; - } - - // Create a logical device - float queue_priority = 1.0f; - VkDeviceQueueCreateInfo queue_info = {}; - queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queue_info.queueFamilyIndex = 0; - queue_info.queueCount = 1; - queue_info.pQueuePriorities = &queue_priority; - - VkDeviceCreateInfo dev_info = {}; - dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - dev_info.queueCreateInfoCount = 1; - dev_info.pQueueCreateInfos = &queue_info; - dev_info.enabledExtensionCount = 0; - - VkDevice device = VK_NULL_HANDLE; - err = vkCreateDevice(phys_dev, &dev_info, nullptr, &device); - if (err != VK_SUCCESS) { - GTEST_SKIP() << "Failed to create logical device, skipping test."; - } - - // Create an image - VkImageCreateInfo img_info = {}; - img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - img_info.imageType = VK_IMAGE_TYPE_2D; - img_info.format = VK_FORMAT_R8G8B8A8_UNORM; - img_info.extent = {64, 64, 1}; - img_info.mipLevels = 1; - img_info.arrayLayers = 1; - img_info.samples = VK_SAMPLE_COUNT_1_BIT; - img_info.tiling = VK_IMAGE_TILING_OPTIMAL; - img_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; - img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - VkImage image = VK_NULL_HANDLE; - err = vkCreateImage(device, &img_info, nullptr, &image); - ASSERT_EQ(err, VK_SUCCESS); - - // The interceptor should have called OnRecordResourceSize. - // Verify that the recorded size is > 0. - VkDeviceSize img_size = DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)); - EXPECT_GT(img_size, 0); - - // Create a buffer - VkBufferCreateInfo buf_info = {}; - buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - buf_info.size = 1024; - buf_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - VkBuffer buffer = VK_NULL_HANDLE; - err = vkCreateBuffer(device, &buf_info, nullptr, &buffer); - ASSERT_EQ(err, VK_SUCCESS); - - // The interceptor should have called OnRecordResourceSize. - VkDeviceSize buf_size = DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)); - EXPECT_GT(buf_size, 0); - - vkDestroyImage(device, image, nullptr); - vkDestroyBuffer(device, buffer, nullptr); - vkDestroyDevice(device, nullptr); -} - class DeviceMemoryReportTestPeer { public: static std::optional FindAllocation(uint64_t memory_handle) { diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 08d133b0ac..354af7ebec 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -68,6 +68,15 @@ uint64_t AsObjectHandle(HandleType handle) { uintptr_t g_next_handle = 0x10000; +VKAPI_ATTR VkResult VKAPI_CALL StubCreateBuffer(VkDevice, const VkBufferCreateInfo*, const VkAllocationCallbacks*, VkBuffer* pBuffer) { + if (pBuffer != nullptr) { + *pBuffer = MakeHandle(++g_next_handle); + } + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL StubDestroyBuffer(VkDevice, VkBuffer, const VkAllocationCallbacks*) {} + VKAPI_ATTR VkResult VKAPI_CALL StubCreateImage(VkDevice, const VkImageCreateInfo*, const VkAllocationCallbacks*, VkImage* pImage) { if (pImage != nullptr) { *pImage = MakeHandle(++g_next_handle); @@ -122,6 +131,8 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL StubGetDeviceProcAddr(VkDevice, const c if (pName == nullptr) return nullptr; const std::string name(pName); + if (name == "vkCreateBuffer") return reinterpret_cast(StubCreateBuffer); + if (name == "vkDestroyBuffer") return reinterpret_cast(StubDestroyBuffer); if (name == "vkCreateImage") return reinterpret_cast(StubCreateImage); if (name == "vkDestroyImage") return reinterpret_cast(StubDestroyImage); if (name == "vkAllocateMemory") return reinterpret_cast(StubAllocateMemory); @@ -187,6 +198,44 @@ class DeviceMemoryReportDispatchTests : public ::testing::Test { } }; +TEST_F(DeviceMemoryReportDispatchTests, ProactiveMemoryRequirementsQuery) { + FakeDevice device; + g_image_requirements_size = 16384; + g_buffer_requirements_size = 2048; + + VkImageCreateInfo img_info = {}; + img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + img_info.imageType = VK_IMAGE_TYPE_2D; + img_info.format = VK_FORMAT_R8G8B8A8_UNORM; + img_info.extent = {64, 64, 1}; + img_info.mipLevels = 1; + img_info.arrayLayers = 1; + img_info.samples = VK_SAMPLE_COUNT_1_BIT; + img_info.tiling = VK_IMAGE_TILING_OPTIMAL; + img_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VkImage image = VK_NULL_HANDLE; + ASSERT_EQ(vkCreateImage(device.handle(), &img_info, nullptr, &image), VK_SUCCESS); + EXPECT_EQ(g_image_requirements_queries, 1); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 16384u); + + VkBufferCreateInfo buf_info = {}; + buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buf_info.size = 1024; + buf_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkBuffer buffer = VK_NULL_HANDLE; + ASSERT_EQ(vkCreateBuffer(device.handle(), &buf_info, nullptr, &buffer), VK_SUCCESS); + EXPECT_EQ(g_buffer_requirements_queries, 1); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer)), 2048u); + + vkDestroyImage(device.handle(), image, nullptr); + vkDestroyBuffer(device.handle(), buffer, nullptr); +} + TEST_F(DeviceMemoryReportDispatchTests, BindBufferMemoryQueriesUnknownResourceSize) { // A buffer whose size was never recorded (for example when the application created it before // the layer was active) must have its size queried from the driver at bind time, otherwise the From ca6d63407fdb93bc1d40ea19628791637177e656 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 16:38:22 +0000 Subject: [PATCH 13/22] device_memory_report: deduplicate DeviceMemoryReportTestPeer into shared header --- layersvt/test/test_devicememoryreport.cpp | 31 +--------- .../test/test_devicememoryreport_dispatch.cpp | 11 +--- layersvt/test/test_devicememoryreport_peer.h | 56 +++++++++++++++++++ 3 files changed, 58 insertions(+), 40 deletions(-) create mode 100644 layersvt/test/test_devicememoryreport_peer.h diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index f988fd9ce9..724d2868de 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -16,6 +16,7 @@ #include "layer_test_helper.h" #include "device_memory_report.h" #include "device_memory_report_perfetto.h" +#include "test_devicememoryreport_peer.h" #include @@ -592,36 +593,6 @@ TEST_F(DeviceMemoryReportTests, DriverVsAppUnboundMemoryAttribution) { DeviceMemoryReport::Get().OnDestroyObject(buffer_handle, VK_OBJECT_TYPE_BUFFER); } -class DeviceMemoryReportTestPeer { -public: - 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 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; - } - - static std::string GetDebugObjectName(VkObjectType object_type, uint64_t object_handle) { - auto& report = DeviceMemoryReport::Get(); - std::lock_guard lock(report.counter_mutex_); - 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(); - } -}; - TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { TEST_DESCRIPTION("Test DumpCurrentCountersAndAllocations state dump and instant event emissions when a trace session begins"); diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 354af7ebec..2933ddcca3 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -21,6 +21,7 @@ // Vulkan implementation. #include "device_memory_report.h" +#include "test_devicememoryreport_peer.h" #include "vk_layer_table.h" #include @@ -30,16 +31,6 @@ #include #include -class DeviceMemoryReportTestPeer { - public: - static std::string GetDebugObjectName(VkObjectType object_type, uint64_t object_handle) { - auto& report = DeviceMemoryReport::Get(); - std::lock_guard lock(report.counter_mutex_); - 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(); - } -}; - namespace { // Sizes returned by the stub driver's memory requirement queries. diff --git a/layersvt/test/test_devicememoryreport_peer.h b/layersvt/test/test_devicememoryreport_peer.h new file mode 100644 index 0000000000..425abadb70 --- /dev/null +++ b/layersvt/test/test_devicememoryreport_peer.h @@ -0,0 +1,56 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "device_memory_report.h" + +#include + +#include +#include +#include +#include +#include + +class DeviceMemoryReportTestPeer { + public: + 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 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; + } + + static std::string GetDebugObjectName(VkObjectType object_type, uint64_t object_handle) { + auto& report = DeviceMemoryReport::Get(); + std::lock_guard lock(report.counter_mutex_); + 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(); + } +}; From 9f394248d119b8f15de497b4caa98d801da2ce94 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 16:39:48 +0000 Subject: [PATCH 14/22] device_memory_report: emit empty-name clear event on object destroy and memory free --- layersvt/device_memory_report/device_memory_report.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 25dec2ec72..5bedacede0 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -450,7 +450,9 @@ void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle, VkObjectType ob std::lock_guard lock(counter_mutex_); RemoveResourceBinding(object_handle); resources_.erase(object_handle); - debug_object_names_.erase(std::make_pair(object_type, object_handle)); + if (debug_object_names_.erase(std::make_pair(object_type, object_handle)) > 0) { + EmitDebugObjectName(object_type, object_handle, ""); + } } void DeviceMemoryReport::SetDebugObjectName(VkObjectType object_type, uint64_t object_handle, const char* name) { @@ -659,7 +661,9 @@ void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory void DeviceMemoryReport::OnFreeMemory(VkDevice device, VkDeviceMemory memory) { std::lock_guard lock(counter_mutex_); uint64_t handle = reinterpret_cast(memory); - debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, handle)); + if (debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, handle)) > 0) { + EmitDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, handle, ""); + } if (has_callback_map_[device]) return; auto allocation_iterator = memory_allocations_.find(handle); if (allocation_iterator == memory_allocations_.end()) return; From a31caf5e16fe3dd11ef31e04d71c341c991c5a0b Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 19:11:02 +0000 Subject: [PATCH 15/22] device_memory_report: retire VkDeviceMemory debug names after DESTROY event --- .../device_memory_report.cpp | 52 +++++++++++-------- layersvt/test/test_devicememoryreport.cpp | 20 +++++++ 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 5bedacede0..c18c850d9f 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -594,12 +594,12 @@ 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()) return; - - memory_type = is_driver ? allocation_iterator->second.cluster_name : "unbound_memory"; - event_size = allocation_iterator->second.total_size; - RemoveAllocationTracking(key); - operation_name = "DESTROY"; + if (allocation_iterator != memory_allocations_.end()) { + memory_type = is_driver ? allocation_iterator->second.cluster_name : "unbound_memory"; + event_size = allocation_iterator->second.total_size; + RemoveAllocationTracking(key); + operation_name = "DESTROY"; + } } if (operation_name != nullptr) { @@ -613,6 +613,13 @@ void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackD .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, ""); + } } void DeviceMemoryReport::SetHasMemoryReportCallback(VkDevice device, bool has_callback) { @@ -660,24 +667,25 @@ void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory 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()) { + VkDeviceSize freed_size = allocation_iterator->second.total_size; + RemoveAllocationTracking(handle); + + EmitAllocationTraceEvent({ + .operation = "DESTROY", + .source = "DEVICE_MEMORY", + .memory_object_id = handle, + .size = freed_size, + .offset = 0, + .object_handle = handle, + .memory_type = "unbound_memory", + }); + } if (debug_object_names_.erase(std::make_pair(VK_OBJECT_TYPE_DEVICE_MEMORY, handle)) > 0) { EmitDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, handle, ""); } - if (has_callback_map_[device]) return; - auto allocation_iterator = memory_allocations_.find(handle); - if (allocation_iterator == memory_allocations_.end()) return; - - VkDeviceSize freed_size = allocation_iterator->second.total_size; - RemoveAllocationTracking(handle); - - EmitAllocationTraceEvent({ - .operation = "DESTROY", - .source = "DEVICE_MEMORY", - .memory_object_id = handle, - .size = freed_size, - .offset = 0, - .object_handle = handle, - .memory_type = "unbound_memory", - }); } diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 724d2868de..b9385b7fe3 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -740,6 +740,26 @@ TEST_F(DeviceMemoryReportTests, DebugObjectNamesDestroyedOnObjectDestroy) { DeviceMemoryReport::Get().OnAllocateMemory(dummy_device, dummy_memory, 1024, 0, 0); DeviceMemoryReport::Get().OnFreeMemory(dummy_device, dummy_memory); EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, memory_handle), ""); + + // On callback-capable devices, OnFreeMemory must keep the debug name intact until the + // VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT callback emits DESTROY. + VkDevice callback_device = reinterpret_cast(0xD002); + DeviceMemoryReport::Get().SetHasMemoryReportCallback(callback_device, true); + const uint64_t callback_mem_handle = 0xE204; + DeviceMemoryReport::Get().SetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, callback_mem_handle, "callback_memory"); + DeviceMemoryReport::Get().OnFreeMemory(callback_device, reinterpret_cast(callback_mem_handle)); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, callback_mem_handle), "callback_memory"); + + VkDeviceMemoryReportCallbackDataEXT free_cb = {}; + free_cb.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + free_cb.flags = 0; + free_cb.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + free_cb.memoryObjectId = 0x9001; + free_cb.size = 1024; + free_cb.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + free_cb.objectHandle = callback_mem_handle; + DeviceMemoryReport::MemoryReportCallback(&free_cb, nullptr); + EXPECT_EQ(DeviceMemoryReportTestPeer::GetDebugObjectName(VK_OBJECT_TYPE_DEVICE_MEMORY, callback_mem_handle), ""); } TEST_F(DeviceMemoryReportTests, DebugObjectNameClearOnlyAffectsItsOwnType) { From df1f87dd260db07bccaa8bb8e5d640c880df45a3 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 19:13:01 +0000 Subject: [PATCH 16/22] device_memory_report: resize downstream_extensions after fill query --- .../device_memory_report_handwritten_functions.h | 1 + 1 file changed, 1 insertion(+) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 25d8e4b7ea..726537d583 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -318,6 +318,7 @@ static VKAPI_ATTR VkResult VKAPI_CALL devmemreport_EnumerateDeviceExtensionPrope if (result != VK_SUCCESS && result != VK_INCOMPLETE) { return result; } + downstream_extensions.resize(downstream_count); std::vector merged_extensions = std::move(downstream_extensions); for (const auto& layer_extension : layer_device_extensions) { From 5000cac8e5be355a336f9411a1917379b972f935 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 19:14:42 +0000 Subject: [PATCH 17/22] device_memory_report: expose vkEnumerateDeviceLayerProperties in NULL-instance GIPA unconditionally --- .../device_memory_report_handwritten_dispatch.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp index 3fe22e1228..34d80a2196 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -109,11 +109,9 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(V if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) { return reinterpret_cast(devmemreport_EnumerateDeviceExtensionProperties); } -#ifdef __ANDROID__ if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) { return reinterpret_cast(vkEnumerateDeviceLayerProperties); } -#endif return nullptr; } From 586864a3bf75237b5c3792ac27594fc70b80ad2a Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 22 Sep 2026 19:16:37 +0000 Subject: [PATCH 18/22] device_memory_report: inline devmemreport_EnumerateDeviceExtensionProperties wrapper --- layersvt/CMakeLists.txt | 3 +++ .../device_memory_report_handwritten_dispatch.cpp | 4 ++-- .../device_memory_report_handwritten_functions.h | 15 ++++----------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 92538d5e3a..2d6663ae95 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -235,6 +235,9 @@ if(BUILD_DEVICEMEMORYREPORT) endif() target_compile_definitions(VkLayer_DeviceMemoryReport PRIVATE VK_ENABLE_BETA_EXTENSIONS) + if(UNIX AND NOT APPLE) + target_link_options(VkLayer_DeviceMemoryReport PRIVATE "-Wl,-Bsymbolic-functions") + endif() endif() if (BUILD_TESTS AND NOT RUN_ON_GITHUB) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp index 34d80a2196..03882f822e 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -31,7 +31,7 @@ static PFN_vkVoidFunction devmemreport_known_instance_functions(const char* pNam if (strcmp(pName, "vkDestroyInstance") == 0) return reinterpret_cast(vkDestroyInstance); if (strcmp(pName, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast(vkEnumeratePhysicalDevices); if (strcmp(pName, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast(vkEnumeratePhysicalDeviceGroups); - if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(devmemreport_EnumerateDeviceExtensionProperties); + if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); if (strcmp(pName, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); if (strcmp(pName, "vkDestroyDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkDestroyDebugUtilsMessengerEXT); if (strcmp(pName, "vkSubmitDebugUtilsMessageEXT") == 0) return reinterpret_cast(vkSubmitDebugUtilsMessageEXT); @@ -107,7 +107,7 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(V if (instance == nullptr) { if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) { - return reinterpret_cast(devmemreport_EnumerateDeviceExtensionProperties); + return reinterpret_cast(vkEnumerateDeviceExtensionProperties); } if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) { return reinterpret_cast(vkEnumerateDeviceLayerProperties); diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 726537d583..e6ad008805 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -279,10 +279,10 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); } -static VKAPI_ATTR VkResult VKAPI_CALL devmemreport_EnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, - const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { +EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { assert(pPropertyCount != nullptr); static const VkExtensionProperties layer_device_extensions[] = { @@ -338,13 +338,6 @@ static VKAPI_ATTR VkResult VKAPI_CALL devmemreport_EnumerateDeviceExtensionPrope merged_extensions.data(), pPropertyCount, pProperties); } -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, - const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - return devmemreport_EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); -} - // Intercept memory binding to correlate buffer object handles with device memory allocations. VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) { From 1a43369bfefb9c494fd72cf2b459720f750990f2 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 23 Sep 2026 14:05:36 +0000 Subject: [PATCH 19/22] device_memory_report: guard and clamp the downstream device extension fill query vkEnumerateDeviceExtensionProperties issued the downstream fill query unconditionally. When the driver reports zero extensions on the count query, std::vector(0).data() is nullptr, so the "fill" call degenerates into a redundant second count query. The subsequent downstream_extensions.resize(downstream_count) was also unclamped: if the driver's count grew between the two calls, resize() appended zero-initialized dummy entries (empty extensionName, zero specVersion) that were then merged into the reported list. Skip the fill query when the count is zero, clamp the resize to the vector's allocated size, cap the allocation at 4096 extensions, and accept VK_INCOMPLETE from the count query as the valid status it is. --- ...device_memory_report_handwritten_functions.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index e6ad008805..e09cc55182 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include #include @@ -308,17 +309,21 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionPropert uint32_t downstream_count = 0; VkResult result = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( physicalDevice, nullptr, &downstream_count, nullptr); - if (result != VK_SUCCESS) { + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { return result; } + constexpr uint32_t max_extensions = 4096; + downstream_count = std::min(downstream_count, max_extensions); std::vector downstream_extensions(downstream_count); - result = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( - physicalDevice, nullptr, &downstream_count, downstream_extensions.data()); - if (result != VK_SUCCESS && result != VK_INCOMPLETE) { - return result; + if (downstream_count > 0) { + result = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( + physicalDevice, nullptr, &downstream_count, downstream_extensions.data()); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + downstream_extensions.resize(std::min(downstream_count, static_cast(downstream_extensions.size()))); } - downstream_extensions.resize(downstream_count); std::vector merged_extensions = std::move(downstream_extensions); for (const auto& layer_extension : layer_device_extensions) { From 864f268a32b9da102e0e0029b8b6bd0d41b3f570 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 23 Sep 2026 14:06:17 +0000 Subject: [PATCH 20/22] device_memory_report: honour LLP_LAYER_15 in vkEnumerateInstanceExtensionProperties The exported vkEnumerateInstanceExtensionProperties answered queries that did not name this layer with VK_SUCCESS and an empty property list. The Khronos loader-layer interface policy LLP_LAYER_15 requires a layer to return VK_ERROR_LAYER_NOT_PRESENT when pLayerName is NULL or names a different layer, so that a caller can tell "this layer does not answer that query" apart from "this layer exposes no extensions". Also assert pPropertyCount before handing it to util_GetExtensionProperties, which dereferences it unconditionally. --- .../device_memory_report_handwritten_functions.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index e09cc55182..1bc367a238 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -244,15 +244,20 @@ VKAPI_ATTR void VKAPI_CALL vkFreeMemory(VkDevice device, VkDeviceMemory memory, EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { + // Khronos loader-layer interface policy LLP_LAYER_15: a layer's exported + // vkEnumerateInstanceExtensionProperties is only valid for queries naming that layer, and must + // report VK_ERROR_LAYER_NOT_PRESENT for anything else. + if (pLayerName == nullptr || strcmp(pLayerName, LAYER_NAME) != 0) { + return VK_ERROR_LAYER_NOT_PRESENT; + } + + assert(pPropertyCount != nullptr); + static const VkExtensionProperties instanceExtensions[] = { {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, }; - if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); - } - - return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); + return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); } EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, From 86c0e945d2520d125974337108ba431a6d750f31 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 23 Sep 2026 14:07:33 +0000 Subject: [PATCH 21/22] device_memory_report: accept VK_INCOMPLETE in the vkCreateDevice extension probe The probe that decides whether the driver natively supports VK_EXT_device_memory_report and VK_EXT_debug_marker only accepted VK_SUCCESS from the downstream fill query. VK_INCOMPLETE is a positive status meaning the requested number of entries were written, so a driver returning it caused the layer to conclude neither extension existed and silently strip them from device creation. Accept VK_INCOMPLETE from both calls, cap the allocation at 4096 extensions, clamp the resize to the allocated size, and stop scanning once both extensions have been seen. Also spell out the abbreviated locals: ext_count, exts, ext and memory_report_ci become extension_count, extensions, extension and memory_report_create_info. --- ...vice_memory_report_handwritten_functions.h | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 1bc367a238..fe2c96bc7f 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -143,17 +143,27 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c // Check if the underlying driver supports VK_EXT_device_memory_report or VK_EXT_debug_marker. bool supports_memory_report = false; bool supports_debug_marker = false; - uint32_t ext_count = 0; if (instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties) { - if (instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, nullptr) == VK_SUCCESS && ext_count > 0) { - std::vector exts(ext_count); - if (instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, exts.data()) == VK_SUCCESS) { - for (const auto& ext : exts) { - if (strcmp(ext.extensionName, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { + uint32_t extension_count = 0; + VkResult enumerate_result = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( + physicalDevice, nullptr, &extension_count, nullptr); + if ((enumerate_result == VK_SUCCESS || enumerate_result == VK_INCOMPLETE) && extension_count > 0) { + constexpr uint32_t max_extensions = 4096; + extension_count = std::min(extension_count, max_extensions); + std::vector extensions(extension_count); + enumerate_result = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( + physicalDevice, nullptr, &extension_count, extensions.data()); + if (enumerate_result == VK_SUCCESS || enumerate_result == VK_INCOMPLETE) { + extensions.resize(std::min(extension_count, static_cast(extensions.size()))); + for (const auto& extension : extensions) { + if (strcmp(extension.extensionName, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { supports_memory_report = true; - } else if (strcmp(ext.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) { + } else if (strcmp(extension.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) { supports_debug_marker = true; } + if (supports_memory_report && supports_debug_marker) { + break; + } } } } @@ -174,7 +184,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c enabled_extensions.push_back(name); } - VkDeviceDeviceMemoryReportCreateInfoEXT memory_report_ci = {}; + VkDeviceDeviceMemoryReportCreateInfoEXT memory_report_create_info = {}; if (supports_memory_report) { bool already_enabled = false; for (const char* name : enabled_extensions) { @@ -187,11 +197,11 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c enabled_extensions.push_back(VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME); } - memory_report_ci.sType = VK_STRUCTURE_TYPE_DEVICE_DEVICE_MEMORY_REPORT_CREATE_INFO_EXT; - memory_report_ci.pfnUserCallback = DeviceMemoryReport::MemoryReportCallback; - memory_report_ci.pUserData = nullptr; - memory_report_ci.pNext = modified_create_info.pNext; - modified_create_info.pNext = &memory_report_ci; + memory_report_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_DEVICE_MEMORY_REPORT_CREATE_INFO_EXT; + memory_report_create_info.pfnUserCallback = DeviceMemoryReport::MemoryReportCallback; + memory_report_create_info.pUserData = nullptr; + memory_report_create_info.pNext = modified_create_info.pNext; + modified_create_info.pNext = &memory_report_create_info; } modified_create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); From a683d06ad753780b7594e71510cd167c0a6eae17 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 23 Sep 2026 14:08:23 +0000 Subject: [PATCH 22/22] device_memory_report: make FakeInstance non-copyable and expand test abbreviations FakeInstance registers an instance dispatch table keyed on its own address and unregisters it in the destructor, so a copy would tear down a table it never owned. FakeDevice already deletes its copy operations; give FakeInstance the same protection. Also spell out the abbreviated test locals: img_info, buf_info, driver_exts, phys_dev and props become image_info, buffer_info, driver_extensions, physical_device and properties. --- .../test/test_devicememoryreport_dispatch.cpp | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 2933ddcca3..f6ff1f364e 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -194,32 +194,32 @@ TEST_F(DeviceMemoryReportDispatchTests, ProactiveMemoryRequirementsQuery) { g_image_requirements_size = 16384; g_buffer_requirements_size = 2048; - VkImageCreateInfo img_info = {}; - img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - img_info.imageType = VK_IMAGE_TYPE_2D; - img_info.format = VK_FORMAT_R8G8B8A8_UNORM; - img_info.extent = {64, 64, 1}; - img_info.mipLevels = 1; - img_info.arrayLayers = 1; - img_info.samples = VK_SAMPLE_COUNT_1_BIT; - img_info.tiling = VK_IMAGE_TILING_OPTIMAL; - img_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; - img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageCreateInfo image_info = {}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = VK_FORMAT_R8G8B8A8_UNORM; + image_info.extent = {64, 64, 1}; + image_info.mipLevels = 1; + image_info.arrayLayers = 1; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkImage image = VK_NULL_HANDLE; - ASSERT_EQ(vkCreateImage(device.handle(), &img_info, nullptr, &image), VK_SUCCESS); + ASSERT_EQ(vkCreateImage(device.handle(), &image_info, nullptr, &image), VK_SUCCESS); EXPECT_EQ(g_image_requirements_queries, 1); EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 16384u); - VkBufferCreateInfo buf_info = {}; - buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - buf_info.size = 1024; - buf_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = 1024; + buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VkBuffer buffer = VK_NULL_HANDLE; - ASSERT_EQ(vkCreateBuffer(device.handle(), &buf_info, nullptr, &buffer), VK_SUCCESS); + ASSERT_EQ(vkCreateBuffer(device.handle(), &buffer_info, nullptr, &buffer), VK_SUCCESS); EXPECT_EQ(g_buffer_requirements_queries, 1); EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer)), 2048u); @@ -420,11 +420,11 @@ TEST_F(DeviceMemoryReportDispatchTests, DebugMarkerSetObjectNameStandaloneAndCha VKAPI_ATTR VkResult VKAPI_CALL StubEnumerateDeviceExtensionPropertiesWithOverlap( VkPhysicalDevice, const char*, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { - static const VkExtensionProperties driver_exts[] = { + static const VkExtensionProperties driver_extensions[] = { {VK_KHR_SWAPCHAIN_EXTENSION_NAME, 70}, {VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, VK_EXT_DEVICE_MEMORY_REPORT_SPEC_VERSION}, }; - return util_GetExtensionProperties(2, driver_exts, pPropertyCount, pProperties); + return util_GetExtensionProperties(2, driver_extensions, pPropertyCount, pProperties); } VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL StubGetInstanceProcAddr(VkInstance, const char* pName) { @@ -441,8 +441,12 @@ class FakeInstance { dispatch_key_ = this; initInstanceTable(handle(), StubGetInstanceProcAddr); } + ~FakeInstance() { destroy_instance_dispatch_table(get_dispatch_key(handle())); } + FakeInstance(const FakeInstance&) = delete; + FakeInstance& operator=(const FakeInstance&) = delete; + VkInstance handle() { return reinterpret_cast(this); } VkPhysicalDevice physical_device() { return reinterpret_cast(this); } @@ -452,27 +456,27 @@ class FakeInstance { TEST_F(DeviceMemoryReportDispatchTests, EnumerateDeviceExtensionPropertiesDeduplicatesAndHandlesIncomplete) { FakeInstance instance; - VkPhysicalDevice phys_dev = instance.physical_device(); + VkPhysicalDevice physical_device = instance.physical_device(); // Downstream exposes VK_KHR_swapchain + VK_EXT_device_memory_report (2 extensions). // The layer merges VK_EXT_device_memory_report (duplicate) + VK_EXT_debug_marker (new), // so both the count query and the fill query must report 3 extensions. uint32_t count = 0; - EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, nullptr, &count, nullptr), VK_SUCCESS); + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &count, nullptr), VK_SUCCESS); EXPECT_EQ(count, 3u); // Passing non-null pProperties with count == 0 or count < 3 must return VK_INCOMPLETE. - std::vector props(3); + std::vector properties(3); uint32_t zero_count = 0; - EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, nullptr, &zero_count, props.data()), VK_INCOMPLETE); + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &zero_count, properties.data()), VK_INCOMPLETE); EXPECT_EQ(zero_count, 0u); uint32_t partial_count = 2; - EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, nullptr, &partial_count, props.data()), VK_INCOMPLETE); + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &partial_count, properties.data()), VK_INCOMPLETE); EXPECT_EQ(partial_count, 2u); uint32_t full_count = 3; - EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, nullptr, &full_count, props.data()), VK_SUCCESS); + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &full_count, properties.data()), VK_SUCCESS); EXPECT_EQ(full_count, 3u); }