diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 92538d5e3a..fc683fb233 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 @@ -207,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/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/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.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..bbc8c9ad17 --- /dev/null +++ b/layersvt/object_names/vulkan_object_names.h @@ -0,0 +1,190 @@ +/* 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 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. + */ +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..43319494ff 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -35,10 +35,10 @@ 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) + 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,3 +87,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_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 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); +}