From 1ceb2a6fd3e37dbd97df75bef74eaa3f6f3ba9cf Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Mon, 21 Sep 2026 23:31:51 +0000 Subject: [PATCH 1/2] layersvt: extract Vulkan object name bookkeeping into a shared component The DebugMarker layer owned the only record of which Vulkan objects the application has named. The DeviceMemoryReport layer needs the same information to attribute memory to a named buffer or image, and duplicating the bookkeeping would let the two drift apart on what a name is and when it changes. Move the (VkObjectType, handle) -> name store, the legacy VkDebugReportObjectTypeEXT mapping and the session-start replay into layersvt::VulkanObjectNames. No behaviour change for DebugMarker: it emits the same VulkanApiEvent.VkDebugUtilsObjectName packets, at the same points, with the same ordering guarantees. Each layer declares its own perfetto::TrackEvent data source via PERFETTO_DEFINE_CATEGORIES, so perfetto::TrackEvent is a distinct type per layer and the component cannot call it directly. EmitVulkanObjectName is therefore declared here and defined by each layer, which also keeps the component free of any Perfetto dependency and lets the new test_ObjectNames run without Perfetto or the Vulkan loader. The store's mutex is deliberately held across emission so that emit order matches store order, preventing a concurrent replay from overwriting a newer name with an older one. This preserves the previous behaviour. One hardening change: SetObjectName now tolerates a null name_info, which the old code dereferenced unconditionally. --- layersvt/CMakeLists.txt | 3 + layersvt/debug_marker/debug_marker.cpp | 48 ---- layersvt/debug_marker/debug_marker.h | 72 +----- ...andwritten_functions_vk_ext_debug_marker.h | 57 +---- ...handwritten_functions_vk_ext_debug_utils.h | 4 +- .../debug_marker/debug_marker_perfetto.cpp | 14 +- layersvt/object_names/vulkan_object_names.cpp | 129 +++++++++++ layersvt/object_names/vulkan_object_names.h | 182 +++++++++++++++ .../vulkan_object_names_perfetto.h | 66 ++++++ layersvt/test/CMakeLists.txt | 18 +- layersvt/test/test_debugmarker.cpp | 18 +- layersvt/test/test_objectnames.cpp | 211 ++++++++++++++++++ 12 files changed, 646 insertions(+), 176 deletions(-) create mode 100644 layersvt/object_names/vulkan_object_names.cpp create mode 100644 layersvt/object_names/vulkan_object_names.h create mode 100644 layersvt/object_names/vulkan_object_names_perfetto.h create mode 100644 layersvt/test/test_objectnames.cpp diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 92538d5e3a..048bf619ac 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -175,6 +175,9 @@ if(BUILD_DEBUGMARKER) debug_marker/debug_marker.cpp debug_marker/debug_marker_perfetto.h debug_marker/debug_marker_perfetto.cpp + object_names/vulkan_object_names.h + object_names/vulkan_object_names.cpp + object_names/vulkan_object_names_perfetto.h perfetto/perfetto.cc vk_layer_table.cpp vk_layer_table.h diff --git a/layersvt/debug_marker/debug_marker.cpp b/layersvt/debug_marker/debug_marker.cpp index 6f21ff4795..4e1619ea1b 100644 --- a/layersvt/debug_marker/debug_marker.cpp +++ b/layersvt/debug_marker/debug_marker.cpp @@ -14,8 +14,6 @@ */ #include "debug_marker.h" -#include "debug_marker_perfetto.h" -#include "perfetto/perfetto.h" DebugMarker& DebugMarker::Get() { static DebugMarker instance; @@ -34,53 +32,7 @@ VkInstance DebugMarker::GetVkInstance(VkPhysicalDevice phys_dev) { return VK_NULL_HANDLE; } -void DebugMarker::SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name) { - std::lock_guard lock(mutex_); - - std::string name_str = name ? name : "NULL"; - debug_object_names_[std::make_pair(type, handle)] = DebugObjectName(device, type, handle, name_str); - - perfetto::TrackEvent::Trace([device, type, handle, name_str](perfetto::TrackEvent::TraceContext ctx) { - auto packet = ctx.NewTracePacket(); - packet->set_timestamp(perfetto::base::GetBootTimeNs().count()); - auto event = packet->set_vulkan_api_event()->set_vk_debug_utils_object_name(); - event->set_vk_device(device); - event->set_object_type(type); - event->set_object(handle); - event->set_object_name(name_str.c_str()); - }); -} - -void DebugMarker::EmitAllDebugMarkers() { - std::lock_guard lock(mutex_); - for (const auto& entry : debug_object_names_) { - const auto& marker = entry.second; - uint64_t device = marker.vk_device; - int32_t type = marker.object_type; - uint64_t handle = marker.handle; - std::string name_str = marker.name; - - perfetto::TrackEvent::Trace([device, type, handle, name_str](perfetto::TrackEvent::TraceContext ctx) { - auto packet = ctx.NewTracePacket(); - packet->set_timestamp(perfetto::base::GetBootTimeNs().count()); - auto event = packet->set_vulkan_api_event()->set_vk_debug_utils_object_name(); - event->set_vk_device(device); - event->set_object_type(type); - event->set_object(handle); - event->set_object_name(name_str.c_str()); - }); - } -} - void DebugMarker::Clear() { std::lock_guard lock(mutex_); vk_instance_map_.clear(); - debug_object_names_.clear(); -} - -bool DebugMarker::HasDebugObjectName(int32_t type, uint64_t handle, const std::string& name) { - std::lock_guard lock(mutex_); - auto it = debug_object_names_.find(std::make_pair(type, handle)); - if (it == debug_object_names_.end()) return false; - return it->second.name == name; } diff --git a/layersvt/debug_marker/debug_marker.h b/layersvt/debug_marker/debug_marker.h index b705cefbfb..ccf81e5f73 100644 --- a/layersvt/debug_marker/debug_marker.h +++ b/layersvt/debug_marker/debug_marker.h @@ -19,35 +19,14 @@ #include #include -#include -#include - /** - * The DebugMarker class is responsible for storing and managing debug marker - * information associated with Vulkan objects and emitting them to Perfetto traces. - * - * Currently, it primarily supports tracking and emitting object names. - * - * How it works: - * We do not store a history of events for setting object names. Instead, we store - * only the current name for each object (one name per object). This keeps the memory - * footprint small for most applications (proportional to the name size multiplied by - * the number of unique objects). - * - * Perfetto Session Support: - * This solution supports: - * - Starting a Perfetto session before the application starts. - * - Starting a Perfetto session after the application is already running. - * - Running multiple Perfetto sessions during a single application run. - * - * To support late-attach and multiple sessions, when a Perfetto session starts, - * we write all currently known object names to the trace. We retain the names in memory - * because a user might start another Perfetto session later, requiring us to emit - * all object names again. + * The DebugMarker class holds the state the DebugMarker layer needs beyond object names. * - * A potential issue exists if an application constantly creates and destroys - * objects without bound, as we currently do not remove names for destroyed objects. - * Support for removing names on object destruction can be added later if needed. + * Object names themselves are not stored here: the tracking of (VkObjectType, handle) -> name, + * the mapping of the legacy VK_EXT_debug_marker object types and the replay of known names when a + * Perfetto session starts all live in layersvt::VulkanObjectNames + * (see object_names/vulkan_object_names.h), which this layer shares with the DeviceMemoryReport + * layer. * * This class is a singleton and provides thread-safe access to its state. */ @@ -60,30 +39,10 @@ class DebugMarker { static DebugMarker& Get(); /** - * @brief Sets or updates the name associated with a Vulkan object. - * @param device The handle of the Vulkan device that owns the object. - * @param type The type of the Vulkan object (represented as int32_t). - * @param handle The handle of the Vulkan object. - * @param name The name to associate with the object. - */ - void SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name); - - /** - * @brief Emits all stored debug markers to the tracing system. - */ - void EmitAllDebugMarkers(); - - /** - * @brief Clears all stored debug markers and instance mappings. + * @brief Clears all instance mappings. * @note This function is for testing only. */ void Clear(); - - /** - * @brief Checks if a debug name is stored for a given object. - * @note This function is for testing only. - */ - bool HasDebugObjectName(int32_t type, uint64_t handle, const std::string& name); /** * @brief Associates a Vulkan physical device with its corresponding instance. @@ -99,27 +58,10 @@ class DebugMarker { */ VkInstance GetVkInstance(VkPhysicalDevice phys_dev); - private: - struct DebugObjectName { - uint64_t vk_device; - int32_t object_type; - uint64_t handle; - std::string name; - - DebugObjectName() = default; - DebugObjectName(uint64_t dev, int32_t type, uint64_t h, const std::string& n) - : vk_device(dev), object_type(type), handle(h), name(n) {} - }; - std::mutex mutex_; /** * @brief Maps a physical device handle to its corresponding Vulkan instance handle. */ std::unordered_map vk_instance_map_; - /** - * @brief Maps a pair of (object_type, object_handle) to its debug name information. - * We use a pair as the key because handles are not guaranteed to be unique across different object types. - */ - std::map, DebugObjectName> debug_object_names_; }; diff --git a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h index 84db032b13..7a58abfcf5 100644 --- a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h +++ b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h @@ -17,61 +17,16 @@ #include #include "vk_layer_table.h" -#include "debug_marker.h" +#include "object_names/vulkan_object_names.h" // This file contains handwritten functions for the VK_EXT_debug_marker extension. // We only actively implement vkDebugMarkerSetObjectNameEXT to track object names for Perfetto traces. // All other functions are simple passthroughs required to be provided so that the layer // can claim full support for the extension. - -// We need to remap objects using getVkObjectType because VK_EXT_debug_marker uses the legacy -// VkDebugReportObjectTypeEXT enum, while we store everything using the modern VkObjectType -// enum to be consistent with VK_EXT_debug_utils and standard trace packets. -static inline VkObjectType getVkObjectType(VkDebugReportObjectTypeEXT vk_debug_report_object_type) { - switch (vk_debug_report_object_type) { - case VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT: return VK_OBJECT_TYPE_UNKNOWN; - case VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT: return VK_OBJECT_TYPE_INSTANCE; - case VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT: return VK_OBJECT_TYPE_PHYSICAL_DEVICE; - case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT: return VK_OBJECT_TYPE_DEVICE; - case VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT: return VK_OBJECT_TYPE_QUEUE; - case VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT: return VK_OBJECT_TYPE_SEMAPHORE; - case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT: return VK_OBJECT_TYPE_COMMAND_BUFFER; - case VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT: return VK_OBJECT_TYPE_FENCE; - case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT: return VK_OBJECT_TYPE_DEVICE_MEMORY; - case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT: return VK_OBJECT_TYPE_BUFFER; - case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT: return VK_OBJECT_TYPE_IMAGE; - case VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT: return VK_OBJECT_TYPE_EVENT; - case VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT: return VK_OBJECT_TYPE_QUERY_POOL; - case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT: return VK_OBJECT_TYPE_BUFFER_VIEW; - case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT: return VK_OBJECT_TYPE_IMAGE_VIEW; - case VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT: return VK_OBJECT_TYPE_SHADER_MODULE; - case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT: return VK_OBJECT_TYPE_PIPELINE_CACHE; - case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT: return VK_OBJECT_TYPE_PIPELINE_LAYOUT; - case VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT: return VK_OBJECT_TYPE_RENDER_PASS; - case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT: return VK_OBJECT_TYPE_PIPELINE; - case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT; - case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT: return VK_OBJECT_TYPE_SAMPLER; - case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_POOL; - case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_SET; - case VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT: return VK_OBJECT_TYPE_FRAMEBUFFER; - case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT: return VK_OBJECT_TYPE_COMMAND_POOL; - case VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT: return VK_OBJECT_TYPE_SURFACE_KHR; - case VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT: return VK_OBJECT_TYPE_SWAPCHAIN_KHR; - case VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT: return VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT; - case VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT: return VK_OBJECT_TYPE_DISPLAY_KHR; - case VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT: return VK_OBJECT_TYPE_DISPLAY_MODE_KHR; - case VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT: return VK_OBJECT_TYPE_VALIDATION_CACHE_EXT; - case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT: return VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION; - case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE; - case VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV_EXT: return VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV; - case VK_DEBUG_REPORT_OBJECT_TYPE_CU_MODULE_NVX_EXT: return VK_OBJECT_TYPE_CU_MODULE_NVX; - case VK_DEBUG_REPORT_OBJECT_TYPE_CU_FUNCTION_NVX_EXT: return VK_OBJECT_TYPE_CU_FUNCTION_NVX; - case VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR_EXT: return VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR; - case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA_EXT: return VK_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA; - default: - return VK_OBJECT_TYPE_UNKNOWN; - } -} +// +// VK_EXT_debug_marker identifies objects with the legacy VkDebugReportObjectTypeEXT enum, while we +// store everything using the modern VkObjectType enum to be consistent with VK_EXT_debug_utils and +// standard trace packets. layersvt::VulkanObjectNames performs that remapping for us. extern "C" { @@ -98,7 +53,7 @@ VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerInsertEXT(VkCommandBuffer commandBuff // Required for VK_EXT_debug_marker. Tracks object name state. VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo) { - DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)getVkObjectType(pNameInfo->objectType), pNameInfo->object, pNameInfo->pObjectName); + layersvt::VulkanObjectNames::Get().SetObjectName(device, pNameInfo); if (device_dispatch_table(device)->DebugMarkerSetObjectNameEXT) { VkResult result = device_dispatch_table(device)->DebugMarkerSetObjectNameEXT(device, pNameInfo); return result; diff --git a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h index 591a6eba82..2ead481116 100644 --- a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h +++ b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h @@ -17,7 +17,7 @@ #include #include "vk_layer_table.h" -#include "debug_marker.h" +#include "object_names/vulkan_object_names.h" extern "C" { @@ -49,7 +49,7 @@ VKAPI_ATTR void VKAPI_CALL vkCmdInsertDebugUtilsLabelEXT(VkCommandBuffer command // Required for VK_EXT_debug_utils. Tracks object name state. VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { - DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)pNameInfo->objectType, pNameInfo->objectHandle, pNameInfo->pObjectName); + layersvt::VulkanObjectNames::Get().SetObjectName(device, pNameInfo); if (device_dispatch_table(device)->SetDebugUtilsObjectNameEXT) { VkResult result = device_dispatch_table(device)->SetDebugUtilsObjectNameEXT(device, pNameInfo); return result; diff --git a/layersvt/debug_marker/debug_marker_perfetto.cpp b/layersvt/debug_marker/debug_marker_perfetto.cpp index 42ec65775a..91da784d41 100644 --- a/layersvt/debug_marker/debug_marker_perfetto.cpp +++ b/layersvt/debug_marker/debug_marker_perfetto.cpp @@ -14,14 +14,24 @@ */ #include "debug_marker_perfetto.h" -#include "debug_marker.h" +#include "object_names/vulkan_object_names_perfetto.h" PERFETTO_TRACK_EVENT_STATIC_STORAGE(); +namespace layersvt { + +// Binds the shared object name store to this layer's track event data source. +void EmitVulkanObjectName(const VulkanObjectName& object_name) { + WriteVulkanObjectNamePacket(object_name); +} + +} // namespace layersvt + class MarkerSessionObserver : public perfetto::TrackEventSessionObserver { public: void OnStart(const perfetto::DataSourceBase::StartArgs&) override { - DebugMarker::Get().EmitAllDebugMarkers(); + // Replay the names of objects that were named before this session started. + layersvt::VulkanObjectNames::Get().EmitAll(); } }; diff --git a/layersvt/object_names/vulkan_object_names.cpp b/layersvt/object_names/vulkan_object_names.cpp new file mode 100644 index 0000000000..8ebb19c956 --- /dev/null +++ b/layersvt/object_names/vulkan_object_names.cpp @@ -0,0 +1,129 @@ +/* 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. + */ + +#include "vulkan_object_names.h" + +namespace layersvt { + +VkObjectType VkObjectTypeFromDebugReportObjectType(VkDebugReportObjectTypeEXT debug_report_object_type) { + switch (debug_report_object_type) { + case VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT: return VK_OBJECT_TYPE_UNKNOWN; + case VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT: return VK_OBJECT_TYPE_INSTANCE; + case VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT: return VK_OBJECT_TYPE_PHYSICAL_DEVICE; + case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT: return VK_OBJECT_TYPE_DEVICE; + case VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT: return VK_OBJECT_TYPE_QUEUE; + case VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT: return VK_OBJECT_TYPE_SEMAPHORE; + case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT: return VK_OBJECT_TYPE_COMMAND_BUFFER; + case VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT: return VK_OBJECT_TYPE_FENCE; + case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT: return VK_OBJECT_TYPE_DEVICE_MEMORY; + case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT: return VK_OBJECT_TYPE_BUFFER; + case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT: return VK_OBJECT_TYPE_IMAGE; + case VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT: return VK_OBJECT_TYPE_EVENT; + case VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT: return VK_OBJECT_TYPE_QUERY_POOL; + case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT: return VK_OBJECT_TYPE_BUFFER_VIEW; + case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT: return VK_OBJECT_TYPE_IMAGE_VIEW; + case VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT: return VK_OBJECT_TYPE_SHADER_MODULE; + case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT: return VK_OBJECT_TYPE_PIPELINE_CACHE; + case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT: return VK_OBJECT_TYPE_PIPELINE_LAYOUT; + case VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT: return VK_OBJECT_TYPE_RENDER_PASS; + case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT: return VK_OBJECT_TYPE_PIPELINE; + case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT; + case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT: return VK_OBJECT_TYPE_SAMPLER; + case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_POOL; + case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_SET; + case VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT: return VK_OBJECT_TYPE_FRAMEBUFFER; + case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT: return VK_OBJECT_TYPE_COMMAND_POOL; + case VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT: return VK_OBJECT_TYPE_SURFACE_KHR; + case VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT: return VK_OBJECT_TYPE_SWAPCHAIN_KHR; + case VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT: return VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT; + case VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT: return VK_OBJECT_TYPE_DISPLAY_KHR; + case VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT: return VK_OBJECT_TYPE_DISPLAY_MODE_KHR; + case VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT: return VK_OBJECT_TYPE_VALIDATION_CACHE_EXT; + case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT: return VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION; + case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE; + case VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV_EXT: return VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV; + case VK_DEBUG_REPORT_OBJECT_TYPE_CU_MODULE_NVX_EXT: return VK_OBJECT_TYPE_CU_MODULE_NVX; + case VK_DEBUG_REPORT_OBJECT_TYPE_CU_FUNCTION_NVX_EXT: return VK_OBJECT_TYPE_CU_FUNCTION_NVX; + case VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR_EXT: return VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR; + case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA_EXT: return VK_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA; + default: + return VK_OBJECT_TYPE_UNKNOWN; + } +} + +VulkanObjectNames& VulkanObjectNames::Get() { + static VulkanObjectNames instance; + return instance; +} + +void VulkanObjectNames::SetObjectName(uint64_t vk_device, int32_t object_type, uint64_t handle, const char* name) { + std::lock_guard lock(mutex_); + + VulkanObjectName& object_name = object_names_[std::make_pair(object_type, handle)]; + object_name.vk_device = vk_device; + object_name.object_type = object_type; + object_name.handle = handle; + object_name.name = name ? name : "NULL"; + + EmitVulkanObjectName(object_name); +} + +void VulkanObjectNames::SetObjectName(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* name_info) { + if (name_info == nullptr) { + return; + } + SetObjectName((uint64_t)device, (int32_t)name_info->objectType, name_info->objectHandle, name_info->pObjectName); +} + +void VulkanObjectNames::SetObjectName(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* name_info) { + if (name_info == nullptr) { + return; + } + SetObjectName((uint64_t)device, (int32_t)VkObjectTypeFromDebugReportObjectType(name_info->objectType), name_info->object, + name_info->pObjectName); +} + +void VulkanObjectNames::EmitAll() const { + std::lock_guard lock(mutex_); + for (const auto& entry : object_names_) { + EmitVulkanObjectName(entry.second); + } +} + +void VulkanObjectNames::Clear() { + std::lock_guard lock(mutex_); + object_names_.clear(); +} + +bool VulkanObjectNames::HasObjectName(int32_t object_type, uint64_t handle, const std::string& name) const { + std::lock_guard lock(mutex_); + auto it = object_names_.find(std::make_pair(object_type, handle)); + if (it == object_names_.end()) return false; + return it->second.name == name; +} + +std::string VulkanObjectNames::GetObjectName(int32_t object_type, uint64_t handle) const { + std::lock_guard lock(mutex_); + auto it = object_names_.find(std::make_pair(object_type, handle)); + if (it == object_names_.end()) return std::string(); + return it->second.name; +} + +size_t VulkanObjectNames::Size() const { + std::lock_guard lock(mutex_); + return object_names_.size(); +} + +} // namespace layersvt diff --git a/layersvt/object_names/vulkan_object_names.h b/layersvt/object_names/vulkan_object_names.h new file mode 100644 index 0000000000..8ca425eb02 --- /dev/null +++ b/layersvt/object_names/vulkan_object_names.h @@ -0,0 +1,182 @@ +/* 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. + */ + +#ifndef LAYERSVT_OBJECT_NAMES_VULKAN_OBJECT_NAMES_H +#define LAYERSVT_OBJECT_NAMES_VULKAN_OBJECT_NAMES_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace layersvt { + +/** + * @brief Converts a legacy VK_EXT_debug_marker object type to the modern VkObjectType. + * + * VK_EXT_debug_marker names objects using the legacy VkDebugReportObjectTypeEXT enum, whereas + * VK_EXT_debug_utils and the Perfetto trace packets both use VkObjectType. Every layer that + * observes object names therefore has to normalise to VkObjectType, so the mapping lives here. + * + * @param debug_report_object_type The legacy object type. + * @return The equivalent VkObjectType, or VK_OBJECT_TYPE_UNKNOWN if there is no equivalent. + */ +VkObjectType VkObjectTypeFromDebugReportObjectType(VkDebugReportObjectTypeEXT debug_report_object_type); + +/** + * @brief A single (object type, handle) -> name association, together with its owning device. + */ +struct VulkanObjectName { + uint64_t vk_device = 0; /**< Handle of the VkDevice the object belongs to. */ + int32_t object_type = 0; /**< VkObjectType of the named object. */ + uint64_t handle = 0; /**< Handle of the named object. */ + std::string name; /**< Name currently assigned to the object. */ +}; + +/** + * @brief Writes one object name to the tracing backend. + * + * This is the seam between the shared bookkeeping in this component and the Perfetto tracing + * machinery of an individual layer. It is *declared* here and *defined by each layer*, because the + * generated perfetto::TrackEvent data source is layer-private: every layer registers its own with + * its own category set. Resolving the emitter at link time keeps this component free of any + * Perfetto dependency. + * + * The representation is left to the layer as well. DebugMarker writes a + * VulkanApiEvent.VkDebugUtilsObjectName trace packet, for which vulkan_object_names_perfetto.h + * provides a ready-made implementation. + * + * @param object_name The object name to write. Called with the store's mutex held, so + * implementations must not call back into VulkanObjectNames. + */ +void EmitVulkanObjectName(const VulkanObjectName& object_name); + +/** + * VulkanObjectNames is the shared store of Vulkan debug object names, used by every layer that + * observes vkSetDebugUtilsObjectNameEXT / vkDebugMarkerSetObjectNameEXT. + * + * How it works: + * We do not store a history of naming events. Instead we store only the current name for each + * object (one name per object). This keeps the memory footprint small for most applications + * (proportional to the name size multiplied by the number of unique objects). + * + * Perfetto session support: + * This solution supports + * - starting a Perfetto session before the application starts, + * - starting a Perfetto session after the application is already running, + * - running multiple Perfetto sessions during a single application run. + * + * To support late attach and multiple sessions, a layer calls EmitAll() when a Perfetto session + * starts, which replays every currently known object name into the new session. Names are retained + * in memory because a user might start another session later, requiring us to replay them again. + * + * A potential issue exists if an application constantly creates and destroys objects without + * bound, as we currently do not remove names for destroyed objects. Support for removing names on + * object destruction can be added later if needed. + * + * This class is a singleton (one per loaded layer module) and provides thread-safe access to its + * state. + */ +class VulkanObjectNames { + public: + /** + * @brief Returns the singleton instance. + */ + static VulkanObjectNames& Get(); + + /** + * @brief Sets or updates the name associated with a Vulkan object and emits it to the trace. + * @param vk_device The handle of the Vulkan device that owns the object. + * @param object_type The VkObjectType of the object, as int32_t. + * @param handle The handle of the Vulkan object. + * @param name The name to associate with the object. A null name is stored as "NULL". + */ + void SetObjectName(uint64_t vk_device, int32_t object_type, uint64_t handle, const char* name); + + /** + * @brief VK_EXT_debug_utils overload of SetObjectName. Ignores a null @p name_info. + * @param device The device passed to vkSetDebugUtilsObjectNameEXT. + * @param name_info The naming information provided by the application. + */ + void SetObjectName(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* name_info); + + /** + * @brief VK_EXT_debug_marker overload of SetObjectName, mapping the legacy object type. + * Ignores a null @p name_info. + * @param device The device passed to vkDebugMarkerSetObjectNameEXT. + * @param name_info The naming information provided by the application. + */ + void SetObjectName(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* name_info); + + /** + * @brief Replays every stored object name into the trace. + * + * Call this when a Perfetto session starts so that sessions which attach after the objects + * were named still observe their names. + */ + void EmitAll() const; + + /** + * @brief Forgets every stored object name. + * @note This function is for testing only. + */ + void Clear(); + + /** + * @brief Checks whether @p name is the name currently stored for an object. + * @note This function is for testing only. + */ + bool HasObjectName(int32_t object_type, uint64_t handle, const std::string& name) const; + + /** + * @brief Returns the name currently stored for an object, or an empty string if it has none. + */ + std::string GetObjectName(int32_t object_type, uint64_t handle) const; + + /** + * @brief Returns the number of objects that currently have a name. + */ + size_t Size() const; + + private: + VulkanObjectNames() = default; + ~VulkanObjectNames() = default; + VulkanObjectNames(const VulkanObjectNames&) = delete; + VulkanObjectNames& operator=(const VulkanObjectNames&) = delete; + + /** + * @brief Guards object_names_. + * + * Names are emitted while this mutex is held. That is deliberate: it makes the order in which + * packets are written match the order in which names were stored, so a concurrent EmitAll() + * replay can never overwrite a newer name with an older one in the trace. + */ + mutable std::mutex mutex_; + + /** + * @brief Maps a pair of (object_type, object_handle) to its name information. + * We use a pair as the key because handles are not guaranteed to be unique across different + * object types. + */ + std::map, VulkanObjectName> object_names_; +}; + +} // namespace layersvt + +#endif // LAYERSVT_OBJECT_NAMES_VULKAN_OBJECT_NAMES_H diff --git a/layersvt/object_names/vulkan_object_names_perfetto.h b/layersvt/object_names/vulkan_object_names_perfetto.h new file mode 100644 index 0000000000..21d7f03636 --- /dev/null +++ b/layersvt/object_names/vulkan_object_names_perfetto.h @@ -0,0 +1,66 @@ +/* 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. + */ + +#ifndef LAYERSVT_OBJECT_NAMES_VULKAN_OBJECT_NAMES_PERFETTO_H +#define LAYERSVT_OBJECT_NAMES_VULKAN_OBJECT_NAMES_PERFETTO_H + +#include "object_names/vulkan_object_names.h" +#include "perfetto/perfetto.h" + +namespace layersvt { + +/** + * @brief Writes one VulkanApiEvent.VkDebugUtilsObjectName trace packet. + * + * This is the single definition of the object-name packet layout, shared by every layer that + * publishes object names. It is a template because each layer declares its own track event data + * source (via PERFETTO_DEFINE_CATEGORIES) with its own category set; a layer instantiates this + * with its own perfetto::TrackEvent to provide EmitVulkanObjectName, e.g. + * + * @code + * namespace layersvt { + * void EmitVulkanObjectName(const VulkanObjectName& object_name) { + * WriteVulkanObjectNamePacket(object_name); + * } + * } // namespace layersvt + * @endcode + * + * The packet is written as a raw trace packet rather than a track event, so it is delivered to + * every session in which the layer's data source is enabled, independently of category filtering. + * + * @tparam TrackEventDataSource The layer's track event data source type. + * @param object_name The object name to write. + */ +template +void WriteVulkanObjectNamePacket(const VulkanObjectName& object_name) { + const uint64_t vk_device = object_name.vk_device; + const int32_t object_type = object_name.object_type; + const uint64_t handle = object_name.handle; + const std::string name = object_name.name; + + TrackEventDataSource::Trace([vk_device, object_type, handle, name](auto ctx) { + auto packet = ctx.NewTracePacket(); + packet->set_timestamp(perfetto::base::GetBootTimeNs().count()); + auto event = packet->set_vulkan_api_event()->set_vk_debug_utils_object_name(); + event->set_vk_device(vk_device); + event->set_object_type(object_type); + event->set_object(handle); + event->set_object_name(name.c_str()); + }); +} + +} // namespace layersvt + +#endif // LAYERSVT_OBJECT_NAMES_VULKAN_OBJECT_NAMES_PERFETTO_H diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index cd635acb9e..938b452944 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -35,7 +35,7 @@ function(LayerTest NAME) add_dependencies(${TEST_NAME} VkLayer_${NAME}) target_link_libraries(${TEST_NAME} Vulkan::Headers Vulkan::Loader GTest::gtest GTest::gtest_main Vulkan::LayerSettings) if (${NAME} STREQUAL "DebugMarker") - target_sources(${TEST_NAME} PRIVATE ../debug_marker/debug_marker.cpp ../debug_marker/debug_marker_perfetto.cpp ../perfetto/perfetto.cc) + target_sources(${TEST_NAME} PRIVATE ../debug_marker/debug_marker.cpp ../debug_marker/debug_marker_perfetto.cpp ../object_names/vulkan_object_names.cpp ../perfetto/perfetto.cc) target_include_directories(${TEST_NAME} PRIVATE .. ../debug_marker) elseif (${NAME} STREQUAL "DeviceMemoryReport") target_sources(${TEST_NAME} PRIVATE ../device_memory_report/device_memory_report.cpp ../device_memory_report/device_memory_report_perfetto.cpp ../perfetto/perfetto.cc) @@ -86,3 +86,19 @@ if (TARGET VkLayer_DeviceMemoryReport) endif() endif() + +# Unit tests for the object name bookkeeping shared by the DebugMarker and DeviceMemoryReport +# layers. The component deliberately has no Perfetto dependency (layers supply the emitter), so +# neither Perfetto nor the Vulkan loader is linked here. +add_executable(test_ObjectNames + test_objectnames.cpp + ../object_names/vulkan_object_names.cpp) +target_include_directories(test_ObjectNames PRIVATE ..) +target_link_libraries(test_ObjectNames Vulkan::Headers GTest::gtest GTest::gtest_main) +target_compile_definitions(test_ObjectNames PRIVATE VK_ENABLE_BETA_EXTENSIONS) +add_test(NAME test_ObjectNames COMMAND test_ObjectNames) +set_target_properties(test_ObjectNames PROPERTIES FOLDER "layers/ObjectNames/Test") + +if(WIN32 AND (QT_TARGET_TYPE STREQUAL STATIC_LIBRARY)) + set_property(TARGET test_ObjectNames PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() diff --git a/layersvt/test/test_debugmarker.cpp b/layersvt/test/test_debugmarker.cpp index e1f2325d2f..84fcf4c029 100644 --- a/layersvt/test/test_debugmarker.cpp +++ b/layersvt/test/test_debugmarker.cpp @@ -15,6 +15,7 @@ #include "layer_test_helper.h" #include "../debug_marker/debug_marker.h" +#include "../object_names/vulkan_object_names.h" #include #include #include @@ -32,7 +33,10 @@ class DebugMarkerTests : public VkTestFramework { TEST_F(DebugMarkerTests, CombinedTest) { TEST_DESCRIPTION("Combined test for DebugMarker layer"); + auto& object_names = layersvt::VulkanObjectNames::Get(); + DebugMarker::Get().Clear(); + object_names.Clear(); layer_test::VulkanInstanceBuilder inst_builder; inst_builder.AddExtension("VK_EXT_debug_utils"); VkResult err = inst_builder.Init(kLayerName); @@ -49,16 +53,16 @@ TEST_F(DebugMarkerTests, CombinedTest) { EXPECT_NE(pfnCmdDebugMarkerBeginEXT, nullptr); // 1. Set instance name - DebugMarker::Get().SetDebugObjectName(0, VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstance"); + object_names.SetObjectName(0, VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstance"); - EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstance")); + EXPECT_TRUE(object_names.HasObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstance")); // 2. Override new name - DebugMarker::Get().SetDebugObjectName(0, VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed"); - EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed")); - EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstance")); + object_names.SetObjectName(0, VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed"); + EXPECT_TRUE(object_names.HasObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed")); + EXPECT_FALSE(object_names.HasObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstance")); // 3. Clear - DebugMarker::Get().Clear(); - EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed")); + object_names.Clear(); + EXPECT_FALSE(object_names.HasObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed")); } diff --git a/layersvt/test/test_objectnames.cpp b/layersvt/test/test_objectnames.cpp new file mode 100644 index 0000000000..2a2aebd75f --- /dev/null +++ b/layersvt/test/test_objectnames.cpp @@ -0,0 +1,211 @@ +/* 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. + */ + +// Unit tests for the object name bookkeeping shared by the DebugMarker and DeviceMemoryReport +// layers. +// +// The component leaves EmitVulkanObjectName to be defined by whichever layer links it, so +// this test supplies its own recording implementation instead of a Perfetto one. That keeps the +// test free of any tracing backend while still covering what the layers rely on: what is stored, +// and exactly which packets would be emitted and when. + +#include "object_names/vulkan_object_names.h" + +#include + +#include +#include +#include + +namespace { + +std::vector& EmittedPackets() { + static std::vector packets; + return packets; +} + +// Resets both the store and the recorded packets. The store is a singleton, so every test must +// start from a known state. +void ResetState() { + layersvt::VulkanObjectNames::Get().Clear(); + EmittedPackets().clear(); +} + +VkDevice FakeDevice(uintptr_t value) { return reinterpret_cast(value); } + +} // namespace + +namespace layersvt { + +// The link seam under test: the store calls this for every name it stores or replays. +void EmitVulkanObjectName(const VulkanObjectName& object_name) { EmittedPackets().push_back(object_name); } + +} // namespace layersvt + +TEST(VulkanObjectNamesTest, StoresAndEmitsName) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + names.SetObjectName(0x1234, VK_OBJECT_TYPE_BUFFER, 0x42, "MyBuffer"); + + EXPECT_EQ(names.Size(), 1u); + EXPECT_TRUE(names.HasObjectName(VK_OBJECT_TYPE_BUFFER, 0x42, "MyBuffer")); + EXPECT_EQ(names.GetObjectName(VK_OBJECT_TYPE_BUFFER, 0x42), "MyBuffer"); + + ASSERT_EQ(EmittedPackets().size(), 1u); + EXPECT_EQ(EmittedPackets()[0].vk_device, 0x1234u); + EXPECT_EQ(EmittedPackets()[0].object_type, VK_OBJECT_TYPE_BUFFER); + EXPECT_EQ(EmittedPackets()[0].handle, 0x42u); + EXPECT_EQ(EmittedPackets()[0].name, "MyBuffer"); +} + +TEST(VulkanObjectNamesTest, RenamingReplacesTheStoredName) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + names.SetObjectName(0, VK_OBJECT_TYPE_IMAGE, 0x7, "First"); + names.SetObjectName(0, VK_OBJECT_TYPE_IMAGE, 0x7, "Second"); + + // Only the current name is retained, but both naming events were emitted. + EXPECT_EQ(names.Size(), 1u); + EXPECT_TRUE(names.HasObjectName(VK_OBJECT_TYPE_IMAGE, 0x7, "Second")); + EXPECT_FALSE(names.HasObjectName(VK_OBJECT_TYPE_IMAGE, 0x7, "First")); + EXPECT_EQ(EmittedPackets().size(), 2u); +} + +TEST(VulkanObjectNamesTest, HandlesAreScopedByObjectType) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + // Handles are only unique within an object type, so the same value must not collide. + names.SetObjectName(0, VK_OBJECT_TYPE_BUFFER, 0x100, "Buffer"); + names.SetObjectName(0, VK_OBJECT_TYPE_IMAGE, 0x100, "Image"); + + EXPECT_EQ(names.Size(), 2u); + EXPECT_EQ(names.GetObjectName(VK_OBJECT_TYPE_BUFFER, 0x100), "Buffer"); + EXPECT_EQ(names.GetObjectName(VK_OBJECT_TYPE_IMAGE, 0x100), "Image"); +} + +TEST(VulkanObjectNamesTest, NullNameIsStoredAsNullLiteral) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + names.SetObjectName(0, VK_OBJECT_TYPE_BUFFER, 0x1, nullptr); + + EXPECT_TRUE(names.HasObjectName(VK_OBJECT_TYPE_BUFFER, 0x1, "NULL")); +} + +TEST(VulkanObjectNamesTest, UnknownObjectHasNoName) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + EXPECT_FALSE(names.HasObjectName(VK_OBJECT_TYPE_BUFFER, 0x999, "Anything")); + EXPECT_EQ(names.GetObjectName(VK_OBJECT_TYPE_BUFFER, 0x999), ""); +} + +TEST(VulkanObjectNamesTest, DebugUtilsNameInfoIsStored) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + VkDebugUtilsObjectNameInfoEXT name_info = {}; + name_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; + name_info.objectType = VK_OBJECT_TYPE_IMAGE; + name_info.objectHandle = 0xABC; + name_info.pObjectName = "GBuffer"; + + names.SetObjectName(FakeDevice(0x5000), &name_info); + + EXPECT_TRUE(names.HasObjectName(VK_OBJECT_TYPE_IMAGE, 0xABC, "GBuffer")); + ASSERT_EQ(EmittedPackets().size(), 1u); + EXPECT_EQ(EmittedPackets()[0].vk_device, (uint64_t)FakeDevice(0x5000)); +} + +TEST(VulkanObjectNamesTest, DebugMarkerNameInfoIsMappedToModernObjectType) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + VkDebugMarkerObjectNameInfoEXT name_info = {}; + name_info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT; + name_info.objectType = VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT; + name_info.object = 0xABC; + name_info.pObjectName = "LegacyImage"; + + names.SetObjectName(FakeDevice(0x5000), &name_info); + + // Stored against the modern enum, matching what VK_EXT_debug_utils would have produced. + EXPECT_TRUE(names.HasObjectName(VK_OBJECT_TYPE_IMAGE, 0xABC, "LegacyImage")); + ASSERT_EQ(EmittedPackets().size(), 1u); + EXPECT_EQ(EmittedPackets()[0].object_type, VK_OBJECT_TYPE_IMAGE); +} + +TEST(VulkanObjectNamesTest, NullNameInfoIsIgnored) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + names.SetObjectName(FakeDevice(0x1), static_cast(nullptr)); + names.SetObjectName(FakeDevice(0x1), static_cast(nullptr)); + + EXPECT_EQ(names.Size(), 0u); + EXPECT_EQ(EmittedPackets().size(), 0u); +} + +TEST(VulkanObjectNamesTest, EmitAllReplaysEveryKnownName) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + names.SetObjectName(0, VK_OBJECT_TYPE_BUFFER, 0x1, "A"); + names.SetObjectName(0, VK_OBJECT_TYPE_BUFFER, 0x2, "B"); + names.SetObjectName(0, VK_OBJECT_TYPE_BUFFER, 0x1, "A2"); + EmittedPackets().clear(); + + // A session starting late must still observe the current name of every object, and only the + // current one. + names.EmitAll(); + + ASSERT_EQ(EmittedPackets().size(), 2u); + EXPECT_EQ(EmittedPackets()[0].name, "A2"); + EXPECT_EQ(EmittedPackets()[1].name, "B"); +} + +TEST(VulkanObjectNamesTest, ClearDropsEverything) { + ResetState(); + auto& names = layersvt::VulkanObjectNames::Get(); + + names.SetObjectName(0, VK_OBJECT_TYPE_BUFFER, 0x1, "A"); + names.Clear(); + EmittedPackets().clear(); + + EXPECT_EQ(names.Size(), 0u); + names.EmitAll(); + EXPECT_EQ(EmittedPackets().size(), 0u); +} + +TEST(VulkanObjectNamesTest, LegacyObjectTypeMapping) { + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT), VK_OBJECT_TYPE_UNKNOWN); + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT), VK_OBJECT_TYPE_INSTANCE); + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT), VK_OBJECT_TYPE_DEVICE); + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT), VK_OBJECT_TYPE_BUFFER); + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT), VK_OBJECT_TYPE_IMAGE); + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT), + VK_OBJECT_TYPE_DEVICE_MEMORY); + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT), + VK_OBJECT_TYPE_COMMAND_BUFFER); + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT), + VK_OBJECT_TYPE_SWAPCHAIN_KHR); + + // Anything without a modern equivalent falls back to UNKNOWN rather than a bogus type. + EXPECT_EQ(layersvt::VkObjectTypeFromDebugReportObjectType(static_cast(0x7FFFFFFF)), + VK_OBJECT_TYPE_UNKNOWN); +} From 027625409fe10ba383e3f3e2dda17d5986fc16ea Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Mon, 21 Sep 2026 23:33:21 +0000 Subject: [PATCH 2/2] device_memory_report: publish object names for memory attribution Memory counters are only actionable if the buffers and images behind them can be identified, so the layer now observes vkSetDebugUtilsObjectNameEXT and vkDebugMarkerSetObjectNameEXT and publishes the names the application sets. Storage, the legacy VkDebugReportObjectTypeEXT mapping and the session-start replay come from layersvt::VulkanObjectNames, shared with DebugMarker. The two layers deliberately emit different events, which is worth stating plainly because it looks like an inconsistency to fix. DebugMarker writes VulkanApiEvent.VkDebugUtilsObjectName packets. Perfetto's trace_processor folds those into an internal map (debug_marker_names_ in gpu_event_parser.cc) and reads it back for exactly three object types: render pass, render target and command buffer, being the handles a GpuRenderStageEvent carries. A name on a VkBuffer or VkImage in that form is parsed and then never surfaced, because nothing joins to it and the map is not exposed as a table. This layer therefore emits "VulkanObjectName" instant events carrying object_type, object_handle and object_name. Those reach the slice table and can be joined to the memory events by object handle, which is what a consumer of this layer's output actually needs. Because the events differ, loading both layers duplicates neither. Unlike DebugMarker, this layer does not claim VK_EXT_debug_utils or VK_EXT_debug_marker. It only intercepts these entry points when the driver below already implements them, which the existing down_func == nullptr guard in vkGetDeviceProcAddr and vkGetInstanceProcAddr enforces. --- layersvt/CMakeLists.txt | 3 + ...ice_memory_report_handwritten_dispatch.cpp | 3 + ...eport_handwritten_functions_object_names.h | 64 ++++++++++++++ .../device_memory_report_perfetto.cpp | 24 +++++ layersvt/object_names/vulkan_object_names.h | 14 ++- layersvt/test/CMakeLists.txt | 5 +- .../test/test_devicememoryreport_dispatch.cpp | 87 +++++++++++++++++++ 7 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 layersvt/device_memory_report/device_memory_report_handwritten_functions_object_names.h diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 048bf619ac..fc683fb233 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -210,10 +210,13 @@ if(BUILD_DEVICEMEMORYREPORT) target_sources(VkLayer_DeviceMemoryReport PRIVATE device_memory_report/device_memory_report_handwritten_dispatch.cpp device_memory_report/device_memory_report_handwritten_functions.h + device_memory_report/device_memory_report_handwritten_functions_object_names.h device_memory_report/device_memory_report.h device_memory_report/device_memory_report.cpp device_memory_report/device_memory_report_perfetto.h device_memory_report/device_memory_report_perfetto.cpp + object_names/vulkan_object_names.h + object_names/vulkan_object_names.cpp perfetto/perfetto.cc vk_layer_table.cpp vk_layer_table.h 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..a87eeeb4d1 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -14,6 +14,7 @@ */ #include "device_memory_report_handwritten_functions.h" +#include "device_memory_report_handwritten_functions_object_names.h" #include "vk_layer_table.h" #include @@ -61,6 +62,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_object_names.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions_object_names.h new file mode 100644 index 0000000000..41763d284f --- /dev/null +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions_object_names.h @@ -0,0 +1,64 @@ +/* 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 +#include "vk_layer_table.h" +#include "object_names/vulkan_object_names.h" + +// Object naming intercepts for the DeviceMemoryReport layer. +// +// Memory counters are only actionable if the buffers and images behind them can be identified, so +// this layer publishes the object names the application sets. The bookkeeping itself (name storage, +// mapping of the legacy VK_EXT_debug_marker object types, and replaying known names when a tracing +// session starts) is shared with the DebugMarker layer through layersvt::VulkanObjectNames, so the +// two layers cannot drift apart on what a name is or when it changes. +// +// What they do not share is how a name reaches the trace. DebugMarker writes +// VulkanApiEvent.VkDebugUtilsObjectName packets, which trace_processor reads back only for render +// passes, render targets and command buffers; a name on a buffer or image in that form is parsed +// and then never surfaced. This layer therefore emits "VulkanObjectName" instant events instead, +// which land in the slice table and can be joined to the memory events by object handle. See +// EmitVulkanObjectName in device_memory_report_perfetto.cpp. +// +// Because the two layers emit different events, loading both does not duplicate either one. +// +// Unlike the DebugMarker layer, this layer does not claim VK_EXT_debug_utils or +// VK_EXT_debug_marker. It only observes these entry points when the underlying driver already +// implements them, which vkGetDeviceProcAddr / vkGetInstanceProcAddr verify before handing out +// these interceptors. + +extern "C" { + +// Observes VK_EXT_debug_utils object names, then passes the call down the chain. +VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { + layersvt::VulkanObjectNames::Get().SetObjectName(device, pNameInfo); + if (device_dispatch_table(device)->SetDebugUtilsObjectNameEXT) { + return device_dispatch_table(device)->SetDebugUtilsObjectNameEXT(device, pNameInfo); + } + return VK_SUCCESS; +} + +// Observes legacy VK_EXT_debug_marker object names, then passes the call down the chain. +VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo) { + layersvt::VulkanObjectNames::Get().SetObjectName(device, pNameInfo); + if (device_dispatch_table(device)->DebugMarkerSetObjectNameEXT) { + return device_dispatch_table(device)->DebugMarkerSetObjectNameEXT(device, pNameInfo); + } + return VK_SUCCESS; +} + +} // extern "C" diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index b5fc41d262..74d8665f7d 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -15,6 +15,7 @@ #include "device_memory_report_perfetto.h" #include "device_memory_report.h" +#include "object_names/vulkan_object_names.h" #include #include #include @@ -23,6 +24,24 @@ PERFETTO_TRACK_EVENT_STATIC_STORAGE(); +namespace layersvt { + +// Binds the shared object name store to this layer's track event data source. +// +// Unlike the DebugMarker layer, this emits an ordinary instant event rather than a +// VulkanApiEvent.VkDebugUtilsObjectName trace packet. trace_processor only reads the latter back for +// render passes, render targets and command buffers, so a name on the buffers and images this layer +// reports on would be parsed and then never surfaced. As an instant event the name becomes a slice +// that a consumer can query directly and join to the memory events by object handle. +void EmitVulkanObjectName(const VulkanObjectName& object_name) { + TRACE_EVENT_INSTANT("VulkanDeviceMemoryReport", "VulkanObjectName", + "object_type", object_name.object_type, + "object_handle", object_name.handle, + "object_name", object_name.name.c_str()); +} + +} // namespace layersvt + namespace { class DeviceMemoryReportSessionObserver : public perfetto::TrackEventSessionObserver { @@ -31,6 +50,7 @@ class DeviceMemoryReportSessionObserver : public perfetto::TrackEventSessionObse // Touch the singleton during observer construction so DeviceMemoryReport // completes construction first and is destroyed after this observer unregisters. (void)DeviceMemoryReport::Get(); + (void)layersvt::VulkanObjectNames::Get(); } ~DeviceMemoryReportSessionObserver() override { @@ -39,6 +59,9 @@ class DeviceMemoryReportSessionObserver : public perfetto::TrackEventSessionObse void OnStart(const perfetto::DataSourceBase::StartArgs&) override { DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); + // Replay the names of objects that were named before this session started, so that + // memory attributed to a buffer or image can still be traced back to it. + layersvt::VulkanObjectNames::Get().EmitAll(); } }; @@ -56,6 +79,7 @@ void InitializeDeviceMemoryReportPerfetto() { if (TRACE_EVENT_CATEGORY_ENABLED("VulkanDeviceMemoryReport")) { DeviceMemoryReport::Get().DumpCurrentCountersAndAllocations(); + layersvt::VulkanObjectNames::Get().EmitAll(); } }); } diff --git a/layersvt/object_names/vulkan_object_names.h b/layersvt/object_names/vulkan_object_names.h index 8ca425eb02..bbc8c9ad17 100644 --- a/layersvt/object_names/vulkan_object_names.h +++ b/layersvt/object_names/vulkan_object_names.h @@ -58,9 +58,17 @@ struct VulkanObjectName { * its own category set. Resolving the emitter at link time keeps this component free of any * Perfetto dependency. * - * The representation is left to the layer as well. DebugMarker writes a - * VulkanApiEvent.VkDebugUtilsObjectName trace packet, for which vulkan_object_names_perfetto.h - * provides a ready-made implementation. + * The layers deliberately emit *different representations*, because their names are read back by + * different consumers: + * + * - DebugMarker writes a VulkanApiEvent.VkDebugUtilsObjectName trace packet (see + * vulkan_object_names_perfetto.h). Perfetto's trace_processor folds those into an internal map and + * uses it to label GPU render stage slices with their render pass, render target and command + * buffer names. + * - DeviceMemoryReport writes a "VulkanObjectName" track event instead, because trace_processor only + * resolves that internal map for those three render stage object types. A name on a VkBuffer or + * VkImage would be parsed and then never read, so the memory layer publishes names as ordinary + * instant events that its consumer can query directly. * * @param object_name The object name to write. Called with the store's mutex held, so * implementations must not call back into VulkanObjectNames. diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index 938b452944..43319494ff 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -38,7 +38,7 @@ function(LayerTest NAME) target_sources(${TEST_NAME} PRIVATE ../debug_marker/debug_marker.cpp ../debug_marker/debug_marker_perfetto.cpp ../object_names/vulkan_object_names.cpp ../perfetto/perfetto.cc) target_include_directories(${TEST_NAME} PRIVATE .. ../debug_marker) elseif (${NAME} STREQUAL "DeviceMemoryReport") - target_sources(${TEST_NAME} PRIVATE ../device_memory_report/device_memory_report.cpp ../device_memory_report/device_memory_report_perfetto.cpp ../perfetto/perfetto.cc) + target_sources(${TEST_NAME} PRIVATE ../device_memory_report/device_memory_report.cpp ../device_memory_report/device_memory_report_perfetto.cpp ../object_names/vulkan_object_names.cpp ../perfetto/perfetto.cc) target_include_directories(${TEST_NAME} PRIVATE .. ../device_memory_report) endif() target_compile_definitions(${TEST_NAME} PUBLIC LAYER_BINARY_PATH="$") @@ -72,6 +72,7 @@ if (TARGET VkLayer_DeviceMemoryReport) ../device_memory_report/device_memory_report_handwritten_dispatch.cpp ../device_memory_report/device_memory_report.cpp ../device_memory_report/device_memory_report_perfetto.cpp + ../object_names/vulkan_object_names.cpp ../perfetto/perfetto.cc ../vk_layer_table.cpp) target_include_directories(test_DeviceMemoryReport_dispatch PRIVATE .. ../device_memory_report) @@ -86,7 +87,6 @@ if (TARGET VkLayer_DeviceMemoryReport) endif() endif() - # Unit tests for the object name bookkeeping shared by the DebugMarker and DeviceMemoryReport # layers. The component deliberately has no Perfetto dependency (layers supply the emitter), so # neither Perfetto nor the Vulkan loader is linked here. @@ -102,3 +102,4 @@ set_target_properties(test_ObjectNames PROPERTIES FOLDER "layers/ObjectNames/Tes if(WIN32 AND (QT_TARGET_TYPE STREQUAL STATIC_LIBRARY)) set_property(TARGET test_ObjectNames PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() + diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 200117cfbf..1b17ae3e89 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 "object_names/vulkan_object_names.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; +// Whether the stub driver implements the object naming entry points, and how often it saw them. +// The layer must only intercept naming calls when the driver below actually supports them. +bool g_supports_object_names = false; +int g_set_debug_utils_object_name_calls = 0; +int g_debug_marker_set_object_name_calls = 0; + template HandleType MakeHandle(uintptr_t value) { return reinterpret_cast(value); @@ -92,10 +99,25 @@ 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_object_name_calls; + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL StubDebugMarkerSetObjectNameEXT(VkDevice, const VkDebugMarkerObjectNameInfoEXT*) { + ++g_debug_marker_set_object_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 (g_supports_object_names) { + if (name == "vkSetDebugUtilsObjectNameEXT") return reinterpret_cast(StubSetDebugUtilsObjectNameEXT); + if (name == "vkDebugMarkerSetObjectNameEXT") return reinterpret_cast(StubDebugMarkerSetObjectNameEXT); + } + if (name == "vkCreateImage") return reinterpret_cast(StubCreateImage); if (name == "vkDestroyImage") return reinterpret_cast(StubDestroyImage); if (name == "vkAllocateMemory") return reinterpret_cast(StubAllocateMemory); @@ -140,14 +162,20 @@ class DeviceMemoryReportDispatchTests : public ::testing::Test { protected: void SetUp() override { DeviceMemoryReport::Get().Reset(); + layersvt::VulkanObjectNames::Get().Clear(); g_buffer_requirements_size = 0; g_image_requirements_size = 0; g_buffer_requirements_queries = 0; g_image_requirements_queries = 0; + g_supports_object_names = false; + g_set_debug_utils_object_name_calls = 0; + g_debug_marker_set_object_name_calls = 0; } void TearDown() override { DeviceMemoryReport::Get().Reset(); + layersvt::VulkanObjectNames::Get().Clear(); + g_supports_object_names = false; } }; @@ -278,5 +306,64 @@ TEST_F(DeviceMemoryReportDispatchTests, BindImageMemory2SkipsDisjointImagePlaneB EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); } +TEST_F(DeviceMemoryReportDispatchTests, ObjectNameEntryPointsHiddenWhenDriverLacksSupport) { + // The layer does not claim VK_EXT_debug_utils or VK_EXT_debug_marker. Handing out an + // interceptor for a command the driver does not implement would make the application believe + // the extension is available. + g_supports_object_names = false; + FakeDevice device; + + EXPECT_EQ(vkGetDeviceProcAddr(device.handle(), "vkSetDebugUtilsObjectNameEXT"), nullptr); + EXPECT_EQ(vkGetDeviceProcAddr(device.handle(), "vkDebugMarkerSetObjectNameEXT"), nullptr); +} + +TEST_F(DeviceMemoryReportDispatchTests, ObjectNameEntryPointsInterceptedWhenDriverSupportsThem) { + g_supports_object_names = true; + FakeDevice device; + + PFN_vkVoidFunction debug_utils = vkGetDeviceProcAddr(device.handle(), "vkSetDebugUtilsObjectNameEXT"); + PFN_vkVoidFunction debug_marker = vkGetDeviceProcAddr(device.handle(), "vkDebugMarkerSetObjectNameEXT"); + + // The layer's own interceptors must be returned, not the driver's implementations. + EXPECT_EQ(debug_utils, reinterpret_cast(vkSetDebugUtilsObjectNameEXT)); + EXPECT_EQ(debug_marker, reinterpret_cast(vkDebugMarkerSetObjectNameEXT)); +} + +TEST_F(DeviceMemoryReportDispatchTests, SetDebugUtilsObjectNameRecordsAndForwards) { + g_supports_object_names = true; + FakeDevice device; + VkBuffer buffer = MakeHandle(0xC1000); + + 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 = "VertexBuffer"; + + EXPECT_EQ(vkSetDebugUtilsObjectNameEXT(device.handle(), &name_info), VK_SUCCESS); + + EXPECT_TRUE(layersvt::VulkanObjectNames::Get().HasObjectName(VK_OBJECT_TYPE_BUFFER, AsObjectHandle(buffer), "VertexBuffer")); + // The application's call must still reach the driver. + EXPECT_EQ(g_set_debug_utils_object_name_calls, 1); +} + +TEST_F(DeviceMemoryReportDispatchTests, DebugMarkerSetObjectNameRecordsAndForwards) { + g_supports_object_names = true; + FakeDevice device; + VkImage image = MakeHandle(0xC2000); + + VkDebugMarkerObjectNameInfoEXT name_info = {}; + name_info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT; + name_info.objectType = VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT; + name_info.object = AsObjectHandle(image); + name_info.pObjectName = "ShadowMap"; + + EXPECT_EQ(vkDebugMarkerSetObjectNameEXT(device.handle(), &name_info), VK_SUCCESS); + + // Stored against the modern object type, exactly as the DebugMarker layer would store it. + EXPECT_TRUE(layersvt::VulkanObjectNames::Get().HasObjectName(VK_OBJECT_TYPE_IMAGE, AsObjectHandle(image), "ShadowMap")); + EXPECT_EQ(g_debug_marker_set_object_name_calls, 1); +} + } // namespace