From a95b9506c31de0a55492f820ed2810e866d2b8aa Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Wed, 2 Sep 2026 16:13:19 +0100 Subject: [PATCH 1/3] layersvt: Refactor DebugMarker to inherit from LayerBase Refactor the DebugMarker layer to inherit from LayerBase, replacing handwritten Vulkan lifecycle boilerplate and dispatch bookkeeping with shared layersvt_common infrastructure: - Inherit DebugMarker from LayerBase and configure LayerManifest with VK_EXT_debug_marker and VK_EXT_debug_utils extensions. - Replace manual instance and device dispatch tables and physical device tracking with DispatchTableManager. - Remove handwritten vkCreateInstance, vkDestroyInstance, vkCreateDevice, vkDestroyDevice, and extension enumeration boilerplate from debug_marker_handwritten_dispatch.cpp. - Implement the GetLayerInstanceCommand and GetLayerDeviceCommand virtual hooks to intercept debug marker and debug utils commands. Since the layer implements both extensions itself, the hooks are returned unconditionally and forward downstream through DispatchDownstreamIfAvailable, DispatchDownstreamOrSuccess, or DispatchDownstreamOr, so they do not crash when the driver lacks the extensions. - Move the g_layer singleton into debug_marker_instance.cpp, which is compiled only into the layer module, so unit tests can manage the active instance via layer_test::ResetLayer. - Delete debug_marker_handwritten_functions.h header. - Add unit tests in test_debugmarker.cpp covering manifest queries, LayerBase lifecycle, dispatch fallback, and hooks with a downstream that lacks the extensions. Bug: Test: new tests - DebugMarkerTests Change-Id: I5ad00681e85f15d0779ad5dcea806b8e6a6a6964 --- layersvt/CMakeLists.txt | 9 +- layersvt/debug_marker/VkLayer_DebugMarker.def | 2 + layersvt/debug_marker/debug_marker.cpp | 88 ++++-- layersvt/debug_marker/debug_marker.h | 53 ++-- .../debug_marker_handwritten_dispatch.cpp | 109 -------- .../debug_marker_handwritten_functions.h | 250 ------------------ ...andwritten_functions_vk_ext_debug_marker.h | 34 +-- ...handwritten_functions_vk_ext_debug_utils.h | 81 +++--- .../debug_marker/debug_marker_instance.cpp | 22 ++ layersvt/test/CMakeLists.txt | 4 + layersvt/test/test_debugmarker.cpp | 167 +++++++++++- 11 files changed, 334 insertions(+), 485 deletions(-) delete mode 100644 layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp delete mode 100644 layersvt/debug_marker/debug_marker_handwritten_functions.h create mode 100644 layersvt/debug_marker/debug_marker_instance.cpp diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 30e43d02b9..99241a66bd 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -169,18 +169,15 @@ if(BUILD_DEBUGMARKER) add_library(VkLayer_DebugMarker MODULE) set_target_properties(VkLayer_DebugMarker PROPERTIES FOLDER "layers/debugmarker") target_sources(VkLayer_DebugMarker PRIVATE - debug_marker/debug_marker_handwritten_dispatch.cpp - debug_marker/debug_marker_handwritten_functions.h + $ debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h debug_marker/debug_marker.h + debug_marker/debug_marker_instance.cpp debug_marker/debug_marker.cpp debug_marker/debug_marker_perfetto.h debug_marker/debug_marker_perfetto.cpp perfetto/perfetto.cc - vk_layer_table.cpp - vk_layer_table.h - common/layer_keep_alive.cpp debug_marker/VkLayer_DebugMarker.json.in ) @@ -190,6 +187,8 @@ if(BUILD_DEBUGMARKER) ${CMAKE_CURRENT_BINARY_DIR} ) + target_link_libraries(VkLayer_DebugMarker PRIVATE layersvt_common) + if(CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|DragonFly|GNU") if (BUILD_WSI_XCB_SUPPORT) target_compile_definitions(VkLayer_DebugMarker PRIVATE VK_USE_PLATFORM_XLIB_KHR) diff --git a/layersvt/debug_marker/VkLayer_DebugMarker.def b/layersvt/debug_marker/VkLayer_DebugMarker.def index a00a872d02..8f69252213 100644 --- a/layersvt/debug_marker/VkLayer_DebugMarker.def +++ b/layersvt/debug_marker/VkLayer_DebugMarker.def @@ -18,3 +18,5 @@ vkGetInstanceProcAddr vkGetDeviceProcAddr vkEnumerateInstanceLayerProperties vkEnumerateInstanceExtensionProperties +vkEnumerateDeviceLayerProperties +vkEnumerateDeviceExtensionProperties diff --git a/layersvt/debug_marker/debug_marker.cpp b/layersvt/debug_marker/debug_marker.cpp index 6f21ff4795..24c1520f08 100644 --- a/layersvt/debug_marker/debug_marker.cpp +++ b/layersvt/debug_marker/debug_marker.cpp @@ -15,23 +15,37 @@ #include "debug_marker.h" #include "debug_marker_perfetto.h" +#include "debug_marker_handwritten_functions_vk_ext_debug_marker.h" +#include "debug_marker_handwritten_functions_vk_ext_debug_utils.h" #include "perfetto/perfetto.h" +#include -DebugMarker& DebugMarker::Get() { - static DebugMarker instance; - return instance; -} +DebugMarker::DebugMarker() = default; -void DebugMarker::SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance) { - std::lock_guard lock(mutex_); - vk_instance_map_[phys_dev] = instance; +const layersvt::LayerManifest* DebugMarker::GetLayerManifest() const { + static const layersvt::LayerManifest manifest{ + .layer_name = "VK_LAYER_GOOGLE_DebugMarker", + .description = "layer: DebugMarker", + .spec_version = VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), + .implementation_version = VK_MAKE_VERSION(0, 1, 0), + .instance_extensions = + { + {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, + }, + .device_extensions = + { + {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, + }, + .tool_properties = std::nullopt, + }; + return &manifest; } -VkInstance DebugMarker::GetVkInstance(VkPhysicalDevice phys_dev) { - std::lock_guard lock(mutex_); - auto it = vk_instance_map_.find(phys_dev); - if (it != vk_instance_map_.end()) return it->second; - return VK_NULL_HANDLE; +void DebugMarker::PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) { + (void)pCreateInfo; + (void)pAllocator; + static std::once_flag perfetto_initialization_flag; + std::call_once(perfetto_initialization_flag, []() { InitializeDebugMarkerPerfetto(); }); } void DebugMarker::SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name) { @@ -72,15 +86,53 @@ void DebugMarker::EmitAllDebugMarkers() { } } -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; } + +PFN_vkVoidFunction DebugMarker::GetLayerInstanceCommand(VkInstance instance, const char* name) { + (void)instance; + if (!name) return nullptr; + if (strcmp(name, "vkCreateDebugUtilsMessengerEXT") == 0) + return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); + if (strcmp(name, "vkDestroyDebugUtilsMessengerEXT") == 0) + return reinterpret_cast(vkDestroyDebugUtilsMessengerEXT); + if (strcmp(name, "vkSubmitDebugUtilsMessageEXT") == 0) + return reinterpret_cast(vkSubmitDebugUtilsMessageEXT); + return nullptr; +} + +PFN_vkVoidFunction DebugMarker::GetLayerDeviceCommand(VkDevice device, const char* name) { + (void)device; + if (!name) return nullptr; + + // VK_EXT_debug_marker + if (strcmp(name, "vkCmdDebugMarkerBeginEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerBeginEXT); + if (strcmp(name, "vkCmdDebugMarkerEndEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerEndEXT); + if (strcmp(name, "vkCmdDebugMarkerInsertEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerInsertEXT); + if (strcmp(name, "vkDebugMarkerSetObjectNameEXT") == 0) + return reinterpret_cast(vkDebugMarkerSetObjectNameEXT); + if (strcmp(name, "vkDebugMarkerSetObjectTagEXT") == 0) + return reinterpret_cast(vkDebugMarkerSetObjectTagEXT); + + // VK_EXT_debug_utils + if (strcmp(name, "vkCmdBeginDebugUtilsLabelEXT") == 0) + return reinterpret_cast(vkCmdBeginDebugUtilsLabelEXT); + if (strcmp(name, "vkCmdEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdEndDebugUtilsLabelEXT); + if (strcmp(name, "vkCmdInsertDebugUtilsLabelEXT") == 0) + return reinterpret_cast(vkCmdInsertDebugUtilsLabelEXT); + if (strcmp(name, "vkSetDebugUtilsObjectNameEXT") == 0) + return reinterpret_cast(vkSetDebugUtilsObjectNameEXT); + if (strcmp(name, "vkSetDebugUtilsObjectTagEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectTagEXT); + if (strcmp(name, "vkQueueBeginDebugUtilsLabelEXT") == 0) + return reinterpret_cast(vkQueueBeginDebugUtilsLabelEXT); + if (strcmp(name, "vkQueueEndDebugUtilsLabelEXT") == 0) + return reinterpret_cast(vkQueueEndDebugUtilsLabelEXT); + if (strcmp(name, "vkQueueInsertDebugUtilsLabelEXT") == 0) + return reinterpret_cast(vkQueueInsertDebugUtilsLabelEXT); + + return nullptr; +} diff --git a/layersvt/debug_marker/debug_marker.h b/layersvt/debug_marker/debug_marker.h index b705cefbfb..954b88d1c8 100644 --- a/layersvt/debug_marker/debug_marker.h +++ b/layersvt/debug_marker/debug_marker.h @@ -16,12 +16,15 @@ #pragma once #include +#include #include -#include #include #include +#include "common/layer_base.h" +#include "common/layer_manifest.h" + /** * The DebugMarker class is responsible for storing and managing debug marker * information associated with Vulkan objects and emitting them to Perfetto traces. @@ -44,20 +47,21 @@ * 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. - * * 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 and provides thread-safe access to its state. + * This class is a singleton, inherits from LayerBase, and provides thread-safe access to its state. */ -class DebugMarker { +class DebugMarker : public layersvt::LayerBase { public: - /** - * @brief Returns the singleton instance of the DebugMarker class. - * @return Reference to the DebugMarker singleton. - */ - static DebugMarker& Get(); + DebugMarker(); + ~DebugMarker() override = default; + + static DebugMarker& Get() { + assert(LayerBase::Get() != nullptr && "LayerBase instance must be initialized"); + return *static_cast(LayerBase::Get()); + } /** * @brief Sets or updates the name associated with a Vulkan object. @@ -67,38 +71,29 @@ class DebugMarker { * @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. - * @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); + protected: /** - * @brief Associates a Vulkan physical device with its corresponding instance. - * @param phys_dev The Vulkan physical device. - * @param instance The Vulkan instance. + * Lifecycle hook called before vkCreateInstance. */ - void SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance); - - /** - * @brief Retrieves the Vulkan instance associated with a given physical device. - * @param phys_dev The Vulkan physical device. - * @return The associated Vulkan instance. - */ - VkInstance GetVkInstance(VkPhysicalDevice phys_dev); + void PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) override; + const layersvt::LayerManifest* GetLayerManifest() const override; + // VK_EXT_debug_marker and VK_EXT_debug_utils are implemented by this layer, so their commands + // are returned unconditionally; the hooks tolerate a downstream that lacks the extension. + PFN_vkVoidFunction GetLayerInstanceCommand(VkInstance instance, const char* name) override; + PFN_vkVoidFunction GetLayerDeviceCommand(VkDevice device, const char* name) override; private: struct DebugObjectName { @@ -113,10 +108,6 @@ class DebugMarker { }; 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. diff --git a/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp b/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp deleted file mode 100644 index a320032500..0000000000 --- a/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* 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 "debug_marker_handwritten_functions.h" -#include "debug_marker_handwritten_functions_vk_ext_debug_marker.h" -#include "debug_marker_handwritten_functions_vk_ext_debug_utils.h" -#include "vk_layer_table.h" -#include - -extern "C" { - -static PFN_vkVoidFunction debug_marker_known_instance_functions(const char* pName) { - if (strcmp(pName, "vkGetInstanceProcAddr") == 0) return reinterpret_cast(vkGetInstanceProcAddr); - if (strcmp(pName, "vkCreateInstance") == 0) return reinterpret_cast(vkCreateInstance); - if (strcmp(pName, "vkDestroyInstance") == 0) return reinterpret_cast(vkDestroyInstance); - if (strcmp(pName, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast(vkEnumeratePhysicalDevices); - if (strcmp(pName, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast(vkEnumeratePhysicalDeviceGroups); - if (strcmp(pName, "vkEnumerateInstanceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateInstanceExtensionProperties); - if (strcmp(pName, "vkEnumerateInstanceLayerProperties") == 0) return reinterpret_cast(vkEnumerateInstanceLayerProperties); - if (strcmp(pName, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); - if (strcmp(pName, "vkDestroyDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkDestroyDebugUtilsMessengerEXT); - if (strcmp(pName, "vkSubmitDebugUtilsMessageEXT") == 0) return reinterpret_cast(vkSubmitDebugUtilsMessageEXT); - return nullptr; -} - -static PFN_vkVoidFunction debug_marker_known_device_functions(const char* pName) { - if (strcmp(pName, "vkGetDeviceProcAddr") == 0) return reinterpret_cast(vkGetDeviceProcAddr); - if (strcmp(pName, "vkCreateDevice") == 0) return reinterpret_cast(vkCreateDevice); - if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast(vkEnumerateDeviceLayerProperties); - if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); - - // VK_EXT_debug_marker - if (strcmp(pName, "vkCmdDebugMarkerBeginEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerBeginEXT); - if (strcmp(pName, "vkCmdDebugMarkerEndEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerEndEXT); - if (strcmp(pName, "vkCmdDebugMarkerInsertEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerInsertEXT); - if (strcmp(pName, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectNameEXT); - if (strcmp(pName, "vkDebugMarkerSetObjectTagEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectTagEXT); - - // VK_EXT_debug_utils - if (strcmp(pName, "vkCmdBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdBeginDebugUtilsLabelEXT); - if (strcmp(pName, "vkCmdEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdEndDebugUtilsLabelEXT); - if (strcmp(pName, "vkCmdInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdInsertDebugUtilsLabelEXT); - if (strcmp(pName, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectNameEXT); - if (strcmp(pName, "vkSetDebugUtilsObjectTagEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectTagEXT); - if (strcmp(pName, "vkQueueBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueBeginDebugUtilsLabelEXT); - if (strcmp(pName, "vkQueueEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueEndDebugUtilsLabelEXT); - if (strcmp(pName, "vkQueueInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueInsertDebugUtilsLabelEXT); - - return nullptr; -} - -EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* pName) { - PFN_vkVoidFunction func = debug_marker_known_instance_functions(pName); - if (func) { - return func; - } - - // If it's a device function, we can also return it here if we want to support GIPA for device functions. - func = debug_marker_known_device_functions(pName); - if (func) { - return func; - } - - if (instance == nullptr) { - return nullptr; - } - - auto table = instance_dispatch_table(instance); - if (table == NULL) { - return nullptr; - } - - if (table->GetInstanceProcAddr == NULL) { - return nullptr; - } - - return table->GetInstanceProcAddr(instance, pName); -} - -EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char* pName) { - PFN_vkVoidFunction func = debug_marker_known_device_functions(pName); - if (func) { - return func; - } - - if (device == nullptr) { - return nullptr; - } - - if (device_dispatch_table(device)->GetDeviceProcAddr == NULL) { - return nullptr; - } - - return device_dispatch_table(device)->GetDeviceProcAddr(device, pName); -} - -} // extern "C" diff --git a/layersvt/debug_marker/debug_marker_handwritten_functions.h b/layersvt/debug_marker/debug_marker_handwritten_functions.h deleted file mode 100644 index 18fe280c05..0000000000 --- a/layersvt/debug_marker/debug_marker_handwritten_functions.h +++ /dev/null @@ -1,250 +0,0 @@ -/* 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 -#include -#include -#include -#include "vk_layer_table.h" -#include "debug_marker.h" -#include "debug_marker_perfetto.h" - -// This file contains handwritten implementations for core Vulkan functions -// (instance/device creation and physical device enumeration) required for the layer's -// infrastructure and state management: -// -// - vkCreateInstance: Initializes Perfetto tracing, the instance dispatch table, and performs eager physical device enumeration. -// - vkEnumeratePhysicalDevices / vkEnumeratePhysicalDeviceGroups: Tracks the mapping -// between physical devices and instances to support dispatch table lookups. -// - vkCreateDevice: Initializes the device dispatch table for intercepted devices. -// -// Extension-specific functions (e.g., VK_EXT_debug_marker, VK_EXT_debug_utils) -// are located in separate dedicated header files. - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) - -#if defined(__GNUC__) && __GNUC__ >= 4 -#define EXPORT_FUNCTION __attribute__((visibility("default"))) -#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) -#define EXPORT_FUNCTION __attribute__((visibility("default"))) -#else -#define EXPORT_FUNCTION -#endif - -static std::once_flag g_perfetto_init_flag; - - -extern "C" { - -VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, - VkInstance* pInstance) { - std::call_once(g_perfetto_init_flag, []() { InitializeDebugMarkerPerfetto(); }); - - // Get the function pointer - VkLayerInstanceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); - assert(chain_info->u.pLayerInfo != 0); - PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; - assert(fpGetInstanceProcAddr != 0); - PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); - if (fpCreateInstance == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - // Call the function and create the dispatch table - chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; - VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance); - if (result == VK_SUCCESS) { - initInstanceTable(*pInstance, fpGetInstanceProcAddr); - - // Eagerly enumerate physical devices and map them to the instance. - // This ensures we have the mapping even if the app bypasses our enumeration hooks. - PFN_vkEnumeratePhysicalDevices fpEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)fpGetInstanceProcAddr(*pInstance, "vkEnumeratePhysicalDevices"); - if (fpEnumeratePhysicalDevices) { - uint32_t count = 0; - fpEnumeratePhysicalDevices(*pInstance, &count, nullptr); - if (count > 0) { - std::vector devices(count); - fpEnumeratePhysicalDevices(*pInstance, &count, devices.data()); - for (uint32_t i = 0; i < count; ++i) { - DebugMarker::Get().SetVkInstance(devices[i], *pInstance); - } - } - } - } - - return result; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) { - if (instance_dispatch_table(instance)->EnumeratePhysicalDevices == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices); - - if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pPhysicalDevices != nullptr) { - for (uint32_t i = 0; i < *pPhysicalDeviceCount; ++i) { - DebugMarker::Get().SetVkInstance(pPhysicalDevices[i], instance); - } - } - return result; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties) { - if (instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups(instance, pPhysicalDeviceGroupCount, pPhysicalDeviceGroupProperties); - - if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pPhysicalDeviceGroupProperties != nullptr) { - for (uint32_t i = 0; i < *pPhysicalDeviceGroupCount; ++i) { - for (uint32_t j = 0; j < pPhysicalDeviceGroupProperties[i].physicalDeviceCount; ++j) { - DebugMarker::Get().SetVkInstance(pPhysicalDeviceGroupProperties[i].physicalDevices[j], instance); - } - } - } - return result; -} - -VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator) { - dispatch_key key = get_dispatch_key(instance); - instance_dispatch_table(instance)->DestroyInstance(instance, pAllocator); - destroy_instance_dispatch_table(key); -} - -VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - // Get the function pointer - VkLayerDeviceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); - assert(chain_info->u.pLayerInfo != 0); - PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; - PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; - VkInstance vk_instance = DebugMarker::Get().GetVkInstance(physicalDevice); - PFN_vkCreateDevice fpCreateDevice = (PFN_vkCreateDevice)fpGetInstanceProcAddr(vk_instance, "vkCreateDevice"); - if (fpCreateDevice == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - // Call the function and create the dispatch table - chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; - VkResult result = fpCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); - if (result == VK_SUCCESS) { - initDeviceTable(*pDevice, fpGetDeviceProcAddr); - } - - return result; -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - static const VkExtensionProperties instanceExtensions[] = { - {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, - }; - - if (pLayerName != nullptr && strcmp(pLayerName, "VK_LAYER_GOOGLE_DebugMarker") == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); - } - - return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, - VkLayerProperties* pProperties) { - static const VkLayerProperties layerProperties[] = {{ - "VK_LAYER_GOOGLE_DebugMarker", - VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), // specVersion - VK_MAKE_VERSION(0, 1, 0), // implementationVersion - "layer: DebugMarker", - }}; - - return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, - uint32_t* pPropertyCount, - VkLayerProperties* pProperties) { - static const VkLayerProperties layerProperties[] = {{ - "VK_LAYER_GOOGLE_DebugMarker", - VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), - VK_MAKE_VERSION(0, 1, 0), - "layer: DebugMarker", - }}; - - return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, - const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - static const VkExtensionProperties deviceExtensions[] = { - {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, - }; - - if (pLayerName != nullptr && strcmp(pLayerName, "VK_LAYER_GOOGLE_DebugMarker") == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); - } - - VkInstance vk_instance = DebugMarker::Get().GetVkInstance(physicalDevice); - - // Manually append device extension. This should not be necessary, but the Android vulkan - // loader does not expose extensions from implicit layer (b/143293104). - if (pProperties == nullptr) { - VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - (*pPropertyCount) += ARRAY_SIZE(deviceExtensions); - } - return res; - } - - if (*pPropertyCount > 0) { - uint32_t requestedCount = *pPropertyCount; - VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - uint32_t originalCount = *pPropertyCount; - uint32_t additionalCount = 0; - - for (uint32_t i = 0; i < ARRAY_SIZE(deviceExtensions); ++i) { - bool found = false; - for (uint32_t j = 0; j < originalCount; ++j) { - if (strcmp(pProperties[j].extensionName, deviceExtensions[i].extensionName) == 0) { - found = true; - break; - } - } - if (!found) { - if (originalCount + additionalCount < requestedCount) { - pProperties[originalCount + additionalCount] = deviceExtensions[i]; - } - additionalCount++; - } - } - *pPropertyCount = originalCount + additionalCount; - if (*pPropertyCount > requestedCount) { - *pPropertyCount = requestedCount; - } - } - return res; - } - return VK_SUCCESS; -} - - -} // extern "C" 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..b5c7216963 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 @@ -16,13 +16,14 @@ #pragma once #include -#include "vk_layer_table.h" +#include "common/dispatch_downstream.h" #include "debug_marker.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. +// can claim full support for the extension. Since the layer implements the extension itself, +// the downstream may lack it, so passthroughs skip downstream dispatch when it is unavailable. // 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 @@ -77,41 +78,30 @@ extern "C" { // Required for VK_EXT_debug_marker VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerBeginEXT(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo) { - if (device_dispatch_table(commandBuffer)->CmdDebugMarkerBeginEXT) { - device_dispatch_table(commandBuffer)->CmdDebugMarkerBeginEXT(commandBuffer, pMarkerInfo); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::CmdDebugMarkerBeginEXT>(commandBuffer, pMarkerInfo); } // Required for VK_EXT_debug_marker VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerEndEXT(VkCommandBuffer commandBuffer) { - if (device_dispatch_table(commandBuffer)->CmdDebugMarkerEndEXT) { - device_dispatch_table(commandBuffer)->CmdDebugMarkerEndEXT(commandBuffer); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::CmdDebugMarkerEndEXT>(commandBuffer); } // Required for VK_EXT_debug_marker VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerInsertEXT(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo) { - if (device_dispatch_table(commandBuffer)->CmdDebugMarkerInsertEXT) { - device_dispatch_table(commandBuffer)->CmdDebugMarkerInsertEXT(commandBuffer, pMarkerInfo); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::CmdDebugMarkerInsertEXT>(commandBuffer, pMarkerInfo); } // 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); - if (device_dispatch_table(device)->DebugMarkerSetObjectNameEXT) { - VkResult result = device_dispatch_table(device)->DebugMarkerSetObjectNameEXT(device, pNameInfo); - return result; - } - return VK_SUCCESS; + assert(pNameInfo != nullptr); + DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)getVkObjectType(pNameInfo->objectType), pNameInfo->object, + pNameInfo->pObjectName); + return layersvt::DispatchDownstreamOrSuccess<&VkuDeviceDispatchTable::DebugMarkerSetObjectNameEXT>(device, pNameInfo); } // Required for VK_EXT_debug_marker VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectTagEXT(VkDevice device, const VkDebugMarkerObjectTagInfoEXT* pTagInfo) { - if (device_dispatch_table(device)->DebugMarkerSetObjectTagEXT) { - return device_dispatch_table(device)->DebugMarkerSetObjectTagEXT(device, pTagInfo); - } - return VK_SUCCESS; + return layersvt::DispatchDownstreamOrSuccess<&VkuDeviceDispatchTable::DebugMarkerSetObjectTagEXT>(device, pTagInfo); } -} // extern "C" +} // extern "C" 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..76e0813e20 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 @@ -16,7 +16,7 @@ #pragma once #include -#include "vk_layer_table.h" +#include "common/dispatch_downstream.h" #include "debug_marker.h" extern "C" { @@ -24,88 +24,79 @@ extern "C" { // This file contains handwritten functions for the VK_EXT_debug_utils extension. // We only actively implement vkSetDebugUtilsObjectNameEXT 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. +// can claim full support for the extension. Since the layer implements the extension itself, +// the downstream may lack it, so passthroughs skip downstream dispatch when it is unavailable. // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkCmdBeginDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo) { - if (device_dispatch_table(commandBuffer)->CmdBeginDebugUtilsLabelEXT) { - device_dispatch_table(commandBuffer)->CmdBeginDebugUtilsLabelEXT(commandBuffer, pLabelInfo); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::CmdBeginDebugUtilsLabelEXT>(commandBuffer, pLabelInfo); } // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkCmdEndDebugUtilsLabelEXT(VkCommandBuffer commandBuffer) { - if (device_dispatch_table(commandBuffer)->CmdEndDebugUtilsLabelEXT) { - device_dispatch_table(commandBuffer)->CmdEndDebugUtilsLabelEXT(commandBuffer); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::CmdEndDebugUtilsLabelEXT>(commandBuffer); } // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkCmdInsertDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo) { - if (device_dispatch_table(commandBuffer)->CmdInsertDebugUtilsLabelEXT) { - device_dispatch_table(commandBuffer)->CmdInsertDebugUtilsLabelEXT(commandBuffer, pLabelInfo); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::CmdInsertDebugUtilsLabelEXT>(commandBuffer, pLabelInfo); } // 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); - if (device_dispatch_table(device)->SetDebugUtilsObjectNameEXT) { - VkResult result = device_dispatch_table(device)->SetDebugUtilsObjectNameEXT(device, pNameInfo); - return result; - } - return VK_SUCCESS; + assert(pNameInfo != nullptr); + DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)pNameInfo->objectType, pNameInfo->objectHandle, + pNameInfo->pObjectName); + return layersvt::DispatchDownstreamOrSuccess<&VkuDeviceDispatchTable::SetDebugUtilsObjectNameEXT>(device, pNameInfo); } // Required for VK_EXT_debug_utils VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectTagEXT(VkDevice device, const VkDebugUtilsObjectTagInfoEXT* pTagInfo) { - if (device_dispatch_table(device)->SetDebugUtilsObjectTagEXT) { - return device_dispatch_table(device)->SetDebugUtilsObjectTagEXT(device, pTagInfo); - } - return VK_SUCCESS; + return layersvt::DispatchDownstreamOrSuccess<&VkuDeviceDispatchTable::SetDebugUtilsObjectTagEXT>(device, pTagInfo); } // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkQueueBeginDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo) { - if (device_dispatch_table(queue)->QueueBeginDebugUtilsLabelEXT) { - device_dispatch_table(queue)->QueueBeginDebugUtilsLabelEXT(queue, pLabelInfo); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::QueueBeginDebugUtilsLabelEXT>(queue, pLabelInfo); } // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkQueueEndDebugUtilsLabelEXT(VkQueue queue) { - if (device_dispatch_table(queue)->QueueEndDebugUtilsLabelEXT) { - device_dispatch_table(queue)->QueueEndDebugUtilsLabelEXT(queue); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::QueueEndDebugUtilsLabelEXT>(queue); } // Passthrough required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkQueueInsertDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo) { - if (device_dispatch_table(queue)->QueueInsertDebugUtilsLabelEXT) { - device_dispatch_table(queue)->QueueInsertDebugUtilsLabelEXT(queue, pLabelInfo); - } + layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::QueueInsertDebugUtilsLabelEXT>(queue, pLabelInfo); } -// Passthrough required for VK_EXT_debug_utils -VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger) { - if (instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT) { - return instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT(instance, pCreateInfo, pAllocator, pMessenger); - } - return VK_SUCCESS; +// Passthrough required for VK_EXT_debug_utils. If the downstream lacks the extension, no messenger +// is created and VK_NULL_HANDLE is returned so that the output handle is never left uninitialized. +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pMessenger) { + return layersvt::DispatchDownstreamOr<&VkuInstanceDispatchTable::CreateDebugUtilsMessengerEXT>( + [pMessenger] { + *pMessenger = VK_NULL_HANDLE; + return VK_SUCCESS; + }, + instance, pCreateInfo, pAllocator, pMessenger); } // Passthrough required for VK_EXT_debug_utils -VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator) { - if (instance_dispatch_table(instance)->DestroyDebugUtilsMessengerEXT) { - instance_dispatch_table(instance)->DestroyDebugUtilsMessengerEXT(instance, messenger, pAllocator); - } +VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger, + const VkAllocationCallbacks* pAllocator) { + layersvt::DispatchDownstreamIfAvailable<&VkuInstanceDispatchTable::DestroyDebugUtilsMessengerEXT>(instance, messenger, + pAllocator); } // Passthrough required for VK_EXT_debug_utils -VKAPI_ATTR void VKAPI_CALL vkSubmitDebugUtilsMessageEXT(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData) { - if (instance_dispatch_table(instance)->SubmitDebugUtilsMessageEXT) { - instance_dispatch_table(instance)->SubmitDebugUtilsMessageEXT(instance, messageSeverity, messageTypes, pCallbackData); - } +VKAPI_ATTR void VKAPI_CALL vkSubmitDebugUtilsMessageEXT(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData) { + layersvt::DispatchDownstreamIfAvailable<&VkuInstanceDispatchTable::SubmitDebugUtilsMessageEXT>(instance, messageSeverity, + messageTypes, pCallbackData); } -} // extern "C" +} // extern "C" diff --git a/layersvt/debug_marker/debug_marker_instance.cpp b/layersvt/debug_marker/debug_marker_instance.cpp new file mode 100644 index 0000000000..fd70255668 --- /dev/null +++ b/layersvt/debug_marker/debug_marker_instance.cpp @@ -0,0 +1,22 @@ +/* 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 "debug_marker.h" + +// The layer singleton lives in its own translation unit so that unit tests can compile +// debug_marker.cpp without it and manage the active instance via layer_test::ResetLayer. +namespace { +DebugMarker g_layer; +} // namespace diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index 99fa92c24e..f2324442c0 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -37,6 +37,10 @@ function(LayerTest NAME) if (${NAME} STREQUAL "DebugMarker") target_sources(${TEST_NAME} PRIVATE ../debug_marker/debug_marker.cpp ../debug_marker/debug_marker_perfetto.cpp ../perfetto/perfetto.cc) target_include_directories(${TEST_NAME} PRIVATE .. ../debug_marker) + target_link_libraries(${TEST_NAME} layersvt_common) + if (NOT MSVC) + set_source_files_properties(../perfetto/perfetto.cc PROPERTIES COMPILE_OPTIONS "-Wno-deprecated-declarations") + endif() 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_include_directories(${TEST_NAME} PRIVATE .. ../device_memory_report) diff --git a/layersvt/test/test_debugmarker.cpp b/layersvt/test/test_debugmarker.cpp index e1f2325d2f..cac7938645 100644 --- a/layersvt/test/test_debugmarker.cpp +++ b/layersvt/test/test_debugmarker.cpp @@ -19,20 +19,27 @@ #include #include +#include "test/common/layer_base_test_peer.h" + static const char* kLayerName = "VK_LAYER_GOOGLE_DebugMarker"; class DebugMarkerTests : public VkTestFramework { public: - ~DebugMarkerTests(){}; + ~DebugMarkerTests() {}; static void SetUpTestSuite() {} - static void TearDownTestSuite(){}; + static void TearDownTestSuite() {}; + + protected: + void SetUp() override { + VkTestFramework::SetUp(); + layer_test::ResetLayer(); + } }; TEST_F(DebugMarkerTests, CombinedTest) { TEST_DESCRIPTION("Combined test for DebugMarker layer"); - DebugMarker::Get().Clear(); layer_test::VulkanInstanceBuilder inst_builder; inst_builder.AddExtension("VK_EXT_debug_utils"); VkResult err = inst_builder.Init(kLayerName); @@ -58,7 +65,157 @@ TEST_F(DebugMarkerTests, CombinedTest) { 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")); - // 3. Clear - DebugMarker::Get().Clear(); + // 3. Reset + layer_test::ResetLayer(); EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed")); } + +TEST_F(DebugMarkerTests, ManifestTest) { + TEST_DESCRIPTION("Verify DebugMarker LayerManifest properties and extension enumeration via LayerBase"); + + // Test EnumerateInstanceLayerProperties + uint32_t property_count = 0; + VkResult result = layersvt::LayerBaseTestPeer::EnumerateInstanceLayerProperties(&property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + VkLayerProperties layer_properties{}; + result = layersvt::LayerBaseTestPeer::EnumerateInstanceLayerProperties(&property_count, &layer_properties); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(layer_properties.layerName, kLayerName); + EXPECT_STREQ(layer_properties.description, "layer: DebugMarker"); + + // Test EnumerateDeviceLayerProperties + property_count = 0; + result = layersvt::LayerBaseTestPeer::EnumerateDeviceLayerProperties(VK_NULL_HANDLE, &property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + // Test EnumerateInstanceExtensionProperties + property_count = 0; + result = layersvt::LayerBaseTestPeer::EnumerateInstanceExtensionProperties(kLayerName, &property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + std::vector extensions(property_count); + result = layersvt::LayerBaseTestPeer::EnumerateInstanceExtensionProperties(kLayerName, &property_count, extensions.data()); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(extensions[0].extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + // Query non-matching layer name returns VK_ERROR_LAYER_NOT_PRESENT + property_count = 0; + result = layersvt::LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_NONEXISTENT", &property_count, nullptr); + EXPECT_EQ(result, VK_ERROR_LAYER_NOT_PRESENT); + + // Test EnumerateDeviceExtensionProperties + property_count = 0; + result = layersvt::LayerBaseTestPeer::EnumerateDeviceExtensionProperties(VK_NULL_HANDLE, kLayerName, &property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + extensions.resize(property_count); + result = layersvt::LayerBaseTestPeer::EnumerateDeviceExtensionProperties(VK_NULL_HANDLE, kLayerName, &property_count, + extensions.data()); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(extensions[0].extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME); +} + +TEST_F(DebugMarkerTests, LayerBaseLifecycleAndTrackerTest) { + TEST_DESCRIPTION("Verify DebugMarker LayerBase inheritance and DispatchTableManager integration"); + + // A physical device shares the dispatch key of its parent instance. + void* mock_dispatch_key = reinterpret_cast(0x1000); + void* mock_instance_object = mock_dispatch_key; + void* mock_physical_device_object = mock_dispatch_key; + VkInstance mock_instance = reinterpret_cast(&mock_instance_object); + VkPhysicalDevice mock_physical_device = reinterpret_cast(&mock_physical_device_object); + + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()) + .InitInstanceTable(mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetVkInstance(mock_physical_device), mock_instance); + + layer_test::ResetLayer(); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetVkInstance(mock_physical_device), VK_NULL_HANDLE); +} + +TEST_F(DebugMarkerTests, TemplateMethodDispatchTest) { + TEST_DESCRIPTION("Verify DebugMarker layer-specific hooks and LayerBase template method dispatching"); + + // Layer-specific instance commands intercepted + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkDestroyDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkSubmitDebugUtilsMessageEXT"), nullptr); + + // Common lifecycle instance commands resolved via LayerBase fallback + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateInstance"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkDestroyInstance"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDevice"), nullptr); + + // Layer-specific device commands intercepted + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkCmdBeginDebugUtilsLabelEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkSetDebugUtilsObjectNameEXT"), nullptr); + + // Common lifecycle device commands resolved via LayerBase fallback + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkCreateDevice"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkDestroyDevice"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkGetDeviceProcAddr"), nullptr); + + // Global commands resolvable with VK_NULL_HANDLE via GetInstanceProcAddr + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"), nullptr); + + // Non-global commands return nullptr when passed VK_NULL_HANDLE + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkCmdBeginDebugUtilsLabelEXT"), nullptr); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkNonExistentCmd"), nullptr); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkNonExistentCmd"), nullptr); + + // Non-global commands resolvable with valid instance/device handles + VkInstance mock_instance = reinterpret_cast(0x1234); + VkDevice mock_device = reinterpret_cast(0x5678); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkCmdBeginDebugUtilsLabelEXT"), nullptr); +} + +TEST_F(DebugMarkerTests, MissingDownstreamExtensionTest) { + TEST_DESCRIPTION("Verify debug marker and debug utils hooks do not crash when the downstream lacks the extensions"); + + // Dispatchable handles of a device share the device's dispatch key. + void* mock_dispatch_key = reinterpret_cast(0x3000); + void* mock_device_object = mock_dispatch_key; + void* mock_command_buffer_object = mock_dispatch_key; + VkDevice mock_device = reinterpret_cast(&mock_device_object); + VkCommandBuffer mock_command_buffer = reinterpret_cast(&mock_command_buffer_object); + + // The downstream device exposes no commands at all. + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()) + .InitDeviceTable(mock_device, [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }); + + auto cmd_begin = reinterpret_cast( + layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkCmdDebugMarkerBeginEXT")); + auto cmd_end = + reinterpret_cast(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkCmdDebugMarkerEndEXT")); + auto set_object_name = reinterpret_cast( + layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkSetDebugUtilsObjectNameEXT")); + ASSERT_NE(cmd_begin, nullptr); + ASSERT_NE(cmd_end, nullptr); + ASSERT_NE(set_object_name, nullptr); + + VkDebugMarkerMarkerInfoEXT marker_info{VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT}; + marker_info.pMarkerName = "Marker"; + cmd_begin(mock_command_buffer, &marker_info); + cmd_end(mock_command_buffer); + + VkDebugUtilsObjectNameInfoEXT name_info{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT}; + name_info.objectType = VK_OBJECT_TYPE_BUFFER; + name_info.objectHandle = 0x4444; + name_info.pObjectName = "Buffer"; + EXPECT_EQ(set_object_name(mock_device, &name_info), VK_SUCCESS); + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x4444, "Buffer")); +} From 5d5445e4bb316a3985051f7355461339e0141db2 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Wed, 9 Sep 2026 19:04:54 +0100 Subject: [PATCH 2/3] layersvt: Clean up debug objects on device destruction in DebugMarker Override PreDestroyDevice in DebugMarker to remove tracked object names associated with a destroyed VkDevice, preventing unbounded memory growth when devices are repeatedly created and destroyed. Add PreDestroyDeviceCleanupTest in test_debugmarker.cpp to verify that tracked objects for destroyed devices are cleaned up while preserving objects on remaining active devices. Bug: Test: new tests - DebugMarkerTests#PreDestroyDeviceCleanupTest Change-Id: I9a3624e7bf3fd05d6447adb46df887dd6a6a6964 --- layersvt/debug_marker/debug_marker.cpp | 13 ++++++++++ layersvt/debug_marker/debug_marker.h | 8 +++--- layersvt/test/test_debugmarker.cpp | 36 ++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/layersvt/debug_marker/debug_marker.cpp b/layersvt/debug_marker/debug_marker.cpp index 24c1520f08..4d87a34c13 100644 --- a/layersvt/debug_marker/debug_marker.cpp +++ b/layersvt/debug_marker/debug_marker.cpp @@ -48,6 +48,19 @@ void DebugMarker::PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkA std::call_once(perfetto_initialization_flag, []() { InitializeDebugMarkerPerfetto(); }); } +void DebugMarker::PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { + (void)pAllocator; + std::lock_guard lock(mutex_); + uint64_t dev_handle = (uint64_t)device; + for (auto it = debug_object_names_.begin(); it != debug_object_names_.end();) { + if (it->second.vk_device == dev_handle) { + it = debug_object_names_.erase(it); + } else { + ++it; + } + } +} + void DebugMarker::SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name) { std::lock_guard lock(mutex_); diff --git a/layersvt/debug_marker/debug_marker.h b/layersvt/debug_marker/debug_marker.h index 954b88d1c8..413b68b551 100644 --- a/layersvt/debug_marker/debug_marker.h +++ b/layersvt/debug_marker/debug_marker.h @@ -47,9 +47,6 @@ * 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. - * 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, inherits from LayerBase, and provides thread-safe access to its state. */ @@ -89,6 +86,11 @@ class DebugMarker : public layersvt::LayerBase { */ void PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) override; + /** + * Lifecycle hook called before vkDestroyDevice to remove tracked names for destroyed objects. + */ + void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) override; + const layersvt::LayerManifest* GetLayerManifest() const override; // VK_EXT_debug_marker and VK_EXT_debug_utils are implemented by this layer, so their commands // are returned unconditionally; the hooks tolerate a downstream that lacks the extension. diff --git a/layersvt/test/test_debugmarker.cpp b/layersvt/test/test_debugmarker.cpp index cac7938645..321c00a50c 100644 --- a/layersvt/test/test_debugmarker.cpp +++ b/layersvt/test/test_debugmarker.cpp @@ -138,6 +138,42 @@ TEST_F(DebugMarkerTests, LayerBaseLifecycleAndTrackerTest) { EXPECT_EQ(layersvt::LayerBaseTestPeer::GetVkInstance(mock_physical_device), VK_NULL_HANDLE); } +TEST_F(DebugMarkerTests, PreDestroyDeviceCleanupTest) { + TEST_DESCRIPTION("Verify that DestroyDevice cleans up tracked objects associated with that device"); + + layer_test::ResetLayer(); + + void* mock_dev1_vtable = reinterpret_cast(0x1000); + VkDevice dev1 = reinterpret_cast(&mock_dev1_vtable); + void* mock_dev2_vtable = reinterpret_cast(0x2000); + VkDevice dev2 = reinterpret_cast(&mock_dev2_vtable); + + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()) + .InitDeviceTable(dev1, [](VkDevice, const char*) -> PFN_vkVoidFunction { + return reinterpret_cast(+[](VkDevice, const VkAllocationCallbacks*) {}); + }); + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()) + .InitDeviceTable(dev2, [](VkDevice, const char*) -> PFN_vkVoidFunction { + return reinterpret_cast(+[](VkDevice, const VkAllocationCallbacks*) {}); + }); + + DebugMarker::Get().SetDebugObjectName((uint64_t)dev1, VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1"); + DebugMarker::Get().SetDebugObjectName((uint64_t)dev2, VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2"); + + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1")); + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); + + // Destroy dev1 - should remove Buffer1 but keep Buffer2 + layersvt::LayerBaseTestPeer::DestroyDevice(dev1, nullptr); + + EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1")); + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); + + // Destroy dev2 - should remove Buffer2 + layersvt::LayerBaseTestPeer::DestroyDevice(dev2, nullptr); + EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); +} + TEST_F(DebugMarkerTests, TemplateMethodDispatchTest) { TEST_DESCRIPTION("Verify DebugMarker layer-specific hooks and LayerBase template method dispatching"); From ec104679f7ceb26f8397541bc5ecafb92ae315f8 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Tue, 6 Oct 2026 17:01:58 +0100 Subject: [PATCH 3/3] layersvt: Emulate debug utils messengers in DebugMarker DebugMarker advertises VK_EXT_debug_utils, so its messenger commands must work even when the downstream lacks the extension. Previously vkCreateDebugUtilsMessengerEXT returned VK_SUCCESS with a VK_NULL_HANDLE messenger, which violates the spec. When the instance's downstream exposes the messenger commands, they are still forwarded unchanged. Otherwise DebugMarker now emulates them: create stores the messenger parameters under a unique non-null handle, destroy removes them, and vkSubmitDebugUtilsMessageEXT invokes every callback whose severity and type masks match. Remaining emulated messengers are released in PreDestroyInstance. Bug: Test: new tests - DebugMarkerTests#EmulatedDebugUtilsMessengerTest, DebugMarkerTests#ForwardedDebugUtilsMessengerTest Change-Id: I3994aab14514096754541be3c718db8e6a6a6964 --- layersvt/debug_marker/debug_marker.cpp | 52 ++++- layersvt/debug_marker/debug_marker.h | 29 +++ ...handwritten_functions_vk_ext_debug_utils.h | 24 +-- layersvt/test/test_debugmarker.cpp | 179 +++++++++++++++++- 4 files changed, 269 insertions(+), 15 deletions(-) diff --git a/layersvt/debug_marker/debug_marker.cpp b/layersvt/debug_marker/debug_marker.cpp index 4d87a34c13..11bf435ad2 100644 --- a/layersvt/debug_marker/debug_marker.cpp +++ b/layersvt/debug_marker/debug_marker.cpp @@ -48,6 +48,18 @@ void DebugMarker::PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkA std::call_once(perfetto_initialization_flag, []() { InitializeDebugMarkerPerfetto(); }); } +void DebugMarker::PreDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator) { + (void)pAllocator; + std::lock_guard lock(mutex_); + for (auto it = emulated_messengers_.begin(); it != emulated_messengers_.end();) { + if (it->second.instance == instance) { + it = emulated_messengers_.erase(it); + } else { + ++it; + } + } +} + void DebugMarker::PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { (void)pAllocator; std::lock_guard lock(mutex_); @@ -106,9 +118,46 @@ bool DebugMarker::HasDebugObjectName(int32_t type, uint64_t handle, const std::s return it->second.name == name; } +VkResult DebugMarker::CreateEmulatedMessenger(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + VkDebugUtilsMessengerEXT* pMessenger) { + std::lock_guard lock(mutex_); + uint64_t handle = next_emulated_messenger_++; + EmulatedMessenger& messenger = emulated_messengers_[handle]; + messenger.instance = instance; + messenger.create_info = *pCreateInfo; + // The pNext chain is owned by the application and is not used by the emulation. + messenger.create_info.pNext = nullptr; + *pMessenger = (VkDebugUtilsMessengerEXT)handle; + return VK_SUCCESS; +} + +void DebugMarker::DestroyEmulatedMessenger(VkDebugUtilsMessengerEXT messenger) { + std::lock_guard lock(mutex_); + emulated_messengers_.erase((uint64_t)messenger); +} + +void DebugMarker::SubmitEmulatedMessage(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData) { + std::vector matching; + { + std::lock_guard lock(mutex_); + for (const auto& entry : emulated_messengers_) { + const VkDebugUtilsMessengerCreateInfoEXT& info = entry.second.create_info; + if (entry.second.instance == instance && (info.messageSeverity & messageSeverity) != 0 && + (info.messageType & messageTypes) != 0) { + matching.push_back(info); + } + } + } + // Invoke callbacks without holding the lock so that they may call back into the layer. + for (const auto& info : matching) { + info.pfnUserCallback(messageSeverity, messageTypes, pCallbackData, info.pUserData); + } +} + PFN_vkVoidFunction DebugMarker::GetLayerInstanceCommand(VkInstance instance, const char* name) { (void)instance; - if (!name) return nullptr; if (strcmp(name, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); if (strcmp(name, "vkDestroyDebugUtilsMessengerEXT") == 0) @@ -120,7 +169,6 @@ PFN_vkVoidFunction DebugMarker::GetLayerInstanceCommand(VkInstance instance, con PFN_vkVoidFunction DebugMarker::GetLayerDeviceCommand(VkDevice device, const char* name) { (void)device; - if (!name) return nullptr; // VK_EXT_debug_marker if (strcmp(name, "vkCmdDebugMarkerBeginEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerBeginEXT); diff --git a/layersvt/debug_marker/debug_marker.h b/layersvt/debug_marker/debug_marker.h index 413b68b551..05df9ce890 100644 --- a/layersvt/debug_marker/debug_marker.h +++ b/layersvt/debug_marker/debug_marker.h @@ -21,6 +21,7 @@ #include #include +#include #include "common/layer_base.h" #include "common/layer_manifest.h" @@ -80,12 +81,28 @@ class DebugMarker : public layersvt::LayerBase { */ bool HasDebugObjectName(int32_t type, uint64_t handle, const std::string& name); + /** + * @brief Emulated VK_EXT_debug_utils messengers, used for instances whose downstream lacks the + * extension so that the commands advertised by this layer remain functional. + */ + VkResult CreateEmulatedMessenger(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + VkDebugUtilsMessengerEXT* pMessenger); + void DestroyEmulatedMessenger(VkDebugUtilsMessengerEXT messenger); + void SubmitEmulatedMessage(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData); + protected: /** * Lifecycle hook called before vkCreateInstance. */ void PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) override; + /** + * Lifecycle hook called before vkDestroyInstance to remove the instance's emulated messengers. + */ + void PreDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator) override; + /** * Lifecycle hook called before vkDestroyDevice to remove tracked names for destroyed objects. */ @@ -115,4 +132,16 @@ class DebugMarker : public layersvt::LayerBase { * 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_; + + struct EmulatedMessenger { + VkInstance instance; + VkDebugUtilsMessengerCreateInfoEXT create_info; + }; + + /** + * @brief Maps an emulated messenger handle to its owning instance and creation parameters. + * Handles are unique non-zero counter values. + */ + std::map emulated_messengers_; + uint64_t next_emulated_messenger_ = 1; }; 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 76e0813e20..c4d3b57c83 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 @@ -26,6 +26,7 @@ extern "C" { // All other functions are simple passthroughs required to be provided so that the layer // can claim full support for the extension. Since the layer implements the extension itself, // the downstream may lack it, so passthroughs skip downstream dispatch when it is unavailable. +// Messenger commands are forwarded when the downstream supports them and emulated by the layer otherwise. // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkCmdBeginDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo) { @@ -70,33 +71,34 @@ VKAPI_ATTR void VKAPI_CALL vkQueueInsertDebugUtilsLabelEXT(VkQueue queue, const layersvt::DispatchDownstreamIfAvailable<&VkuDeviceDispatchTable::QueueInsertDebugUtilsLabelEXT>(queue, pLabelInfo); } -// Passthrough required for VK_EXT_debug_utils. If the downstream lacks the extension, no messenger -// is created and VK_NULL_HANDLE is returned so that the output handle is never left uninitialized. +// Required for VK_EXT_debug_utils. If the downstream lacks the extension, the messenger is emulated. VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger) { return layersvt::DispatchDownstreamOr<&VkuInstanceDispatchTable::CreateDebugUtilsMessengerEXT>( - [pMessenger] { - *pMessenger = VK_NULL_HANDLE; - return VK_SUCCESS; + [instance, pCreateInfo, pMessenger] { + return DebugMarker::Get().CreateEmulatedMessenger(instance, pCreateInfo, pMessenger); }, instance, pCreateInfo, pAllocator, pMessenger); } -// Passthrough required for VK_EXT_debug_utils +// Required for VK_EXT_debug_utils. If the downstream lacks the extension, the messenger is emulated. VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator) { - layersvt::DispatchDownstreamIfAvailable<&VkuInstanceDispatchTable::DestroyDebugUtilsMessengerEXT>(instance, messenger, - pAllocator); + layersvt::DispatchDownstreamOr<&VkuInstanceDispatchTable::DestroyDebugUtilsMessengerEXT>( + [messenger] { DebugMarker::Get().DestroyEmulatedMessenger(messenger); }, instance, messenger, pAllocator); } -// Passthrough required for VK_EXT_debug_utils +// Required for VK_EXT_debug_utils. If the downstream lacks the extension, emulated messengers are invoked. VKAPI_ATTR void VKAPI_CALL vkSubmitDebugUtilsMessageEXT(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData) { - layersvt::DispatchDownstreamIfAvailable<&VkuInstanceDispatchTable::SubmitDebugUtilsMessageEXT>(instance, messageSeverity, - messageTypes, pCallbackData); + layersvt::DispatchDownstreamOr<&VkuInstanceDispatchTable::SubmitDebugUtilsMessageEXT>( + [instance, messageSeverity, messageTypes, pCallbackData] { + DebugMarker::Get().SubmitEmulatedMessage(instance, messageSeverity, messageTypes, pCallbackData); + }, + instance, messageSeverity, messageTypes, pCallbackData); } } // extern "C" diff --git a/layersvt/test/test_debugmarker.cpp b/layersvt/test/test_debugmarker.cpp index 321c00a50c..544fa7fc53 100644 --- a/layersvt/test/test_debugmarker.cpp +++ b/layersvt/test/test_debugmarker.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "test/common/layer_base_test_peer.h" @@ -141,8 +142,6 @@ TEST_F(DebugMarkerTests, LayerBaseLifecycleAndTrackerTest) { TEST_F(DebugMarkerTests, PreDestroyDeviceCleanupTest) { TEST_DESCRIPTION("Verify that DestroyDevice cleans up tracked objects associated with that device"); - layer_test::ResetLayer(); - void* mock_dev1_vtable = reinterpret_cast(0x1000); VkDevice dev1 = reinterpret_cast(&mock_dev1_vtable); void* mock_dev2_vtable = reinterpret_cast(0x2000); @@ -255,3 +254,179 @@ TEST_F(DebugMarkerTests, MissingDownstreamExtensionTest) { EXPECT_EQ(set_object_name(mock_device, &name_info), VK_SUCCESS); EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x4444, "Buffer")); } + +namespace { + +struct MessengerCallbackLog { + uint32_t call_count = 0; + VkDebugUtilsMessageSeverityFlagBitsEXT last_severity = static_cast(0); +}; + +VKAPI_ATTR VkBool32 VKAPI_CALL LogMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT, const VkDebugUtilsMessengerCallbackDataEXT*, + void* pUserData) { + auto* log = static_cast(pUserData); + ++log->call_count; + log->last_severity = messageSeverity; + return VK_FALSE; +} + +VkDebugUtilsMessengerCreateInfoEXT MakeMessengerCreateInfo(VkDebugUtilsMessageSeverityFlagsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, MessengerCallbackLog* log) { + VkDebugUtilsMessengerCreateInfoEXT create_info{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT}; + create_info.messageSeverity = severity; + create_info.messageType = type; + create_info.pfnUserCallback = LogMessengerCallback; + create_info.pUserData = log; + return create_info; +} + +struct DebugUtilsMessengerCommands { + PFN_vkCreateDebugUtilsMessengerEXT create; + PFN_vkDestroyDebugUtilsMessengerEXT destroy; + PFN_vkSubmitDebugUtilsMessageEXT submit; +}; + +DebugUtilsMessengerCommands GetDebugUtilsMessengerCommands() { + return {reinterpret_cast( + layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDebugUtilsMessengerEXT")), + reinterpret_cast( + layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkDestroyDebugUtilsMessengerEXT")), + reinterpret_cast( + layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkSubmitDebugUtilsMessageEXT"))}; +} + +} // namespace + +TEST_F(DebugMarkerTests, EmulatedDebugUtilsMessengerTest) { + TEST_DESCRIPTION("Verify debug utils messengers are emulated when the downstream lacks VK_EXT_debug_utils"); + + void* mock_instance_object = reinterpret_cast(0x5000); + VkInstance mock_instance = reinterpret_cast(&mock_instance_object); + + // The downstream instance only exposes vkDestroyInstance. + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()) + .InitInstanceTable(mock_instance, [](VkInstance, const char* name) -> PFN_vkVoidFunction { + if (strcmp(name, "vkDestroyInstance") == 0) { + return reinterpret_cast(+[](VkInstance, const VkAllocationCallbacks*) {}); + } + return nullptr; + }); + + DebugUtilsMessengerCommands commands = GetDebugUtilsMessengerCommands(); + ASSERT_NE(commands.create, nullptr); + ASSERT_NE(commands.destroy, nullptr); + ASSERT_NE(commands.submit, nullptr); + + MessengerCallbackLog error_log; + MessengerCallbackLog verbose_log; + VkDebugUtilsMessengerCreateInfoEXT error_info = MakeMessengerCreateInfo( + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, &error_log); + VkDebugUtilsMessengerCreateInfoEXT verbose_info = MakeMessengerCreateInfo( + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, &verbose_log); + + VkDebugUtilsMessengerEXT error_messenger = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT verbose_messenger = VK_NULL_HANDLE; + EXPECT_EQ(commands.create(mock_instance, &error_info, nullptr, &error_messenger), VK_SUCCESS); + EXPECT_EQ(commands.create(mock_instance, &verbose_info, nullptr, &verbose_messenger), VK_SUCCESS); + EXPECT_NE(error_messenger, VK_NULL_HANDLE); + EXPECT_NE(verbose_messenger, VK_NULL_HANDLE); + EXPECT_NE(error_messenger, verbose_messenger); + + // Only the messenger whose severity and type masks match is invoked. + VkDebugUtilsMessengerCallbackDataEXT callback_data{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT}; + callback_data.pMessage = "message"; + commands.submit(mock_instance, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + &callback_data); + EXPECT_EQ(error_log.call_count, 1u); + EXPECT_EQ(error_log.last_severity, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT); + EXPECT_EQ(verbose_log.call_count, 0u); + + // Matching severity with a non-matching type does not invoke the messenger. + commands.submit(mock_instance, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, + &callback_data); + EXPECT_EQ(error_log.call_count, 1u); + EXPECT_EQ(verbose_log.call_count, 0u); + + // A destroyed messenger is no longer invoked; destroying VK_NULL_HANDLE is a no-op. + commands.destroy(mock_instance, error_messenger, nullptr); + commands.destroy(mock_instance, VK_NULL_HANDLE, nullptr); + commands.submit(mock_instance, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + &callback_data); + EXPECT_EQ(error_log.call_count, 1u); + + commands.submit(mock_instance, VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, + &callback_data); + EXPECT_EQ(verbose_log.call_count, 1u); + + // Destroying the instance removes its remaining emulated messengers. + layersvt::LayerBaseTestPeer::DestroyInstance(mock_instance, nullptr); + commands.submit(mock_instance, VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, + &callback_data); + EXPECT_EQ(verbose_log.call_count, 1u); +} + +namespace { + +uint32_t downstream_create_messenger_calls = 0; +uint32_t downstream_destroy_messenger_calls = 0; +uint32_t downstream_submit_message_calls = 0; +const VkDebugUtilsMessengerEXT kDownstreamMessenger = (VkDebugUtilsMessengerEXT)0xABCDu; + +} // namespace + +TEST_F(DebugMarkerTests, ForwardedDebugUtilsMessengerTest) { + TEST_DESCRIPTION("Verify debug utils messenger commands are forwarded when the downstream supports VK_EXT_debug_utils"); + + downstream_create_messenger_calls = 0; + downstream_destroy_messenger_calls = 0; + downstream_submit_message_calls = 0; + + void* mock_instance_object = reinterpret_cast(0x6000); + VkInstance mock_instance = reinterpret_cast(&mock_instance_object); + + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()) + .InitInstanceTable(mock_instance, [](VkInstance, const char* name) -> PFN_vkVoidFunction { + if (strcmp(name, "vkCreateDebugUtilsMessengerEXT") == 0) { + return reinterpret_cast(+[](VkInstance, const VkDebugUtilsMessengerCreateInfoEXT*, + const VkAllocationCallbacks*, + VkDebugUtilsMessengerEXT* pMessenger) { + ++downstream_create_messenger_calls; + *pMessenger = kDownstreamMessenger; + return VK_SUCCESS; + }); + } + if (strcmp(name, "vkDestroyDebugUtilsMessengerEXT") == 0) { + return reinterpret_cast( + +[](VkInstance, VkDebugUtilsMessengerEXT, const VkAllocationCallbacks*) { + ++downstream_destroy_messenger_calls; + }); + } + if (strcmp(name, "vkSubmitDebugUtilsMessageEXT") == 0) { + return reinterpret_cast( + +[](VkInstance, VkDebugUtilsMessageSeverityFlagBitsEXT, VkDebugUtilsMessageTypeFlagsEXT, + const VkDebugUtilsMessengerCallbackDataEXT*) { ++downstream_submit_message_calls; }); + } + return nullptr; + }); + + DebugUtilsMessengerCommands commands = GetDebugUtilsMessengerCommands(); + MessengerCallbackLog log; + VkDebugUtilsMessengerCreateInfoEXT create_info = MakeMessengerCreateInfo(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, &log); + + VkDebugUtilsMessengerEXT messenger = VK_NULL_HANDLE; + EXPECT_EQ(commands.create(mock_instance, &create_info, nullptr, &messenger), VK_SUCCESS); + EXPECT_EQ(messenger, kDownstreamMessenger); + EXPECT_EQ(downstream_create_messenger_calls, 1u); + + // Messages go to the downstream; the layer does not invoke the callback itself. + VkDebugUtilsMessengerCallbackDataEXT callback_data{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT}; + commands.submit(mock_instance, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + &callback_data); + EXPECT_EQ(downstream_submit_message_calls, 1u); + EXPECT_EQ(log.call_count, 0u); + + commands.destroy(mock_instance, messenger, nullptr); + EXPECT_EQ(downstream_destroy_messenger_calls, 1u); +}