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/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.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 2f797f42cb..c18c850d9f 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) { @@ -402,6 +419,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 +446,77 @@ 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 (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) { + // 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& [key, name] : debug_object_names_) { + const auto& [object_type, object_handle] = key; + EmitDebugObjectName(object_type, object_handle, name); + } } void DeviceMemoryReport::DumpCurrentCountersAndAllocations() { @@ -476,6 +561,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) { @@ -504,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) { @@ -523,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) { @@ -552,6 +649,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); @@ -570,20 +668,24 @@ 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()) return; - - VkDeviceSize freed_size = allocation_iterator->second.total_size; - RemoveAllocationTracking(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", - }); + 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, ""); + } } diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 43890fe195..8e7efba775 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 @@ -52,12 +54,18 @@ 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: * - 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 +213,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); + + /** + * @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 +317,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 +376,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..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,6 +31,10 @@ 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(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); return nullptr; } @@ -56,6 +60,29 @@ 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); @@ -79,6 +106,12 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(V } if (instance == nullptr) { + if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) { + return reinterpret_cast(vkEnumerateDeviceExtensionProperties); + } + if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) { + return reinterpret_cast(vkEnumerateDeviceLayerProperties); + } return nullptr; } @@ -87,18 +120,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; @@ -113,10 +150,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 62ab0c7517..fe2c96bc7f 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 @@ -31,17 +32,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])) @@ -134,16 +140,28 @@ 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; - uint32_t ext_count = 0; + bool supports_debug_marker = false; 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(extension.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) { + supports_debug_marker = true; + } + if (supports_memory_report && supports_debug_marker) { break; } } @@ -151,14 +169,22 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c } } - // 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 = {}; + VkDeviceDeviceMemoryReportCreateInfoEXT memory_report_create_info = {}; if (supports_memory_report) { bool already_enabled = false; for (const char* name : enabled_extensions) { @@ -170,17 +196,18 @@ 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; - 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; } - 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,7 +254,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) { - return util_GetExtensionProperties(0, nullptr, pPropertyCount, 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}, + }; + + return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); } EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, @@ -255,6 +295,69 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, 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[] = { + {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 && strcmp(pLayerName, LAYER_NAME) == 0) { + return util_GetExtensionProperties(ARRAY_SIZE(layer_device_extensions), layer_device_extensions, + 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 merge device extensions when pLayerName == nullptr because the Android Vulkan + // loader does not expose device extensions from implicit layers (b/143293104). + uint32_t downstream_count = 0; + VkResult result = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties( + physicalDevice, nullptr, &downstream_count, nullptr); + 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); + 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()))); + } + + 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; + } + } + if (!duplicate) { + merged_extensions.push_back(layer_extension); + } + } + + return util_GetExtensionProperties(static_cast(merged_extensions.size()), + merged_extensions.data(), 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) { @@ -400,7 +503,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 +532,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 +604,124 @@ 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) { + 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->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) { + assert(pNameInfo != nullptr); + auto* table = device_dispatch_table(device); + VkResult result = (table->DebugMarkerSetObjectNameEXT != nullptr) + ? table->DebugMarkerSetObjectNameEXT(device, pNameInfo) + : VK_SUCCESS; + if (result == VK_SUCCESS) { + DeviceMemoryReport::Get().SetDebugObjectName(pNameInfo->objectType, pNameInfo->object, + pNameInfo->pObjectName); + } + 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) { + 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; +} + +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" diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index a27f174821..b9385b7fe3 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 @@ -30,6 +31,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 +87,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 +104,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(); @@ -254,18 +279,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 +346,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 +354,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 +563,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,108 +590,9 @@ 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); -} - -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); + 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; - } -}; - TEST_F(DeviceMemoryReportTests, MemoryReportSnapshotDump) { TEST_DESCRIPTION("Test DumpCurrentCountersAndAllocations state dump and instant event emissions when a trace session begins"); @@ -715,8 +641,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 +651,177 @@ 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), ""); + + // 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) { + 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), ""); +} diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 200117cfbf..f6ff1f364e 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 @@ -40,6 +41,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); @@ -52,6 +59,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); @@ -92,10 +108,22 @@ 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); + 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); @@ -110,6 +138,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 +178,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 { @@ -151,6 +189,44 @@ class DeviceMemoryReportDispatchTests : public ::testing::Test { } }; +TEST_F(DeviceMemoryReportDispatchTests, ProactiveMemoryRequirementsQuery) { + FakeDevice device; + g_image_requirements_size = 16384; + g_buffer_requirements_size = 2048; + + 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(), &image_info, nullptr, &image), VK_SUCCESS); + EXPECT_EQ(g_image_requirements_queries, 1); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 16384u); + + 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(), &buffer_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 @@ -278,5 +354,145 @@ 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"); +} + +VKAPI_ATTR VkResult VKAPI_CALL StubEnumerateDeviceExtensionPropertiesWithOverlap( + VkPhysicalDevice, const char*, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { + 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_extensions, 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())); } + + FakeInstance(const FakeInstance&) = delete; + FakeInstance& operator=(const FakeInstance&) = delete; + + 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 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(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 properties(3); + uint32_t zero_count = 0; + 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(physical_device, nullptr, &partial_count, properties.data()), VK_INCOMPLETE); + EXPECT_EQ(partial_count, 2u); + + uint32_t full_count = 3; + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &full_count, properties.data()), VK_SUCCESS); + 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 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(); + } +};