diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 92538d5e3a..edab4bf6a0 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -45,6 +45,8 @@ else() add_compile_options(-Wpointer-arith) endif() +add_subdirectory(common) + if(BUILD_APIDUMP) find_package(Python3 REQUIRED) @@ -104,6 +106,10 @@ if(BUILD_MONITOR) ) endif () +if (NOT MSVC) + set_source_files_properties(perfetto/perfetto.cc PROPERTIES COMPILE_OPTIONS "-Wno-deprecated-declarations") +endif() + if(BUILD_SCREENSHOT) add_library(VkLayer_screenshot MODULE) set_target_properties(VkLayer_screenshot PROPERTIES FOLDER "layers/screenshot") @@ -168,7 +174,6 @@ if(BUILD_DEBUGMARKER) 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 @@ -176,9 +181,6 @@ if(BUILD_DEBUGMARKER) debug_marker/debug_marker_perfetto.h debug_marker/debug_marker_perfetto.cpp perfetto/perfetto.cc - vk_layer_table.cpp - vk_layer_table.h - layer_keep_alive.cpp debug_marker/VkLayer_DebugMarker.json.in ) @@ -188,6 +190,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/common/CMakeLists.txt b/layersvt/common/CMakeLists.txt new file mode 100644 index 0000000000..740cd201e6 --- /dev/null +++ b/layersvt/common/CMakeLists.txt @@ -0,0 +1,47 @@ +# 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. + +add_library(layersvt_common OBJECT + device_instance_tracker.h + device_instance_tracker.cpp + dispatch_table_manager.h + dispatch_table_manager.cpp + layer_manifest.h + layer_manifest.cpp + layer_base.h + layer_base.cpp + layer_keep_alive.h + layer_keep_alive.cpp + log.h +) + +set_target_properties(layersvt_common PROPERTIES + FOLDER "layers/common" + POSITION_INDEPENDENT_CODE ON +) + +target_include_directories(layersvt_common PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +target_link_libraries(layersvt_common PUBLIC + Vulkan::Headers + Vulkan::UtilityHeaders + Vulkan::LayerSettings +) + +if (ANDROID) + target_link_libraries(layersvt_common PUBLIC log android) +endif() diff --git a/layersvt/common/device_instance_tracker.cpp b/layersvt/common/device_instance_tracker.cpp new file mode 100644 index 0000000000..c5f8ea15c0 --- /dev/null +++ b/layersvt/common/device_instance_tracker.cpp @@ -0,0 +1,82 @@ +/* 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 "device_instance_tracker.h" +#include + +#include + +namespace layersvt { + +DeviceInstanceTracker& DeviceInstanceTracker::Get() { + static DeviceInstanceTracker instance; + return instance; +} + +void DeviceInstanceTracker::SetVkInstance(VkPhysicalDevice physical_device, VkInstance instance) { + std::unique_lock lock(mutex_); + physical_device_to_instance_map_[physical_device] = instance; +} + +VkInstance DeviceInstanceTracker::GetVkInstance(VkPhysicalDevice physical_device) const { + std::shared_lock lock(mutex_); + auto it = physical_device_to_instance_map_.find(physical_device); + if (it != physical_device_to_instance_map_.end()) { + return it->second; + } + return VK_NULL_HANDLE; +} + +void DeviceInstanceTracker::EagerMapDevices(VkInstance instance, PFN_vkGetInstanceProcAddr get_instance_proc_addr) { + if (!instance || !get_instance_proc_addr) { + return; + } + auto enumerate_physical_devices = + reinterpret_cast(get_instance_proc_addr(instance, "vkEnumeratePhysicalDevices")); + if (!enumerate_physical_devices) { + return; + } + + uint32_t count = 0; + VkResult result = enumerate_physical_devices(instance, &count, nullptr); + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && count > 0) { + std::vector devices(count); + result = enumerate_physical_devices(instance, &count, devices.data()); + if (result == VK_SUCCESS || result == VK_INCOMPLETE) { + std::unique_lock lock(mutex_); + for (uint32_t i = 0; i < count; ++i) { + physical_device_to_instance_map_[devices[i]] = instance; + } + } + } +} + +void DeviceInstanceTracker::RemoveInstance(VkInstance instance) { + std::unique_lock lock(mutex_); + for (auto it = physical_device_to_instance_map_.begin(); it != physical_device_to_instance_map_.end();) { + if (it->second == instance) { + it = physical_device_to_instance_map_.erase(it); + } else { + ++it; + } + } +} + +void DeviceInstanceTracker::Clear() { + std::unique_lock lock(mutex_); + physical_device_to_instance_map_.clear(); +} + +} // namespace layersvt diff --git a/layersvt/common/device_instance_tracker.h b/layersvt/common/device_instance_tracker.h new file mode 100644 index 0000000000..50fd909608 --- /dev/null +++ b/layersvt/common/device_instance_tracker.h @@ -0,0 +1,76 @@ +/* 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 + +namespace layersvt { + +/** + * @brief Thread-safe manager for tracking associations between Vulkan physical + * devices and their corresponding parent instances. + * + * Vulkan layers require the VkInstance handle to resolve downstream function + * pointers in vkCreateDevice via pfnNextGetInstanceProcAddr. This class provides + * synchronized, low-latency lookups using a reader-writer lock (std::shared_mutex). + */ +class DeviceInstanceTracker { + public: + static DeviceInstanceTracker& Get(); + + /** + * @brief Associates a physical device handle with its parent VkInstance. + */ + void SetVkInstance(VkPhysicalDevice physical_device, VkInstance instance); + + /** + * @brief Retrieves the VkInstance associated with a given physical device. + * @return The associated VkInstance, or VK_NULL_HANDLE if not registered. + */ + VkInstance GetVkInstance(VkPhysicalDevice physical_device) const; + + /** + * @brief Eagerly enumerates and associates all physical devices under an instance. + * + * Calling this during vkCreateInstance guarantees that all physical devices + * are mapped to their parent instance before any vkCreateDevice call, even if + * the application acquires physical devices prior to layer hooking or across threads. + */ + void EagerMapDevices(VkInstance instance, PFN_vkGetInstanceProcAddr get_instance_proc_addr); + + /** + * @brief Removes all physical device mappings associated with a specific VkInstance. + */ + void RemoveInstance(VkInstance instance); + + /** + * @brief Clears all registered mappings. + */ + void Clear(); + + private: + DeviceInstanceTracker() = default; + ~DeviceInstanceTracker() = default; + DeviceInstanceTracker(const DeviceInstanceTracker&) = delete; + DeviceInstanceTracker& operator=(const DeviceInstanceTracker&) = delete; + + mutable std::shared_mutex mutex_; + std::unordered_map physical_device_to_instance_map_; +}; + +} // namespace layersvt diff --git a/layersvt/common/dispatch_table_manager.cpp b/layersvt/common/dispatch_table_manager.cpp new file mode 100644 index 0000000000..55ca98fc33 --- /dev/null +++ b/layersvt/common/dispatch_table_manager.cpp @@ -0,0 +1,141 @@ +/* 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 "dispatch_table_manager.h" +#include +#include + +namespace layersvt { + +DispatchTableManager& DispatchTableManager::Get() { + static DispatchTableManager instance; + return instance; +} + +DispatchTableManager::~DispatchTableManager() { Clear(); } + +void DispatchTableManager::Clear() { + { + std::unique_lock lock(instance_mutex_); + instance_tables_.clear(); + } + { + std::unique_lock lock(device_mutex_); + device_tables_.clear(); + } + { + std::unique_lock lock(callback_mutex_); + loader_callbacks_.clear(); + } +} + +VkuInstanceDispatchTable* DispatchTableManager::InitInstanceTable(VkInstance instance, + PFN_vkGetInstanceProcAddr get_instance_proc_addr) { + auto table = std::make_unique(); + vkuInitInstanceDispatchTable(instance, table.get(), get_instance_proc_addr); + + DispatchKey key = GetDispatchKey(instance); + std::unique_lock lock(instance_mutex_); + auto raw_ptr = table.get(); + instance_tables_[key] = std::move(table); + return raw_ptr; +} + +VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(const void* object) const { + DispatchKey key = GetDispatchKey(object); + std::shared_lock lock(instance_mutex_); + auto it = instance_tables_.find(key); + if (it != instance_tables_.end()) { + return it->second.get(); + } + return nullptr; +} + +void DispatchTableManager::DestroyInstanceTable(DispatchKey key) { + std::unique_lock lock(instance_mutex_); + instance_tables_.erase(key); +} + +VkuDeviceDispatchTable* DispatchTableManager::InitDeviceTable(VkDevice device, PFN_vkGetDeviceProcAddr get_device_proc_addr) { + auto table = std::make_unique(); + vkuInitDeviceDispatchTable(device, table.get(), get_device_proc_addr); + + DispatchKey key = GetDispatchKey(device); + std::unique_lock lock(device_mutex_); + auto raw_ptr = table.get(); + device_tables_[key] = std::move(table); + return raw_ptr; +} + +VkuDeviceDispatchTable* DispatchTableManager::GetDeviceDispatchTable(const void* object) const { + DispatchKey key = GetDispatchKey(object); + std::shared_lock lock(device_mutex_); + auto it = device_tables_.find(key); + if (it != device_tables_.end()) { + return it->second.get(); + } + return nullptr; +} + +void DispatchTableManager::DestroyDeviceTable(DispatchKey key) { + { + std::unique_lock lock(device_mutex_); + device_tables_.erase(key); + } + { + std::unique_lock lock(callback_mutex_); + loader_callbacks_.erase(key); + } +} + +void DispatchTableManager::SetDeviceLoaderDataCallback(VkDevice device, PFN_vkSetDeviceLoaderData callback) { + DispatchKey key = GetDispatchKey(device); + std::unique_lock lock(callback_mutex_); + loader_callbacks_[key] = callback; +} + +PFN_vkSetDeviceLoaderData DispatchTableManager::GetDeviceLoaderDataCallback(VkDevice device) const { + DispatchKey key = GetDispatchKey(device); + std::shared_lock lock(callback_mutex_); + auto it = loader_callbacks_.find(key); + if (it != loader_callbacks_.end()) { + return it->second; + } + return nullptr; +} + +VkLayerInstanceCreateInfo* GetChainInfo(const VkInstanceCreateInfo* create_info, VkLayerFunction function) { + if (!create_info) { + return nullptr; + } + auto* chain_info = static_cast(create_info->pNext); + while (chain_info && (chain_info->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO || chain_info->function != function)) { + chain_info = static_cast(chain_info->pNext); + } + return const_cast(chain_info); +} + +VkLayerDeviceCreateInfo* GetChainInfo(const VkDeviceCreateInfo* create_info, VkLayerFunction function) { + if (!create_info) { + return nullptr; + } + auto* chain_info = static_cast(create_info->pNext); + while (chain_info && (chain_info->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO || chain_info->function != function)) { + chain_info = static_cast(chain_info->pNext); + } + return const_cast(chain_info); +} + +} // namespace layersvt diff --git a/layersvt/common/dispatch_table_manager.h b/layersvt/common/dispatch_table_manager.h new file mode 100644 index 0000000000..f91c9fb3be --- /dev/null +++ b/layersvt/common/dispatch_table_manager.h @@ -0,0 +1,135 @@ +/* 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 +#include +#include + +namespace layersvt { + +using DispatchKey = void*; + +/** + * @brief Computes the dispatch key for a dispatchable Vulkan handle. + * + * In the Vulkan loader architecture, dispatchable handles (VkInstance, + * VkPhysicalDevice, VkDevice, VkQueue, VkCommandBuffer) have their first + * pointer-sized word pointing to the loader's dispatch table. + */ +inline DispatchKey GetDispatchKey(const void* object) { + return (object == nullptr) ? nullptr : *reinterpret_cast(object); +} + +/** + * @brief Thread-safe manager for Vulkan instance and device dispatch tables. + * + * Provides thread-safe storage, initialization, lookup, and destruction of + * dispatch tables using reader-writer locks (std::shared_mutex). + * + * Also tracks the loader data callback (VK_LOADER_DATA_CALLBACK) to allow + * layers to initialize dispatchable objects created internally. + */ +class DispatchTableManager { + public: + static DispatchTableManager& Get(); + + // Instance dispatch tables + VkuInstanceDispatchTable* InitInstanceTable(VkInstance instance, PFN_vkGetInstanceProcAddr get_instance_proc_addr); + VkuInstanceDispatchTable* GetInstanceDispatchTable(const void* object) const; + void DestroyInstanceTable(DispatchKey key); + + // Device dispatch tables + VkuDeviceDispatchTable* InitDeviceTable(VkDevice device, PFN_vkGetDeviceProcAddr get_device_proc_addr); + VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object) const; + void DestroyDeviceTable(DispatchKey key); + + // Loader data callbacks (from VK_LOADER_DATA_CALLBACK) + void SetDeviceLoaderDataCallback(VkDevice device, PFN_vkSetDeviceLoaderData callback); + PFN_vkSetDeviceLoaderData GetDeviceLoaderDataCallback(VkDevice device) const; + + // Reset all tables (useful for tests) + void Clear(); + + private: + DispatchTableManager() = default; + ~DispatchTableManager(); + DispatchTableManager(const DispatchTableManager&) = delete; + DispatchTableManager& operator=(const DispatchTableManager&) = delete; + + mutable std::shared_mutex instance_mutex_; + std::unordered_map> instance_tables_; + + mutable std::shared_mutex device_mutex_; + std::unordered_map> device_tables_; + + mutable std::shared_mutex callback_mutex_; + std::unordered_map loader_callbacks_; +}; + +/** + * @brief Helper function to extract layer chain information from create info. + */ +VkLayerInstanceCreateInfo* GetChainInfo(const VkInstanceCreateInfo* create_info, VkLayerFunction function); +VkLayerDeviceCreateInfo* GetChainInfo(const VkDeviceCreateInfo* create_info, VkLayerFunction function); + +template +struct MemberTraits; + +template +struct MemberTraits { + using ClassType = Class; +}; + +/** + * @brief Forwards a Vulkan API call downstream using the appropriate dispatch table. + * + * Automatically deduces whether MemberPtr belongs to VkuDeviceDispatchTable or + * VkuInstanceDispatchTable, looks up the table using DispatchTableManager, checks + * for null pointers, and invokes the function. If the table or function pointer + * is null and the return type is VkResult, returns VK_SUCCESS. + */ +template +inline auto DispatchDownstream(Handle handle, Args&&... args) { + using TableType = typename MemberTraits::ClassType; + TableType* table = nullptr; + if constexpr (std::is_same_v) { + table = DispatchTableManager::Get().GetInstanceDispatchTable(handle); + } else { + table = DispatchTableManager::Get().GetDeviceDispatchTable(handle); + } + + using FnPtr = decltype(table->*MemberPtr); + using ReturnType = std::invoke_result_t; + + if (table && table->*MemberPtr) { + return (table->*MemberPtr)(handle, std::forward(args)...); + } + if constexpr (!std::is_void_v) { + if constexpr (std::is_same_v) { + return VK_SUCCESS; + } else { + return ReturnType{}; + } + } +} + +} // namespace layersvt diff --git a/layersvt/common/layer_base.cpp b/layersvt/common/layer_base.cpp new file mode 100644 index 0000000000..376f118267 --- /dev/null +++ b/layersvt/common/layer_base.cpp @@ -0,0 +1,437 @@ +/* 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 "layer_base.h" +#include "layer_keep_alive.h" +#include "device_instance_tracker.h" +#include "dispatch_table_manager.h" +#include "layer_manifest.h" + +#include +#include + +#if defined(_WIN32) +#include +#include +#endif + +extern "C" { + +static VkResult VKAPI_CALL LayerBaseCreateInstance(const VkInstanceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, + VkInstance* instance) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return layersvt::LayerBase::GetActiveLayer()->CreateInstance(create_info, allocator, instance); +} + +static void VKAPI_CALL LayerBaseDestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) { + if (layersvt::LayerBase::GetActiveLayer()) { + layersvt::LayerBase::GetActiveLayer()->DestroyInstance(instance, allocator); + } +} + +static VkResult VKAPI_CALL LayerBaseEnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return layersvt::LayerBase::GetActiveLayer()->EnumeratePhysicalDevices(instance, physical_device_count, physical_devices); +} + +static VkResult VKAPI_CALL LayerBaseEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return layersvt::LayerBase::GetActiveLayer()->EnumeratePhysicalDeviceGroups(instance, physical_device_group_count, physical_device_group_properties); +} + +static VkResult VKAPI_CALL LayerBaseCreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return layersvt::LayerBase::GetActiveLayer()->CreateDevice(physical_device, create_info, allocator, device); +} + +static void VKAPI_CALL LayerBaseDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) { + if (layersvt::LayerBase::GetActiveLayer()) { + layersvt::LayerBase::GetActiveLayer()->DestroyDevice(device, allocator); + } +} + +static VkResult VKAPI_CALL LayerBaseEnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return layersvt::LayerBase::GetActiveLayer()->EnumerateInstanceExtensionProperties(layer_name, property_count, properties); +} + +static VkResult VKAPI_CALL LayerBaseEnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return layersvt::LayerBase::GetActiveLayer()->EnumerateInstanceLayerProperties(property_count, properties); +} + +static VkResult VKAPI_CALL LayerBaseEnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, VkExtensionProperties* properties) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return layersvt::LayerBase::GetActiveLayer()->EnumerateDeviceExtensionProperties(physical_device, layer_name, property_count, properties); +} + +static VkResult VKAPI_CALL LayerBaseEnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return layersvt::LayerBase::GetActiveLayer()->EnumerateDeviceLayerProperties(physical_device, property_count, properties); +} + +static PFN_vkVoidFunction VKAPI_CALL LayerBaseGetInstanceProcAddr(VkInstance instance, const char* name) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return nullptr; + } + return layersvt::LayerBase::GetActiveLayer()->GetInstanceProcAddr(instance, name); +} + +static PFN_vkVoidFunction VKAPI_CALL LayerBaseGetDeviceProcAddr(VkDevice device, const char* name) { + if (!layersvt::LayerBase::GetActiveLayer()) { + return nullptr; + } + return layersvt::LayerBase::GetActiveLayer()->GetDeviceProcAddr(device, name); +} + +} // extern "C" + +namespace layersvt { + +LayerBase::LayerBase(const LayerManifest* manifest) : manifest_(manifest) { + active_layer_ = this; +} + +LayerBase::~LayerBase() { + if (active_layer_ == this) { + active_layer_ = nullptr; + } +} + +VkResult LayerBase::CreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, + VkInstance* instance) { +#if defined(_WIN32) && defined(_CRTDBG_MODE_FILE) +#if !defined(NDEBUG) + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); +#endif + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); +#endif + + EnsureLayerKeepAlive(); + + if (!create_info || !instance) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkInstanceCreateInfo modified_create_info = *create_info; + VkLayerInstanceCreateInfo* chain_info = GetChainInfo(&modified_create_info, VK_LAYER_LINK_INFO); + if (!chain_info || !chain_info->u.pLayerInfo || !chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + PFN_vkGetInstanceProcAddr get_instance_proc_addr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + auto create_instance = reinterpret_cast(get_instance_proc_addr(VK_NULL_HANDLE, "vkCreateInstance")); + if (create_instance == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + PreCreateInstance(&modified_create_info, allocator); + + chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; + VkResult result = create_instance(&modified_create_info, allocator, instance); + if (result == VK_SUCCESS) { + DispatchTableManager::Get().InitInstanceTable(*instance, get_instance_proc_addr); + DeviceInstanceTracker::Get().EagerMapDevices(*instance, get_instance_proc_addr); + PostCreateInstance(*instance, &modified_create_info); + } + return result; +} + +void LayerBase::DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) { + PreDestroyInstance(instance, allocator); + auto* table = DispatchTableManager::Get().GetInstanceDispatchTable(instance); + DispatchKey key = GetDispatchKey(instance); + if (table && table->DestroyInstance) { + table->DestroyInstance(instance, allocator); + } + PostDestroyInstance(instance); + DeviceInstanceTracker::Get().RemoveInstance(instance); + DispatchTableManager::Get().DestroyInstanceTable(key); +} + +VkResult LayerBase::EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices) { + auto* table = DispatchTableManager::Get().GetInstanceDispatchTable(instance); + if (!table || !table->EnumeratePhysicalDevices) { + return VK_ERROR_INITIALIZATION_FAILED; + } + VkResult result = table->EnumeratePhysicalDevices(instance, physical_device_count, physical_devices); + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && physical_device_count && physical_devices) { + for (uint32_t i = 0; i < *physical_device_count; ++i) { + DeviceInstanceTracker::Get().SetVkInstance(physical_devices[i], instance); + } + } + return result; +} + +VkResult LayerBase::EnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties) { + auto* table = DispatchTableManager::Get().GetInstanceDispatchTable(instance); + if (!table || !table->EnumeratePhysicalDeviceGroups) { + return VK_ERROR_INITIALIZATION_FAILED; + } + VkResult result = table->EnumeratePhysicalDeviceGroups(instance, physical_device_group_count, physical_device_group_properties); + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && physical_device_group_count && physical_device_group_properties) { + for (uint32_t i = 0; i < *physical_device_group_count; ++i) { + for (uint32_t j = 0; j < physical_device_group_properties[i].physicalDeviceCount; ++j) { + DeviceInstanceTracker::Get().SetVkInstance(physical_device_group_properties[i].physicalDevices[j], instance); + } + } + } + return result; +} + +VkResult LayerBase::CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device) { + if (!create_info || !device) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkInstance instance = DeviceInstanceTracker::Get().GetVkInstance(physical_device); + if (instance == VK_NULL_HANDLE) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkDeviceCreateInfo modified_create_info = *create_info; + VkLayerDeviceCreateInfo* chain_info = GetChainInfo(&modified_create_info, VK_LAYER_LINK_INFO); + if (!chain_info || !chain_info->u.pLayerInfo || !chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr || + !chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + PFN_vkGetInstanceProcAddr get_instance_proc_addr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr get_device_proc_addr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; + + auto create_device = reinterpret_cast(get_instance_proc_addr(instance, "vkCreateDevice")); + if (create_device == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + // Check for loader callback to initialize dispatchable handles created internally by the layer + PFN_vkSetDeviceLoaderData loader_callback = nullptr; + VkLayerDeviceCreateInfo* callback_info = GetChainInfo(&modified_create_info, VK_LOADER_DATA_CALLBACK); + if (callback_info && callback_info->u.pfnSetDeviceLoaderData) { + loader_callback = callback_info->u.pfnSetDeviceLoaderData; + } + + PreCreateDevice(physical_device, &modified_create_info, allocator); + + chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; + + VkResult result = create_device(physical_device, &modified_create_info, allocator, device); + if (result == VK_SUCCESS) { + DispatchTableManager::Get().InitDeviceTable(*device, get_device_proc_addr); + if (loader_callback) { + DispatchTableManager::Get().SetDeviceLoaderDataCallback(*device, loader_callback); + } + OnDeviceCreated(*device, physical_device, &modified_create_info); + } + return result; +} + +void LayerBase::DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) { + PreDestroyDevice(device, allocator); + auto* table = DispatchTableManager::Get().GetDeviceDispatchTable(device); + DispatchKey key = GetDispatchKey(device); + if (table && table->DestroyDevice) { + table->DestroyDevice(device, allocator); + } + PostDestroyDevice(device); + DispatchTableManager::Get().DestroyDeviceTable(key); +} + +VkResult LayerBase::EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) { + if (!manifest_) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return manifest_->EnumerateInstanceExtensionProperties(layer_name, property_count, properties); +} + +VkResult LayerBase::EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) { + if (!manifest_) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return manifest_->EnumerateInstanceLayerProperties(property_count, properties); +} + +VkResult LayerBase::EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties) { + (void)physical_device; + if (!manifest_) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return manifest_->EnumerateDeviceLayerProperties(property_count, properties); +} + +VkResult LayerBase::EnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, VkExtensionProperties* properties) { + if (!manifest_) { + return VK_ERROR_INITIALIZATION_FAILED; + } + PFN_vkEnumerateDeviceExtensionProperties downstream = nullptr; + if (physical_device != VK_NULL_HANDLE) { + VkInstance instance = DeviceInstanceTracker::Get().GetVkInstance(physical_device); + if (instance != VK_NULL_HANDLE) { + auto* table = DispatchTableManager::Get().GetInstanceDispatchTable(instance); + if (table) { + downstream = table->EnumerateDeviceExtensionProperties; + } + } + } + return manifest_->EnumerateDeviceExtensionProperties(physical_device, layer_name, property_count, properties, downstream); +} + +void LayerBase::PreCreateInstance(VkInstanceCreateInfo*, const VkAllocationCallbacks*) {} +void LayerBase::PostCreateInstance(VkInstance, const VkInstanceCreateInfo*) {} +void LayerBase::PreDestroyInstance(VkInstance, const VkAllocationCallbacks*) {} +void LayerBase::PostDestroyInstance(VkInstance) {} + +void LayerBase::PreCreateDevice(VkPhysicalDevice, VkDeviceCreateInfo*, const VkAllocationCallbacks*) {} +void LayerBase::OnDeviceCreated(VkDevice, VkPhysicalDevice, const VkDeviceCreateInfo*) {} +void LayerBase::PreDestroyDevice(VkDevice, const VkAllocationCallbacks*) {} +void LayerBase::PostDestroyDevice(VkDevice) {} + +PFN_vkVoidFunction LayerBase::GetLayerSpecificInstanceFunction(const char*) { + return nullptr; +} + +PFN_vkVoidFunction LayerBase::GetLayerSpecificDeviceFunction(const char*) { + return nullptr; +} + +PFN_vkVoidFunction LayerBase::GetKnownInstanceFunction(const char* name) { + if (!name) { + return nullptr; + } + + PFN_vkVoidFunction custom_func = GetLayerSpecificInstanceFunction(name); + if (custom_func != nullptr) { + return custom_func; + } + + if (std::strcmp(name, "vkGetInstanceProcAddr") == 0) return reinterpret_cast(LayerBaseGetInstanceProcAddr); + if (std::strcmp(name, "vkCreateInstance") == 0) return reinterpret_cast(LayerBaseCreateInstance); + if (std::strcmp(name, "vkDestroyInstance") == 0) return reinterpret_cast(LayerBaseDestroyInstance); + if (std::strcmp(name, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast(LayerBaseEnumeratePhysicalDevices); + if (std::strcmp(name, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast(LayerBaseEnumeratePhysicalDeviceGroups); + if (std::strcmp(name, "vkCreateDevice") == 0) return reinterpret_cast(LayerBaseCreateDevice); + if (manifest_) { + if (std::strcmp(name, "vkEnumerateInstanceExtensionProperties") == 0) return reinterpret_cast(LayerBaseEnumerateInstanceExtensionProperties); + if (std::strcmp(name, "vkEnumerateInstanceLayerProperties") == 0) return reinterpret_cast(LayerBaseEnumerateInstanceLayerProperties); + if (std::strcmp(name, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast(LayerBaseEnumerateDeviceLayerProperties); + if (std::strcmp(name, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(LayerBaseEnumerateDeviceExtensionProperties); + } + return nullptr; +} + +PFN_vkVoidFunction LayerBase::GetKnownDeviceFunction(const char* name) { + if (!name) { + return nullptr; + } + + PFN_vkVoidFunction custom_func = GetLayerSpecificDeviceFunction(name); + if (custom_func != nullptr) { + return custom_func; + } + + if (std::strcmp(name, "vkGetDeviceProcAddr") == 0) return reinterpret_cast(LayerBaseGetDeviceProcAddr); + if (std::strcmp(name, "vkDestroyDevice") == 0) return reinterpret_cast(LayerBaseDestroyDevice); + return nullptr; +} + +PFN_vkVoidFunction LayerBase::GetInstanceProcAddr(VkInstance instance, const char* name) { + if (!name) { + return nullptr; + } + + if (instance == VK_NULL_HANDLE) { + bool is_global_command = (std::strcmp(name, "vkGetInstanceProcAddr") == 0) || + (std::strcmp(name, "vkCreateInstance") == 0) || + (std::strcmp(name, "vkEnumerateInstanceExtensionProperties") == 0) || + (std::strcmp(name, "vkEnumerateInstanceLayerProperties") == 0); + if (!is_global_command) { + return nullptr; + } + } + + PFN_vkVoidFunction func = GetKnownInstanceFunction(name); + if (func != nullptr) { + return func; + } + + func = GetKnownDeviceFunction(name); + if (func != nullptr) { + return func; + } + + if (instance == VK_NULL_HANDLE) { + return nullptr; + } + + auto* table = DispatchTableManager::Get().GetInstanceDispatchTable(instance); + if (!table || !table->GetInstanceProcAddr) { + return nullptr; + } + return table->GetInstanceProcAddr(instance, name); +} + +PFN_vkVoidFunction LayerBase::GetDeviceProcAddr(VkDevice device, const char* name) { + if (!name) { + return nullptr; + } + + if (device == VK_NULL_HANDLE) { + return nullptr; + } + + PFN_vkVoidFunction func = GetKnownDeviceFunction(name); + if (func != nullptr) { + return func; + } + + auto* table = DispatchTableManager::Get().GetDeviceDispatchTable(device); + if (!table || !table->GetDeviceProcAddr) { + return nullptr; + } + return table->GetDeviceProcAddr(device, name); +} + +} // namespace layersvt diff --git a/layersvt/common/layer_base.h b/layersvt/common/layer_base.h new file mode 100644 index 0000000000..8612d4543a --- /dev/null +++ b/layersvt/common/layer_base.h @@ -0,0 +1,152 @@ +/* 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 + +#ifndef VK_LAYER_EXPORT +#if defined(_WIN32) +#define VK_LAYER_EXPORT __declspec(dllexport) +#else +#define VK_LAYER_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +/** + * @brief Macro to define standard Vulkan layer exported C entry points (vkGetInstanceProcAddr + * and vkGetDeviceProcAddr) for a LayerBase-derived singleton class. + * + * Instantiate this macro in the layer's dispatch translation unit (compiled only into the + * layer shared library module, avoiding symbol collisions in test executables). + */ +#define DEFINE_VK_LAYER_ENTRYPOINTS(LayerClass) \ + extern "C" { \ + VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr( \ + VkInstance instance, const char* pName) { \ + return LayerClass::Get().GetInstanceProcAddr(instance, pName); \ + } \ + VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr( \ + VkDevice device, const char* pName) { \ + return LayerClass::Get().GetDeviceProcAddr(device, pName); \ + } \ + VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties( \ + uint32_t* pPropertyCount, VkLayerProperties* pProperties) { \ + return LayerClass::Get().EnumerateInstanceLayerProperties(pPropertyCount, pProperties); \ + } \ + VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties( \ + const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { \ + return LayerClass::Get().EnumerateInstanceExtensionProperties( \ + pLayerName, pPropertyCount, pProperties); \ + } \ + VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( \ + VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkLayerProperties* pProperties) { \ + return LayerClass::Get().EnumerateDeviceLayerProperties( \ + physicalDevice, pPropertyCount, pProperties); \ + } \ + VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties( \ + VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pPropertyCount, \ + VkExtensionProperties* pProperties) { \ + return LayerClass::Get().EnumerateDeviceExtensionProperties( \ + physicalDevice, pLayerName, pPropertyCount, pProperties); \ + } \ + } + +namespace layersvt { + +class LayerManifest; + +/** + * @brief Base class for Vulkan layers providing standard lifecycle management, + * loader chain unwrapping, and dispatch table tracking. + * + * Derived classes can customize behavior by overriding the virtual hooks: + * - PreCreateInstance / PostCreateInstance + * - PreDestroyInstance / PostDestroyInstance + * - PreCreateDevice / OnDeviceCreated + * - PreDestroyDevice / PostDestroyDevice + */ +class LayerBase { + public: + explicit LayerBase(const LayerManifest* manifest = nullptr); + virtual ~LayerBase(); + + const LayerManifest* GetManifest() const { return manifest_; } + + // === Active Layer Management === + static LayerBase* GetActiveLayer() { return active_layer_; } + static void SetActiveLayer(LayerBase* layer) { active_layer_ = layer; } + + // === Vulkan ProcAddr Dispatch (Template Method / Non-Virtual Interface) === + + PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* name); + PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* name); + + PFN_vkVoidFunction GetKnownInstanceFunction(const char* name); + PFN_vkVoidFunction GetKnownDeviceFunction(const char* name); + + // === Core Vulkan Lifecycle Intercepts === + + virtual VkResult CreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, + VkInstance* instance); + + virtual void DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator); + + virtual VkResult EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices); + + virtual VkResult EnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties); + + virtual VkResult CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device); + + virtual void DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); + + // === Vulkan Layer Manifest Enumeration === + + VkResult EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties); + VkResult EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties); + VkResult EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties); + VkResult EnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, VkExtensionProperties* properties); + + // === Customization Hooks === + + virtual void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator); + virtual void PostCreateInstance(VkInstance instance, const VkInstanceCreateInfo* create_info); + virtual void PreDestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator); + virtual void PostDestroyInstance(VkInstance instance); + + virtual void PreCreateDevice(VkPhysicalDevice physical_device, VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator); + virtual void OnDeviceCreated(VkDevice device, VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info); + virtual void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); + virtual void PostDestroyDevice(VkDevice device); + + protected: + // Hooks for layer-specific functions ONLY. Default returns nullptr. + // Derived layers override these to expose layer-specific commands and extensions. + virtual PFN_vkVoidFunction GetLayerSpecificInstanceFunction(const char* name); + virtual PFN_vkVoidFunction GetLayerSpecificDeviceFunction(const char* name); + + const LayerManifest* manifest_ = nullptr; + static inline LayerBase* active_layer_ = nullptr; +}; + +} // namespace layersvt diff --git a/layersvt/common/layer_keep_alive.cpp b/layersvt/common/layer_keep_alive.cpp new file mode 100644 index 0000000000..5557de9217 --- /dev/null +++ b/layersvt/common/layer_keep_alive.cpp @@ -0,0 +1,48 @@ +/* Copyright (c) 2015-2026 The Khronos Group Inc. + * Copyright (c) 2015-2026 Valve Corporation + * Copyright (c) 2015-2026 LunarG, Inc. + * 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 "layer_keep_alive.h" + +#if defined(__ANDROID__) +#include + +namespace layersvt { + +void EnsureLayerKeepAlive() { + static const bool initialized = []() { + Dl_info info; + if (dladdr(reinterpret_cast(&EnsureLayerKeepAlive), &info) && info.dli_fname) { + dlopen(info.dli_fname, RTLD_NODELETE); + } + return true; + }(); + (void)initialized; +} + +namespace { +__attribute__((constructor)) void keep_alive_ctor() { + EnsureLayerKeepAlive(); +} +} // namespace + +} // namespace layersvt +#else +namespace layersvt { +void EnsureLayerKeepAlive() {} +} // namespace layersvt +#endif diff --git a/layersvt/common/layer_keep_alive.h b/layersvt/common/layer_keep_alive.h new file mode 100644 index 0000000000..5ae6265c70 --- /dev/null +++ b/layersvt/common/layer_keep_alive.h @@ -0,0 +1,29 @@ +/* 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 + +namespace layersvt { + +/** + * @brief Ensures the layer shared library is pinned with RTLD_NODELETE on Android. + * + * Calling this explicitly guarantees that the dynamic linker does not unload the + * layer shared object when dlclose is called, preventing crashes during teardown. + * On non-Android platforms, this is a no-op. + */ +void EnsureLayerKeepAlive(); + +} // namespace layersvt diff --git a/layersvt/common/layer_manifest.cpp b/layersvt/common/layer_manifest.cpp new file mode 100644 index 0000000000..d36a404a3d --- /dev/null +++ b/layersvt/common/layer_manifest.cpp @@ -0,0 +1,213 @@ +/* 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 "layer_manifest.h" +#include +#include + +namespace layersvt { + +namespace { + +VkResult CopyExtensionProperties(const std::vector& extensions, uint32_t* property_count, + VkExtensionProperties* properties) { + if (property_count == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + const uint32_t total = static_cast(extensions.size()); + if (properties == nullptr) { + *property_count = total; + return VK_SUCCESS; + } + + const uint32_t copy_count = std::min(*property_count, total); + if (copy_count > 0) { + std::memcpy(properties, extensions.data(), copy_count * sizeof(VkExtensionProperties)); + } + *property_count = copy_count; + + return (copy_count < total) ? VK_INCOMPLETE : VK_SUCCESS; +} + +} // namespace + +LayerManifest::LayerManifest(Config config) : config_(std::move(config)) { + std::memset(&layer_properties_, 0, sizeof(layer_properties_)); + if (config_.layer_name) { + std::strncpy(layer_properties_.layerName, config_.layer_name, VK_MAX_EXTENSION_NAME_SIZE - 1); + } + if (config_.description) { + std::strncpy(layer_properties_.description, config_.description, VK_MAX_DESCRIPTION_SIZE - 1); + } + layer_properties_.specVersion = config_.spec_version; + layer_properties_.implementationVersion = config_.implementation_version; +} + +VkResult LayerManifest::EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) const { + if (property_count == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + if (properties == nullptr) { + *property_count = 1; + return VK_SUCCESS; + } + if (*property_count < 1) { + return VK_INCOMPLETE; + } + *properties = layer_properties_; + *property_count = 1; + return VK_SUCCESS; +} + +VkResult LayerManifest::EnumerateDeviceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) const { + return EnumerateInstanceLayerProperties(property_count, properties); +} + +VkResult LayerManifest::EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties) const { + (void)physical_device; + return EnumerateDeviceLayerProperties(property_count, properties); +} + +VkResult LayerManifest::EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) const { + if (property_count == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + if (layer_name && std::strcmp(layer_name, layer_properties_.layerName) != 0) { + *property_count = 0; + return VK_ERROR_LAYER_NOT_PRESENT; + } + + return CopyExtensionProperties(config_.instance_extensions, property_count, properties); +} + +VkResult LayerManifest::EnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, VkExtensionProperties* properties, + PFN_vkEnumerateDeviceExtensionProperties downstream_function) const { + if (property_count == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + // When explicitly querying this layer's device extensions: + if (layer_name && std::strcmp(layer_name, layer_properties_.layerName) == 0) { + return CopyExtensionProperties(config_.device_extensions, property_count, properties); + } + + // If another layer is being queried, this layer doesn't handle it. + if (layer_name && std::strcmp(layer_name, layer_properties_.layerName) != 0) { + if (downstream_function) { + return downstream_function(physical_device, layer_name, property_count, properties); + } + *property_count = 0; + return VK_ERROR_LAYER_NOT_PRESENT; + } + + // layer_name is nullptr: query downstream extensions and merge with layer device extensions + const uint32_t layer_count = static_cast(config_.device_extensions.size()); + if (properties == nullptr) { + uint32_t driver_count = 0; + VkResult result = downstream_function ? downstream_function(physical_device, nullptr, &driver_count, nullptr) : VK_SUCCESS; + if (result == VK_SUCCESS || result == VK_INCOMPLETE) { + *property_count = driver_count + layer_count; + return VK_SUCCESS; + } + return result; + } + + uint32_t requested = *property_count; + uint32_t driver_count = requested; + VkResult result = VK_SUCCESS; + if (downstream_function) { + result = downstream_function(physical_device, nullptr, &driver_count, properties); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + } else { + // When downstream_function is null (standalone layer or bottom of loader chain), + // driver_count must be reset to 0 so we don't inspect uninitialized properties memory + // with strcmp or skip layer extensions. + driver_count = 0; + } + + uint32_t current_count = driver_count; + for (const auto& layer_extension : config_.device_extensions) { + bool duplicate = false; + for (uint32_t i = 0; i < driver_count; ++i) { + if (std::strcmp(properties[i].extensionName, layer_extension.extensionName) == 0) { + duplicate = true; + break; + } + } + if (!duplicate) { + if (current_count < requested) { + properties[current_count] = layer_extension; + } + ++current_count; + } + } + *property_count = std::min(current_count, requested); + return (current_count > requested || result == VK_INCOMPLETE) ? VK_INCOMPLETE : VK_SUCCESS; +} + +VkResult LayerManifest::GetPhysicalDeviceToolProperties(VkPhysicalDevice physical_device, uint32_t* tool_count, + VkPhysicalDeviceToolPropertiesEXT* tool_properties, + PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream_function) const { + if (tool_count == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + uint32_t downstream_count = 0; + if (downstream_function) { + VkResult result = downstream_function(physical_device, &downstream_count, nullptr); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + } + + const uint32_t layer_tool_count = config_.tool_properties.has_value() ? 1 : 0; + const uint32_t total = downstream_count + layer_tool_count; + + if (tool_properties == nullptr) { + *tool_count = total; + return VK_SUCCESS; + } + + std::vector tools; + if (downstream_function && downstream_count > 0) { + tools.resize(downstream_count); + VkResult result = downstream_function(physical_device, &downstream_count, tools.data()); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + tools.resize(downstream_count); + } + + if (config_.tool_properties.has_value()) { + tools.push_back(*config_.tool_properties); + } + + const uint32_t copy_count = std::min(*tool_count, static_cast(tools.size())); + for (uint32_t i = 0; i < copy_count; ++i) { + tool_properties[i] = tools[i]; + } + *tool_count = copy_count; + + return (copy_count < tools.size()) ? VK_INCOMPLETE : VK_SUCCESS; +} + +} // namespace layersvt diff --git a/layersvt/common/layer_manifest.h b/layersvt/common/layer_manifest.h new file mode 100644 index 0000000000..a774359864 --- /dev/null +++ b/layersvt/common/layer_manifest.h @@ -0,0 +1,73 @@ +/* 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 + +namespace layersvt { + +/** + * @brief Declarative metadata and extension property handler for a Vulkan layer. + * + * Centralizes the boilerplate for vkEnumerateInstanceLayerProperties, + * vkEnumerateDeviceLayerProperties, vkEnumerateInstanceExtensionProperties, + * vkEnumerateDeviceExtensionProperties, and vkGetPhysicalDeviceToolPropertiesEXT. + * + * Automatically handles the Android b/143293104 workaround and deduplication + * when merging layer extensions with downstream driver extensions. + */ +class LayerManifest { + public: + struct Config { + const char* layer_name = ""; + const char* description = ""; + uint32_t spec_version = VK_API_VERSION_1_3; + uint32_t implementation_version = 1; + std::vector instance_extensions; + std::vector device_extensions; + std::optional tool_properties; + }; + + explicit LayerManifest(Config config); + + const Config& GetConfig() const { return config_; } + + VkResult EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) const; + VkResult EnumerateDeviceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) const; + VkResult EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties) const; + + VkResult EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) const; + + VkResult EnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties, + PFN_vkEnumerateDeviceExtensionProperties downstream_function) const; + + VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physical_device, uint32_t* tool_count, + VkPhysicalDeviceToolPropertiesEXT* tool_properties, + PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream_function) const; + + private: + Config config_; + VkLayerProperties layer_properties_{}; +}; + +} // namespace layersvt diff --git a/layersvt/common/log.h b/layersvt/common/log.h new file mode 100644 index 0000000000..f2cfd3b1ec --- /dev/null +++ b/layersvt/common/log.h @@ -0,0 +1,28 @@ +/* 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 + +#if defined(__ANDROID__) +#include +#define VT_LOGI(tag, fmt, ...) __android_log_print(ANDROID_LOG_INFO, tag, fmt, ##__VA_ARGS__) +#define VT_LOGW(tag, fmt, ...) __android_log_print(ANDROID_LOG_WARN, tag, fmt, ##__VA_ARGS__) +#define VT_LOGE(tag, fmt, ...) __android_log_print(ANDROID_LOG_ERROR, tag, fmt, ##__VA_ARGS__) +#else +#include +#define VT_LOGI(tag, fmt, ...) std::fprintf(stderr, "[%s] " fmt "\n", tag, ##__VA_ARGS__) +#define VT_LOGW(tag, fmt, ...) std::fprintf(stderr, "[%s WARN] " fmt "\n", tag, ##__VA_ARGS__) +#define VT_LOGE(tag, fmt, ...) std::fprintf(stderr, "[%s ERROR] " fmt "\n", tag, ##__VA_ARGS__) +#endif 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..0be3439117 100644 --- a/layersvt/debug_marker/debug_marker.cpp +++ b/layersvt/debug_marker/debug_marker.cpp @@ -15,23 +15,63 @@ #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 "common/device_instance_tracker.h" #include "perfetto/perfetto.h" +#include + +const layersvt::LayerManifest& GetDebugMarkerManifest() { + static const layersvt::LayerManifest manifest(layersvt::LayerManifest::Config{ + .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; +} + +DebugMarker::DebugMarker() : LayerBase(&GetDebugMarkerManifest()) {} DebugMarker& DebugMarker::Get() { static DebugMarker instance; return instance; } -void DebugMarker::SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance) { +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::PostDestroyDevice(VkDevice device) { std::lock_guard lock(mutex_); - vk_instance_map_[phys_dev] = instance; + 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::SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance) { + layersvt::DeviceInstanceTracker::Get().SetVkInstance(phys_dev, instance); } 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; + return layersvt::DeviceInstanceTracker::Get().GetVkInstance(phys_dev); } void DebugMarker::SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name) { @@ -74,8 +114,8 @@ void DebugMarker::EmitAllDebugMarkers() { void DebugMarker::Clear() { std::lock_guard lock(mutex_); - vk_instance_map_.clear(); debug_object_names_.clear(); + layersvt::DeviceInstanceTracker::Get().Clear(); } bool DebugMarker::HasDebugObjectName(int32_t type, uint64_t handle, const std::string& name) { @@ -84,3 +124,34 @@ bool DebugMarker::HasDebugObjectName(int32_t type, uint64_t handle, const std::s if (it == debug_object_names_.end()) return false; return it->second.name == name; } + +PFN_vkVoidFunction DebugMarker::GetLayerSpecificInstanceFunction(const char* name) { + 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::GetLayerSpecificDeviceFunction(const char* name) { + 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..ce53f00532 100644 --- a/layersvt/debug_marker/debug_marker.h +++ b/layersvt/debug_marker/debug_marker.h @@ -17,11 +17,13 @@ #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. @@ -45,13 +47,9 @@ * 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. @@ -59,6 +57,19 @@ class DebugMarker { */ static DebugMarker& Get(); + DebugMarker(); + ~DebugMarker() override = default; + + /** + * @brief Lifecycle hook called before vkCreateInstance. + */ + void PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) override; + + /** + * @brief Lifecycle hook called after vkDestroyDevice to remove tracked names for destroyed objects. + */ + void PostDestroyDevice(VkDevice device) override; + /** * @brief Sets or updates the name associated with a Vulkan object. * @param device The handle of the Vulkan device that owns the object. @@ -99,6 +110,9 @@ class DebugMarker { */ VkInstance GetVkInstance(VkPhysicalDevice phys_dev); + protected: + PFN_vkVoidFunction GetLayerSpecificInstanceFunction(const char* name) override; + PFN_vkVoidFunction GetLayerSpecificDeviceFunction(const char* name) override; private: struct DebugObjectName { @@ -113,13 +127,11 @@ 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. */ std::map, DebugObjectName> debug_object_names_; }; + +const layersvt::LayerManifest& GetDebugMarkerManifest(); diff --git a/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp b/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp index a320032500..935d70e411 100644 --- a/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp +++ b/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp @@ -13,97 +13,6 @@ * 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 +#include "debug_marker.h" -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" +DEFINE_VK_LAYER_ENTRYPOINTS(DebugMarker) 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..937ebff9e7 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,7 +16,7 @@ #pragma once #include -#include "vk_layer_table.h" +#include "common/dispatch_table_manager.h" #include "debug_marker.h" // This file contains handwritten functions for the VK_EXT_debug_marker extension. @@ -77,41 +77,31 @@ 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::DispatchDownstream<&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::DispatchDownstream<&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::DispatchDownstream<&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; + if (pNameInfo) { + DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)getVkObjectType(pNameInfo->objectType), pNameInfo->object, pNameInfo->pObjectName); } - return VK_SUCCESS; + return layersvt::DispatchDownstream<&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::DispatchDownstream<&VkuDeviceDispatchTable::DebugMarkerSetObjectTagEXT>(device, pTagInfo); } } // 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..b29b841180 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_table_manager.h" #include "debug_marker.h" extern "C" { @@ -28,84 +28,61 @@ extern "C" { // 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::DispatchDownstream<&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::DispatchDownstream<&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::DispatchDownstream<&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; + if (pNameInfo) { + DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)pNameInfo->objectType, pNameInfo->objectHandle, pNameInfo->pObjectName); } - return VK_SUCCESS; + return layersvt::DispatchDownstream<&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::DispatchDownstream<&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::DispatchDownstream<&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::DispatchDownstream<&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::DispatchDownstream<&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; + return layersvt::DispatchDownstream<&VkuInstanceDispatchTable::CreateDebugUtilsMessengerEXT>(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); - } + layersvt::DispatchDownstream<&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); - } + layersvt::DispatchDownstream<&VkuInstanceDispatchTable::SubmitDebugUtilsMessageEXT>(instance, messageSeverity, messageTypes, pCallbackData); } + } // extern "C" diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index 6f7bdbd646..2949f6e394 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -18,6 +18,10 @@ if (ANDROID) return() endif() +if (NOT MSVC) + set_source_files_properties(../perfetto/perfetto.cc PROPERTIES COMPILE_OPTIONS "-Wno-deprecated-declarations") +endif() + function(LayerTest NAME) string(TOLOWER ${NAME} LOWER_NAME) set(TEST_FILENAME ./test_${LOWER_NAME}.cpp) @@ -37,6 +41,7 @@ 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) 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) @@ -61,3 +66,17 @@ foreach(test_item ${LAYER_TEST_FILES}) LayerTest(${test_item}) endforeach() + +add_executable(test_common_layer + test_common.cpp + layer_test_main.cpp +) +target_link_libraries(test_common_layer PRIVATE + layersvt_common + GTest::gtest + Vulkan::Headers + Vulkan::UtilityHeaders +) +add_test(NAME test_common_layer COMMAND test_common_layer) +set_target_properties(test_common_layer PROPERTIES FOLDER "layers/common/Test") + diff --git a/layersvt/test/test_common.cpp b/layersvt/test/test_common.cpp new file mode 100644 index 0000000000..d683d513f8 --- /dev/null +++ b/layersvt/test/test_common.cpp @@ -0,0 +1,1287 @@ +/* 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 "common/device_instance_tracker.h" +#include "common/dispatch_table_manager.h" +#include "common/layer_manifest.h" +#include "common/layer_base.h" +#include "common/log.h" + +#include +#include +#include +#include +#include + +using namespace layersvt; + +// ============================================================================= +// DeviceInstanceTracker Tests +// ============================================================================= + +TEST(DeviceInstanceTrackerTest, BasicTracking) { + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + auto mock_instance1 = reinterpret_cast(0x1000); + auto mock_physical_device1 = reinterpret_cast(0x2001); + auto mock_physical_device2 = reinterpret_cast(0x2002); + + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device1), VK_NULL_HANDLE); + + tracker.SetVkInstance(mock_physical_device1, mock_instance1); + tracker.SetVkInstance(mock_physical_device2, mock_instance1); + + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device1), mock_instance1); + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device2), mock_instance1); +} + +TEST(DeviceInstanceTrackerTest, RemoveInstance) { + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + auto mock_instance1 = reinterpret_cast(0x1001); + auto mock_instance2 = reinterpret_cast(0x1002); + auto mock_physical_device1 = reinterpret_cast(0x2001); + auto mock_physical_device2 = reinterpret_cast(0x2002); + + tracker.SetVkInstance(mock_physical_device1, mock_instance1); + tracker.SetVkInstance(mock_physical_device2, mock_instance2); + + tracker.RemoveInstance(mock_instance1); + + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device1), VK_NULL_HANDLE); + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device2), mock_instance2); +} + +TEST(DeviceInstanceTrackerTest, ConcurrentAccess) { + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + constexpr int kNumThreads = 8; + constexpr int kIterations = 1000; + std::atomic start_flag{false}; + std::vector threads; + + for (int thread_index = 0; thread_index < kNumThreads; ++thread_index) { + threads.emplace_back([&, thread_index]() { + while (!start_flag.load()) { + std::this_thread::yield(); + } + for (int i = 0; i < kIterations; ++i) { + auto instance = reinterpret_cast(static_cast(0x1000 + thread_index)); + auto physical_device = reinterpret_cast(static_cast(0x2000 + (i % 50))); + tracker.SetVkInstance(physical_device, instance); + auto result = tracker.GetVkInstance(physical_device); + EXPECT_NE(result, VK_NULL_HANDLE); + } + }); + } + + start_flag.store(true); + for (auto& thread : threads) { + thread.join(); + } +} + +TEST(DeviceInstanceTrackerTest, EagerMapDevices) { + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + void* mock_instance_vtable = reinterpret_cast(0xABCDEF01); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device1 = reinterpret_cast(0x3001); + auto mock_physical_device2 = reinterpret_cast(0x3002); + + PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDevices") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* property_count, VkPhysicalDevice* physical_devices) -> VkResult { + if (!property_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_devices) { + *property_count = 2; + return VK_SUCCESS; + } + physical_devices[0] = reinterpret_cast(0x3001); + physical_devices[1] = reinterpret_cast(0x3002); + *property_count = 2; + return VK_SUCCESS; + }); + } + return nullptr; + }; + + tracker.EagerMapDevices(mock_instance, mock_get_instance_proc_addr); + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device1), mock_instance); + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device2), mock_instance); +} + +TEST(DeviceInstanceTrackerTest, EagerMapDevicesIncomplete) { + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + void* mock_instance_vtable = reinterpret_cast(0xABCDEF02); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(0x3003); + + PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDevices") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* property_count, VkPhysicalDevice* physical_devices) -> VkResult { + if (!property_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_devices) { + *property_count = 1; + return VK_INCOMPLETE; + } + physical_devices[0] = reinterpret_cast(0x3003); + *property_count = 1; + return VK_INCOMPLETE; + }); + } + return nullptr; + }; + + tracker.EagerMapDevices(mock_instance, mock_get_instance_proc_addr); + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device), mock_instance); +} + +// ============================================================================= +// DispatchTableManager Tests +// ============================================================================= + +TEST(DispatchTableManagerTest, GetDispatchKey) { + EXPECT_EQ(GetDispatchKey(nullptr), nullptr); + + void* mock_vtable = reinterpret_cast(0xDEADBEEF); + void* mock_object = &mock_vtable; + + EXPECT_EQ(GetDispatchKey(mock_object), mock_vtable); +} + +TEST(DispatchTableManagerTest, LoaderDataCallback) { + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + + void* mock_device_vtable = reinterpret_cast(0x12345678); + VkDevice mock_device = reinterpret_cast(&mock_device_vtable); + + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(mock_device), nullptr); + + PFN_vkSetDeviceLoaderData dummy_callback = [](VkDevice, void*) -> VkResult { return VK_SUCCESS; }; + dispatch_table_manager.SetDeviceLoaderDataCallback(mock_device, dummy_callback); + + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(mock_device), dummy_callback); + + dispatch_table_manager.DestroyDeviceTable(GetDispatchKey(mock_device)); + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(mock_device), nullptr); +} + +TEST(DispatchTableManagerTest, InstanceAndDeviceTableLifecycle) { + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + + void* mock_instance_vtable = reinterpret_cast(0x1111); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_instance), nullptr); + auto* instance_table = dispatch_table_manager.InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(instance_table, nullptr); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_instance), instance_table); + + dispatch_table_manager.DestroyInstanceTable(GetDispatchKey(mock_instance)); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_instance), nullptr); + + void* mock_device_vtable = reinterpret_cast(0x2222); + auto mock_device = reinterpret_cast(&mock_device_vtable); + + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(mock_device), nullptr); + auto* device_table = + dispatch_table_manager.InitDeviceTable(mock_device, [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(device_table, nullptr); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(mock_device), device_table); + + dispatch_table_manager.DestroyDeviceTable(GetDispatchKey(mock_device)); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(mock_device), nullptr); +} + +TEST(DispatchTableManagerTest, DispatchDownstream) { + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + + void* mock_instance_vtable = reinterpret_cast(0x1111); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + + void* mock_device_vtable = reinterpret_cast(0x2222); + auto mock_device = reinterpret_cast(&mock_device_vtable); + + // 1. Unregistered handles (no dispatch table present) + // VkResult return type returns VK_SUCCESS fallback + uint32_t count = 0; + VkResult inst_result = DispatchDownstream<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(mock_instance, &count, nullptr); + EXPECT_EQ(inst_result, VK_SUCCESS); + + VkResult dev_result = DispatchDownstream<&VkuDeviceDispatchTable::DeviceWaitIdle>(mock_device); + EXPECT_EQ(dev_result, VK_SUCCESS); + + // void return type safely no-ops without crashing + DispatchDownstream<&VkuInstanceDispatchTable::DestroyInstance>(mock_instance, nullptr); + DispatchDownstream<&VkuDeviceDispatchTable::DestroyDevice>(mock_device, nullptr); + + // 2. Initialized tables with null function pointers (fallback behavior) + dispatch_table_manager.InitInstanceTable(mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + dispatch_table_manager.InitDeviceTable(mock_device, [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }); + + EXPECT_EQ((DispatchDownstream<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(mock_instance, &count, nullptr)), VK_SUCCESS); + EXPECT_EQ((DispatchDownstream<&VkuDeviceDispatchTable::DeviceWaitIdle>(mock_device)), VK_SUCCESS); + DispatchDownstream<&VkuInstanceDispatchTable::DestroyInstance>(mock_instance, nullptr); + DispatchDownstream<&VkuDeviceDispatchTable::DestroyDevice>(mock_device, nullptr); + + // 3. Initialized tables with valid mock function pointers (downstream forwarding) + static bool inst_fn_called = false; + static bool dev_fn_called = false; + inst_fn_called = false; + dev_fn_called = false; + + auto* inst_table = dispatch_table_manager.GetInstanceDispatchTable(mock_instance); + ASSERT_NE(inst_table, nullptr); + inst_table->EnumeratePhysicalDevices = [](VkInstance, uint32_t* pCount, VkPhysicalDevice*) -> VkResult { + inst_fn_called = true; + if (pCount) *pCount = 42; + return VK_INCOMPLETE; + }; + + count = 0; + EXPECT_EQ((DispatchDownstream<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(mock_instance, &count, nullptr)), VK_INCOMPLETE); + EXPECT_TRUE(inst_fn_called); + EXPECT_EQ(count, 42u); + + auto* dev_table = dispatch_table_manager.GetDeviceDispatchTable(mock_device); + ASSERT_NE(dev_table, nullptr); + dev_table->DeviceWaitIdle = [](VkDevice) -> VkResult { + dev_fn_called = true; + return VK_NOT_READY; + }; + + EXPECT_EQ((DispatchDownstream<&VkuDeviceDispatchTable::DeviceWaitIdle>(mock_device)), VK_NOT_READY); + EXPECT_TRUE(dev_fn_called); + + dispatch_table_manager.Clear(); +} + +TEST(DispatchTableManagerTest, ConcurrentAccess) { + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + + constexpr int kNumThreads = 8; + constexpr int kIterations = 500; + std::atomic start_flag{false}; + std::vector threads; + + // Pre-allocate dummy vtables and handles for each thread to ensure stable memory addresses + struct ThreadMockData { + void* instance_vtable; + VkInstance instance; + void* device_vtable; + VkDevice device; + }; + std::vector mock_data(kNumThreads); + for (int t = 0; t < kNumThreads; ++t) { + mock_data[t].instance_vtable = reinterpret_cast(static_cast(0x10000 + t * 0x100)); + mock_data[t].instance = reinterpret_cast(&mock_data[t].instance_vtable); + mock_data[t].device_vtable = reinterpret_cast(static_cast(0x20000 + t * 0x100)); + mock_data[t].device = reinterpret_cast(&mock_data[t].device_vtable); + } + + PFN_vkSetDeviceLoaderData dummy_callback = [](VkDevice, void*) -> VkResult { return VK_SUCCESS; }; + + for (int thread_index = 0; thread_index < kNumThreads; ++thread_index) { + threads.emplace_back([&, thread_index]() { + while (!start_flag.load()) { + std::this_thread::yield(); + } + + auto& my_data = mock_data[thread_index]; + + for (int i = 0; i < kIterations; ++i) { + // Initialize tables + auto* inst_table = dispatch_table_manager.InitInstanceTable( + my_data.instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(inst_table, nullptr); + + auto* dev_table = dispatch_table_manager.InitDeviceTable( + my_data.device, [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(dev_table, nullptr); + + // Set loader data callback + dispatch_table_manager.SetDeviceLoaderDataCallback(my_data.device, dummy_callback); + + // Read back own tables and callback + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(my_data.instance), inst_table); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(my_data.device), dev_table); + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(my_data.device), dummy_callback); + + // Concurrent cross-thread read from a neighbor's handle + int neighbor_index = (thread_index + 1) % kNumThreads; + (void)dispatch_table_manager.GetDeviceDispatchTable(mock_data[neighbor_index].device); + (void)dispatch_table_manager.GetInstanceDispatchTable(mock_data[neighbor_index].instance); + + // Destroy tables periodically + if ((i % 10) == 0) { + dispatch_table_manager.DestroyDeviceTable(GetDispatchKey(my_data.device)); + dispatch_table_manager.DestroyInstanceTable(GetDispatchKey(my_data.instance)); + } + } + }); + } + + start_flag.store(true); + for (auto& thread : threads) { + thread.join(); + } + + dispatch_table_manager.Clear(); +} + +// ============================================================================= +// LayerManifest Tests +// ============================================================================= + +TEST(LayerManifestTest, LayerProperties) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + config.description = "Sample Test Layer"; + config.spec_version = VK_API_VERSION_1_3; + config.implementation_version = 42; + + LayerManifest manifest(config); + + uint32_t count = 0; + VkResult result = manifest.EnumerateInstanceLayerProperties(&count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + + VkLayerProperties layer_properties{}; + result = manifest.EnumerateInstanceLayerProperties(&count, &layer_properties); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + EXPECT_STREQ(layer_properties.layerName, "VK_LAYER_TEST_Sample"); + EXPECT_STREQ(layer_properties.description, "Sample Test Layer"); + EXPECT_EQ(layer_properties.specVersion, VK_API_VERSION_1_3); + EXPECT_EQ(layer_properties.implementationVersion, 42u); +} + +TEST(LayerManifestTest, InstanceExtensions) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + config.instance_extensions = { + {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, + }; + + LayerManifest manifest(config); + + uint32_t count = 0; + VkResult result = manifest.EnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector extensions(count); + result = manifest.EnumerateInstanceExtensionProperties("VK_LAYER_TEST_Sample", &count, extensions.data()); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(extensions[0].extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + EXPECT_EQ(extensions[0].specVersion, static_cast(VK_EXT_DEBUG_UTILS_SPEC_VERSION)); + + // Querying for an unknown layer should return error + result = manifest.EnumerateInstanceExtensionProperties("VK_LAYER_UNKNOWN", &count, nullptr); + EXPECT_EQ(result, VK_ERROR_LAYER_NOT_PRESENT); +} + +TEST(LayerManifestTest, DeviceExtensionsDownstreamMerge) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + config.device_extensions = { + {"VK_EXT_custom_layer_extension", 1}, + }; + + LayerManifest manifest(config); + + // Mock downstream driver enumeration that returns VK_KHR_swapchain + auto mock_downstream = [](VkPhysicalDevice, const char*, uint32_t* count, VkExtensionProperties* properties) -> VkResult { + if (!properties) { + *count = 1; + return VK_SUCCESS; + } + std::strncpy(properties[0].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_MAX_EXTENSION_NAME_SIZE); + properties[0].specVersion = VK_KHR_SWAPCHAIN_SPEC_VERSION; + *count = 1; + return VK_SUCCESS; + }; + + uint32_t count = 0; + VkResult result = manifest.EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, nullptr, mock_downstream); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); // 1 from layer + 1 from driver + + std::vector merged(count); + result = manifest.EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, merged.data(), mock_downstream); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + + bool has_layer_extension = false; + bool has_driver_extension = false; + for (const auto& extension : merged) { + if (std::strcmp(extension.extensionName, "VK_EXT_custom_layer_extension") == 0) { + has_layer_extension = true; + } + if (std::strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { + has_driver_extension = true; + } + } + EXPECT_TRUE(has_layer_extension); + EXPECT_TRUE(has_driver_extension); +} + +TEST(LayerManifestTest, DeviceExtensionsDownstreamIncomplete) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + // Layer defines no device extensions of its own + LayerManifest manifest(config); + + // Mock downstream driver returning VK_INCOMPLETE + auto mock_downstream = [](VkPhysicalDevice, const char*, uint32_t* count, VkExtensionProperties* properties) -> VkResult { + if (!properties) { + *count = 5; + return VK_SUCCESS; + } + // Return incomplete since caller only provided space for 1 + std::strncpy(properties[0].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_MAX_EXTENSION_NAME_SIZE); + properties[0].specVersion = VK_KHR_SWAPCHAIN_SPEC_VERSION; + *count = 1; + return VK_INCOMPLETE; + }; + + uint32_t count = 1; + VkExtensionProperties prop{}; + VkResult result = manifest.EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, &prop, mock_downstream); + EXPECT_EQ(result, VK_INCOMPLETE); + EXPECT_EQ(count, 1u); +} + +TEST(LayerManifestTest, ToolPropertiesMerge) { + VkPhysicalDeviceToolPropertiesEXT layer_tool_properties = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, + nullptr, + "CommonLayerTool", + "1.0", + VK_TOOL_PURPOSE_PROFILING_BIT_EXT, + "Diagnostic tool description", + "CommonLayer"}; + + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + config.tool_properties = layer_tool_properties; + + LayerManifest manifest(config); + + // Mock downstream reporting 1 driver tool + auto mock_downstream_tool = [](VkPhysicalDevice, uint32_t* count, VkPhysicalDeviceToolPropertiesEXT* properties) -> VkResult { + if (!properties) { + *count = 1; + return VK_SUCCESS; + } + std::strncpy(properties[0].name, "DriverTool", VK_MAX_EXTENSION_NAME_SIZE); + *count = 1; + return VK_SUCCESS; + }; + + uint32_t count = 0; + VkResult result = manifest.GetPhysicalDeviceToolProperties(nullptr, &count, nullptr, mock_downstream_tool); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + + std::vector tools(count); + result = manifest.GetPhysicalDeviceToolProperties(nullptr, &count, tools.data(), mock_downstream_tool); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(tools[0].name, "DriverTool"); + EXPECT_STREQ(tools[1].name, "CommonLayerTool"); +} + +TEST(LayerManifestTest, DeviceLayerPropertiesOverload) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + LayerManifest manifest(config); + + uint32_t count = 0; + VkResult result = manifest.EnumerateDeviceLayerProperties(reinterpret_cast(0x123), &count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); +} + +TEST(LayerManifestTest, DeviceExtensionsNullDownstream) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + config.device_extensions = { + {"VK_EXT_standalone_extension", 1}, + }; + LayerManifest manifest(config); + + uint32_t count = 0; + EXPECT_EQ(manifest.EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, nullptr, nullptr), VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector extensions(count); + EXPECT_EQ(manifest.EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, extensions.data(), nullptr), VK_SUCCESS); + EXPECT_EQ(count, 1u); + EXPECT_STREQ(extensions[0].extensionName, "VK_EXT_standalone_extension"); +} + +TEST(LayerManifestTest, QueryDifferentLayerName) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + LayerManifest manifest(config); + + uint32_t count = 5; + EXPECT_EQ(manifest.EnumerateInstanceExtensionProperties("VK_LAYER_OTHER", &count, nullptr), VK_ERROR_LAYER_NOT_PRESENT); + EXPECT_EQ(count, 0u); + + count = 5; + EXPECT_EQ(manifest.EnumerateDeviceExtensionProperties(nullptr, "VK_LAYER_OTHER", &count, nullptr, nullptr), + VK_ERROR_LAYER_NOT_PRESENT); + EXPECT_EQ(count, 0u); +} + +TEST(LayerManifestTest, DeviceExtensionsMatchingLayerName) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + config.device_extensions = { + {"VK_EXT_custom_ext1", 1}, + {"VK_EXT_custom_ext2", 2}, + }; + LayerManifest manifest(config); + + // 1. Query count with matching layer name (returns layer's own device extension count) + uint32_t count = 0; + VkResult result = manifest.EnumerateDeviceExtensionProperties( + nullptr, "VK_LAYER_TEST_Sample", &count, nullptr, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + + // 2. Query properties buffer with sufficient space + std::vector extensions(count); + result = manifest.EnumerateDeviceExtensionProperties( + nullptr, "VK_LAYER_TEST_Sample", &count, extensions.data(), nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + EXPECT_STREQ(extensions[0].extensionName, "VK_EXT_custom_ext1"); + EXPECT_STREQ(extensions[1].extensionName, "VK_EXT_custom_ext2"); + + // 3. Query properties buffer with insufficient space (returns VK_INCOMPLETE) + count = 1; + VkExtensionProperties single_extension{}; + result = manifest.EnumerateDeviceExtensionProperties( + nullptr, "VK_LAYER_TEST_Sample", &count, &single_extension, nullptr); + EXPECT_EQ(result, VK_INCOMPLETE); + EXPECT_EQ(count, 1u); + EXPECT_STREQ(single_extension.extensionName, "VK_EXT_custom_ext1"); +} + +TEST(LayerManifestTest, DeviceExtensionsForwardDifferentLayerName) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + LayerManifest manifest(config); + + auto mock_downstream = [](VkPhysicalDevice, const char* layer_name, uint32_t* count, + VkExtensionProperties* properties) -> VkResult { + if (std::strcmp(layer_name, "VK_LAYER_DOWNSTREAM") == 0) { + if (!properties) { + *count = 1; + return VK_SUCCESS; + } + std::strncpy(properties[0].extensionName, "VK_EXT_downstream_ext", VK_MAX_EXTENSION_NAME_SIZE); + *count = 1; + return VK_SUCCESS; + } + return VK_ERROR_LAYER_NOT_PRESENT; + }; + + uint32_t count = 0; + VkResult result = manifest.EnumerateDeviceExtensionProperties( + nullptr, "VK_LAYER_DOWNSTREAM", &count, nullptr, mock_downstream); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector extensions(count); + result = manifest.EnumerateDeviceExtensionProperties( + nullptr, "VK_LAYER_DOWNSTREAM", &count, extensions.data(), mock_downstream); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + EXPECT_STREQ(extensions[0].extensionName, "VK_EXT_downstream_ext"); +} + +TEST(LayerManifestTest, ToolPropertiesErrorPropagation) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + LayerManifest manifest(config); + + auto error_downstream_tool = [](VkPhysicalDevice, uint32_t*, VkPhysicalDeviceToolPropertiesEXT*) -> VkResult { + return VK_ERROR_OUT_OF_HOST_MEMORY; + }; + + uint32_t count = 0; + EXPECT_EQ(manifest.GetPhysicalDeviceToolProperties(nullptr, &count, nullptr, error_downstream_tool), + VK_ERROR_OUT_OF_HOST_MEMORY); +} + +TEST(LayerManifestTest, EnumerateInstanceExtensionsNullCount) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + LayerManifest manifest(config); + + EXPECT_EQ(manifest.EnumerateInstanceExtensionProperties("VK_LAYER_UNKNOWN", nullptr, nullptr), VK_ERROR_INITIALIZATION_FAILED); +} + +// ============================================================================= +// LayerBase Mock Tests +// ============================================================================= + +class MockTestLayer : public LayerBase { + public: + bool pre_create_instance_called = false; + bool post_create_instance_called = false; + bool pre_destroy_instance_called = false; + bool post_destroy_instance_called = false; + + bool pre_create_device_called = false; + bool on_device_created_called = false; + bool pre_destroy_device_called = false; + bool post_destroy_device_called = false; + + void PreCreateInstance(VkInstanceCreateInfo*, const VkAllocationCallbacks*) override { pre_create_instance_called = true; } + void PostCreateInstance(VkInstance, const VkInstanceCreateInfo*) override { post_create_instance_called = true; } + void PreDestroyInstance(VkInstance, const VkAllocationCallbacks*) override { pre_destroy_instance_called = true; } + void PostDestroyInstance(VkInstance) override { post_destroy_instance_called = true; } + + void PreCreateDevice(VkPhysicalDevice, VkDeviceCreateInfo*, const VkAllocationCallbacks*) override { + pre_create_device_called = true; + } + void OnDeviceCreated(VkDevice, VkPhysicalDevice, const VkDeviceCreateInfo*) override { on_device_created_called = true; } + void PreDestroyDevice(VkDevice, const VkAllocationCallbacks*) override { pre_destroy_device_called = true; } + void PostDestroyDevice(VkDevice) override { post_destroy_device_called = true; } +}; + +TEST(LayerBaseTest, HookInvocations) { + MockTestLayer layer; + EXPECT_FALSE(layer.pre_create_instance_called); + EXPECT_FALSE(layer.pre_create_device_called); + EXPECT_FALSE(layer.on_device_created_called); + + // Verify hooks trigger as expected + layer.PreCreateInstance(nullptr, nullptr); + EXPECT_TRUE(layer.pre_create_instance_called); + + layer.PreCreateDevice(VK_NULL_HANDLE, nullptr, nullptr); + EXPECT_TRUE(layer.pre_create_device_called); + + layer.OnDeviceCreated(VK_NULL_HANDLE, VK_NULL_HANDLE, nullptr); + EXPECT_TRUE(layer.on_device_created_called); +} + +TEST(LayerBaseTest, CreateInstanceWithMockChain) { + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + static void* mock_instance_vtable = reinterpret_cast(0x11223344); + static auto mock_instance_handle = reinterpret_cast(&mock_instance_vtable); + + PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkCreateInstance") == 0) { + return reinterpret_cast( + +[](const VkInstanceCreateInfo*, const VkAllocationCallbacks*, VkInstance* instance_handle) -> VkResult { + *instance_handle = mock_instance_handle; + return VK_SUCCESS; + }); + } + if (std::strcmp(function_name, "vkEnumeratePhysicalDevices") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* property_count, VkPhysicalDevice* physical_devices) -> VkResult { + if (!physical_devices) { + *property_count = 1; + return VK_SUCCESS; + } + physical_devices[0] = reinterpret_cast(0x9999); + *property_count = 1; + return VK_SUCCESS; + }); + } + if (std::strcmp(function_name, "vkDestroyInstance") == 0) { + return reinterpret_cast(+[](VkInstance, const VkAllocationCallbacks*) {}); + } + return nullptr; + }; + + VkLayerInstanceLink layer_link{nullptr, mock_get_instance_proc_addr, nullptr}; + VkLayerInstanceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = &chain_info; + MockTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + + EXPECT_EQ(layer.CreateInstance(&instance_create_info, nullptr, &instance), VK_SUCCESS); + EXPECT_EQ(instance, mock_instance_handle); + EXPECT_TRUE(layer.pre_create_instance_called); + EXPECT_TRUE(layer.post_create_instance_called); + EXPECT_NE(dispatch_table_manager.GetInstanceDispatchTable(instance), nullptr); + // Verify eager mapping mapped physical device 0x9999 to instance + EXPECT_EQ(tracker.GetVkInstance(reinterpret_cast(0x9999)), instance); + + layer.DestroyInstance(instance, nullptr); + EXPECT_TRUE(layer.pre_destroy_instance_called); + EXPECT_TRUE(layer.post_destroy_instance_called); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(instance), nullptr); + EXPECT_EQ(tracker.GetVkInstance(reinterpret_cast(0x9999)), VK_NULL_HANDLE); +} + +TEST(LayerBaseTest, CreateInstanceNullHandling) { + LayerBase layer; + VkInstance instance = VK_NULL_HANDLE; + // Null create info + EXPECT_EQ(layer.CreateInstance(nullptr, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); + + // Missing chain info + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + EXPECT_EQ(layer.CreateInstance(&instance_create_info, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, PreCreateNotInvokedOnMissingChain) { + MockTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + EXPECT_EQ(layer.CreateInstance(&instance_create_info, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); + EXPECT_FALSE(layer.pre_create_instance_called); +} + +class SubclassWithInspection : public LayerBase { + public: + bool inspected = false; + void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks*) override { + if (create_info && create_info->pApplicationInfo) { + inspected = true; + } + } +}; + +TEST(LayerBaseTest, CreateInstanceNullSafetyInHook) { + SubclassWithInspection layer; + VkInstance instance = VK_NULL_HANDLE; + EXPECT_EQ(layer.CreateInstance(nullptr, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); + EXPECT_FALSE(layer.inspected); + + VkInstanceCreateInfo instance_create_info{}; + EXPECT_EQ(layer.CreateInstance(&instance_create_info, nullptr, nullptr), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, PreCreateInstanceMutation) { + class MutatingLayer : public LayerBase { + public: + void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks*) override { + if (create_info) { + create_info->flags = 0xABCD; + } + } + }; + + static VkInstanceCreateFlags received_flags = 0; + static void* mock_instance_vtable = reinterpret_cast(0x11223344); + static auto mock_instance_handle = reinterpret_cast(&mock_instance_vtable); + + PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkCreateInstance") == 0) { + return reinterpret_cast( + +[](const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks*, VkInstance* instance) -> VkResult { + received_flags = create_info->flags; + *instance = mock_instance_handle; + return VK_SUCCESS; + }); + } + return nullptr; + }; + + VkLayerInstanceLink layer_link{nullptr, mock_get_instance_proc_addr, nullptr}; + VkLayerInstanceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = &chain_info; + + MutatingLayer layer; + VkInstance instance = VK_NULL_HANDLE; + EXPECT_EQ(layer.CreateInstance(&instance_create_info, nullptr, &instance), VK_SUCCESS); + EXPECT_EQ(received_flags, 0xABCDu); +} + +TEST(LayerBaseTest, TeardownOrdering) { + class TeardownOrderLayer : public LayerBase { + public: + VkInstance captured_instance_in_post_destroy = VK_NULL_HANDLE; + VkPhysicalDevice mock_physical_device = reinterpret_cast(0x9999); + + void PostDestroyInstance(VkInstance) override { + captured_instance_in_post_destroy = DeviceInstanceTracker::Get().GetVkInstance(mock_physical_device); + } + }; + + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + + void* mock_instance_vtable = reinterpret_cast(0x11223344); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(0x9999); + + dispatch_table_manager.InitInstanceTable(mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkDestroyInstance") == 0) { + return reinterpret_cast(+[](VkInstance, const VkAllocationCallbacks*) {}); + } + return nullptr; + }); + tracker.SetVkInstance(mock_physical_device, mock_instance); + + TeardownOrderLayer layer; + layer.DestroyInstance(mock_instance, nullptr); + + // Verify PostDestroyInstance could still query the physical device mapping + EXPECT_EQ(layer.captured_instance_in_post_destroy, mock_instance); + // After DestroyInstance finishes, mapping is cleaned up + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device), VK_NULL_HANDLE); +} + +TEST(LayerBaseTest, CreateDeviceWithMockChain) { + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + void* mock_instance_vtable = reinterpret_cast(0x11223344); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(0x5555); + tracker.SetVkInstance(mock_physical_device, mock_instance); + + static void* mock_device_vtable = reinterpret_cast(0x55667788); + static auto mock_device_handle = reinterpret_cast(&mock_device_vtable); + + PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkCreateDevice") == 0) { + return reinterpret_cast(+[](VkPhysicalDevice, const VkDeviceCreateInfo*, + const VkAllocationCallbacks*, VkDevice* device_handle) -> VkResult { + *device_handle = mock_device_handle; + return VK_SUCCESS; + }); + } + return nullptr; + }; + + PFN_vkGetDeviceProcAddr mock_get_device_proc_addr = [](VkDevice, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkDestroyDevice") == 0) { + return reinterpret_cast(+[](VkDevice, const VkAllocationCallbacks*) {}); + } + return nullptr; + }; + + VkLayerDeviceLink layer_link{nullptr, mock_get_instance_proc_addr, mock_get_device_proc_addr}; + VkLayerDeviceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + PFN_vkSetDeviceLoaderData mock_loader_callback = [](VkDevice, void*) -> VkResult { return VK_SUCCESS; }; + VkLayerDeviceCreateInfo callback_info{VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO, &chain_info, VK_LOADER_DATA_CALLBACK, {}}; + callback_info.u.pfnSetDeviceLoaderData = mock_loader_callback; + + VkDeviceCreateInfo device_create_info{}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = &callback_info; + + MockTestLayer layer; + VkDevice device = VK_NULL_HANDLE; + + EXPECT_EQ(layer.CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), VK_SUCCESS); + EXPECT_EQ(device, mock_device_handle); + EXPECT_TRUE(layer.pre_create_device_called); + EXPECT_TRUE(layer.on_device_created_called); + EXPECT_NE(dispatch_table_manager.GetDeviceDispatchTable(device), nullptr); + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(device), mock_loader_callback); + + layer.DestroyDevice(device, nullptr); + EXPECT_TRUE(layer.pre_destroy_device_called); + EXPECT_TRUE(layer.post_destroy_device_called); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(device), nullptr); + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(device), nullptr); +} + +TEST(LayerBaseTest, CreateDeviceNullHandling) { + LayerBase layer; + VkDevice device = VK_NULL_HANDLE; + auto mock_physical_device = reinterpret_cast(0x5555); + + // Null create info + EXPECT_EQ(layer.CreateDevice(mock_physical_device, nullptr, nullptr, &device), VK_ERROR_INITIALIZATION_FAILED); + + // Missing chain info + VkDeviceCreateInfo device_create_info{}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + EXPECT_EQ(layer.CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, CreateDeviceInvalidInputSafety) { + LayerBase layer; + VkDevice device = VK_NULL_HANDLE; + VkDeviceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + auto mock_untracked_physical_device = reinterpret_cast(0xBAADF00D); + // Untracked physical device must fail cleanly without crashing + EXPECT_EQ(layer.CreateDevice(mock_untracked_physical_device, &create_info, nullptr, &device), VK_ERROR_INITIALIZATION_FAILED); + + // Null device pointer must fail cleanly + EXPECT_EQ(layer.CreateDevice(mock_untracked_physical_device, &create_info, nullptr, nullptr), VK_ERROR_INITIALIZATION_FAILED); + + // VK_NULL_HANDLE physical device must fail cleanly + EXPECT_EQ(layer.CreateDevice(VK_NULL_HANDLE, &create_info, nullptr, &device), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, EnumeratePhysicalDevicesMapping) { + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + void* mock_instance_vtable = reinterpret_cast(0x11223344); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(0x7777); + + dispatch_table_manager.InitInstanceTable(mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDevices") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* property_count, VkPhysicalDevice* physical_devices) -> VkResult { + if (!property_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_devices) { + *property_count = 1; + return VK_SUCCESS; + } + physical_devices[0] = reinterpret_cast(0x7777); + *property_count = 1; + return VK_SUCCESS; + }); + } + return nullptr; + }); + + LayerBase layer; + uint32_t count = 0; + EXPECT_EQ(layer.EnumeratePhysicalDevices(mock_instance, &count, nullptr), VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector devices(count); + EXPECT_EQ(layer.EnumeratePhysicalDevices(mock_instance, &count, devices.data()), VK_SUCCESS); + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device), mock_instance); +} + +TEST(LayerBaseTest, DefaultHooksExecution) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Sample"; + LayerManifest manifest(config); + LayerBase base(&manifest); + EXPECT_EQ(base.GetManifest(), &manifest); + + // Execute default no-op hooks to verify base class behavior + VkInstanceCreateInfo instance_create_info{}; + base.PreCreateInstance(&instance_create_info, nullptr); + base.PostCreateInstance(VK_NULL_HANDLE, &instance_create_info); + base.PreDestroyInstance(VK_NULL_HANDLE, nullptr); + base.PostDestroyInstance(VK_NULL_HANDLE); + + VkDeviceCreateInfo device_create_info{}; + base.PreCreateDevice(VK_NULL_HANDLE, &device_create_info, nullptr); + base.OnDeviceCreated(VK_NULL_HANDLE, VK_NULL_HANDLE, &device_create_info); + base.PreDestroyDevice(VK_NULL_HANDLE, nullptr); + base.PostDestroyDevice(VK_NULL_HANDLE); +} + +TEST(LayerBaseTest, EnumeratePhysicalDeviceGroupsMapping) { + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + + void* mock_instance_vtable = reinterpret_cast(0x11223344); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device1 = reinterpret_cast(0x8881); + auto mock_physical_device2 = reinterpret_cast(0x8882); + + dispatch_table_manager.InitInstanceTable(mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDeviceGroups") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* property_count, VkPhysicalDeviceGroupProperties* physical_device_groups) -> VkResult { + if (!property_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_device_groups) { + *property_count = 1; + return VK_SUCCESS; + } + physical_device_groups[0].physicalDeviceCount = 2; + physical_device_groups[0].physicalDevices[0] = reinterpret_cast(0x8881); + physical_device_groups[0].physicalDevices[1] = reinterpret_cast(0x8882); + *property_count = 1; + return VK_SUCCESS; + }); + } + return nullptr; + }); + + LayerBase layer; + void* missing_instance_vtable = reinterpret_cast(0xBAADF00D); + auto missing_instance = reinterpret_cast(&missing_instance_vtable); + // Missing table or function + EXPECT_EQ(layer.EnumeratePhysicalDeviceGroups(missing_instance, nullptr, nullptr), VK_ERROR_INITIALIZATION_FAILED); + + uint32_t count = 0; + EXPECT_EQ(layer.EnumeratePhysicalDeviceGroups(mock_instance, &count, nullptr), VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector groups(count); + EXPECT_EQ(layer.EnumeratePhysicalDeviceGroups(mock_instance, &count, groups.data()), VK_SUCCESS); + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device1), mock_instance); + EXPECT_EQ(tracker.GetVkInstance(mock_physical_device2), mock_instance); +} + +TEST(LayerBaseTest, EnumeratePhysicalDevicesNullTable) { + LayerBase layer; + void* missing_instance_vtable = reinterpret_cast(0xBAADF00D); + auto missing_instance = reinterpret_cast(&missing_instance_vtable); + uint32_t count = 0; + EXPECT_EQ(layer.EnumeratePhysicalDevices(missing_instance, &count, nullptr), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, CreateInstanceNullFpCreateInstance) { + static PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char*) -> PFN_vkVoidFunction { + return nullptr; + }; + VkLayerInstanceLink layer_link{nullptr, mock_get_instance_proc_addr, nullptr}; + VkLayerInstanceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = &chain_info; + + LayerBase layer; + VkInstance instance = VK_NULL_HANDLE; + EXPECT_EQ(layer.CreateInstance(&instance_create_info, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, CreateDeviceNullFpCreateDevice) { + auto& tracker = DeviceInstanceTracker::Get(); + tracker.Clear(); + void* mock_instance_vtable = reinterpret_cast(0x11223344); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(0x5555); + tracker.SetVkInstance(mock_physical_device, mock_instance); + + static PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char*) -> PFN_vkVoidFunction { + return nullptr; + }; + static PFN_vkGetDeviceProcAddr mock_get_device_proc_addr = [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }; + VkLayerDeviceLink layer_link{nullptr, mock_get_instance_proc_addr, mock_get_device_proc_addr}; + VkLayerDeviceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkDeviceCreateInfo device_create_info{}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = &chain_info; + + LayerBase layer; + VkDevice device = VK_NULL_HANDLE; + EXPECT_EQ(layer.CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LogTest, MacroCompilation) { + VT_LOGI("CommonTest", "Info message test: %d", 123); + VT_LOGW("CommonTest", "Warning message test: %s", "warn"); + VT_LOGE("CommonTest", "Error message test: %f", 3.14); +} + +// ============================================================================= +// LayerBase ProcAddr Dispatch & Template Method Tests +// ============================================================================= + +TEST(LayerBaseTest, ActiveLayerTracking) { + EXPECT_EQ(LayerBase::GetActiveLayer(), nullptr); + { + LayerBase layer; + EXPECT_EQ(LayerBase::GetActiveLayer(), &layer); + } + EXPECT_EQ(LayerBase::GetActiveLayer(), nullptr); + + LayerBase layer1; + EXPECT_EQ(LayerBase::GetActiveLayer(), &layer1); + LayerBase::SetActiveLayer(nullptr); + EXPECT_EQ(LayerBase::GetActiveLayer(), nullptr); + LayerBase::SetActiveLayer(&layer1); + EXPECT_EQ(LayerBase::GetActiveLayer(), &layer1); +} + +TEST(LayerBaseTest, GetKnownFunctionsCommonWithoutManifest) { + LayerBase layer; + EXPECT_NE(layer.GetKnownInstanceFunction("vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkCreateInstance"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkDestroyInstance"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkEnumeratePhysicalDevices"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkEnumeratePhysicalDeviceGroups"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkCreateDevice"), nullptr); + + // Without manifest, manifest-based enumeration functions return nullptr + EXPECT_EQ(layer.GetKnownInstanceFunction("vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_EQ(layer.GetKnownInstanceFunction("vkEnumerateInstanceLayerProperties"), nullptr); + EXPECT_EQ(layer.GetKnownInstanceFunction("vkEnumerateDeviceLayerProperties"), nullptr); + EXPECT_EQ(layer.GetKnownInstanceFunction("vkEnumerateDeviceExtensionProperties"), nullptr); + EXPECT_EQ(layer.GetKnownInstanceFunction("vkNonExistentInstanceFunction"), nullptr); + EXPECT_EQ(layer.GetKnownInstanceFunction(nullptr), nullptr); + + EXPECT_NE(layer.GetKnownDeviceFunction("vkGetDeviceProcAddr"), nullptr); + EXPECT_NE(layer.GetKnownDeviceFunction("vkDestroyDevice"), nullptr); + // Instance commands must return nullptr from GetKnownDeviceFunction + EXPECT_EQ(layer.GetKnownDeviceFunction("vkCreateDevice"), nullptr); + EXPECT_EQ(layer.GetKnownDeviceFunction("vkEnumerateDeviceLayerProperties"), nullptr); + EXPECT_EQ(layer.GetKnownDeviceFunction("vkEnumerateDeviceExtensionProperties"), nullptr); + EXPECT_EQ(layer.GetKnownDeviceFunction("vkNonExistentDeviceFunction"), nullptr); + EXPECT_EQ(layer.GetKnownDeviceFunction(nullptr), nullptr); +} + +TEST(LayerBaseTest, GetKnownFunctionsCommonWithManifest) { + LayerManifest::Config config; + config.layer_name = "VK_LAYER_TEST_Common"; + LayerManifest manifest(config); + LayerBase layer(&manifest); + + EXPECT_NE(layer.GetKnownInstanceFunction("vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkEnumerateInstanceLayerProperties"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkEnumerateDeviceLayerProperties"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkEnumerateDeviceExtensionProperties"), nullptr); + EXPECT_EQ(layer.GetKnownDeviceFunction("vkEnumerateDeviceLayerProperties"), nullptr); + EXPECT_EQ(layer.GetKnownDeviceFunction("vkEnumerateDeviceExtensionProperties"), nullptr); +} + +class TestDerivedLayer : public LayerBase { + public: + static inline auto mock_custom_inst_func = reinterpret_cast(0x12345678); + static inline auto mock_custom_dev_func = reinterpret_cast(0x87654321); + + protected: + PFN_vkVoidFunction GetLayerSpecificInstanceFunction(const char* name) override { + if (std::strcmp(name, "vkCustomInstanceCmd") == 0) { + return mock_custom_inst_func; + } + return nullptr; + } + + PFN_vkVoidFunction GetLayerSpecificDeviceFunction(const char* name) override { + if (std::strcmp(name, "vkCustomDeviceCmd") == 0) { + return mock_custom_dev_func; + } + return nullptr; + } +}; + +TEST(LayerBaseTest, LayerSpecificOverrideHooks) { + TestDerivedLayer layer; + + // Custom functions handled by virtual hooks + EXPECT_EQ(layer.GetKnownInstanceFunction("vkCustomInstanceCmd"), TestDerivedLayer::mock_custom_inst_func); + EXPECT_EQ(layer.GetKnownDeviceFunction("vkCustomDeviceCmd"), TestDerivedLayer::mock_custom_dev_func); + + // Common functions still handled by base template method fallback + EXPECT_NE(layer.GetKnownInstanceFunction("vkCreateInstance"), nullptr); + EXPECT_NE(layer.GetKnownInstanceFunction("vkCreateDevice"), nullptr); + EXPECT_NE(layer.GetKnownDeviceFunction("vkDestroyDevice"), nullptr); + EXPECT_EQ(layer.GetKnownDeviceFunction("vkCreateDevice"), nullptr); + + // Unhandled functions return nullptr + EXPECT_EQ(layer.GetKnownInstanceFunction("vkUnknownCmd"), nullptr); + EXPECT_EQ(layer.GetKnownDeviceFunction("vkUnknownCmd"), nullptr); +} + +TEST(LayerBaseTest, ProcAddrDispatchChain) { + TestDerivedLayer layer; + auto& dispatch_table_manager = DispatchTableManager::Get(); + dispatch_table_manager.Clear(); + + // 1. Global commands can be queried with VK_NULL_HANDLE + EXPECT_NE(layer.GetInstanceProcAddr(VK_NULL_HANDLE, "vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(layer.GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"), nullptr); + + // Non-global commands must return nullptr when instance is VK_NULL_HANDLE + EXPECT_EQ(layer.GetInstanceProcAddr(VK_NULL_HANDLE, "vkDestroyInstance"), nullptr); + EXPECT_EQ(layer.GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDevice"), nullptr); + EXPECT_EQ(layer.GetInstanceProcAddr(VK_NULL_HANDLE, "vkCustomInstanceCmd"), nullptr); + EXPECT_EQ(layer.GetInstanceProcAddr(VK_NULL_HANDLE, "vkCustomDeviceCmd"), nullptr); + EXPECT_EQ(layer.GetInstanceProcAddr(VK_NULL_HANDLE, "vkNextLayerCmd"), nullptr); + + // GetDeviceProcAddr with VK_NULL_HANDLE must always return nullptr + EXPECT_EQ(layer.GetDeviceProcAddr(VK_NULL_HANDLE, "vkGetDeviceProcAddr"), nullptr); + EXPECT_EQ(layer.GetDeviceProcAddr(VK_NULL_HANDLE, "vkDestroyDevice"), nullptr); + EXPECT_EQ(layer.GetDeviceProcAddr(VK_NULL_HANDLE, "vkCreateDevice"), nullptr); + EXPECT_EQ(layer.GetDeviceProcAddr(VK_NULL_HANDLE, "vkCustomDeviceCmd"), nullptr); + EXPECT_EQ(layer.GetDeviceProcAddr(VK_NULL_HANDLE, "vkNextLayerCmd"), nullptr); + + // 2. Querying with valid instance handle + void* mock_instance_vtable = reinterpret_cast(0x11223344); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + static auto mock_next_inst_cmd = reinterpret_cast(0xABCDEF01); + + dispatch_table_manager.InitInstanceTable(mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkNextLayerInstCmd") == 0) { + return mock_next_inst_cmd; + } + return nullptr; + }); + + EXPECT_EQ(layer.GetInstanceProcAddr(mock_instance, "vkCustomInstanceCmd"), TestDerivedLayer::mock_custom_inst_func); + EXPECT_EQ(layer.GetInstanceProcAddr(mock_instance, "vkCustomDeviceCmd"), TestDerivedLayer::mock_custom_dev_func); + EXPECT_NE(layer.GetInstanceProcAddr(mock_instance, "vkCreateDevice"), nullptr); + EXPECT_NE(layer.GetInstanceProcAddr(mock_instance, "vkDestroyInstance"), nullptr); + EXPECT_EQ(layer.GetInstanceProcAddr(mock_instance, "vkNextLayerInstCmd"), mock_next_inst_cmd); + EXPECT_EQ(layer.GetInstanceProcAddr(mock_instance, "vkUnimplementedCmd"), nullptr); + + // 3. Querying with valid device handle + void* mock_device_vtable = reinterpret_cast(0x55667788); + auto mock_device = reinterpret_cast(&mock_device_vtable); + static auto mock_next_dev_cmd = reinterpret_cast(0xABCDEF02); + + dispatch_table_manager.InitDeviceTable(mock_device, [](VkDevice, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkNextLayerDevCmd") == 0) { + return mock_next_dev_cmd; + } + return nullptr; + }); + + EXPECT_EQ(layer.GetDeviceProcAddr(mock_device, "vkCustomDeviceCmd"), TestDerivedLayer::mock_custom_dev_func); + EXPECT_NE(layer.GetDeviceProcAddr(mock_device, "vkGetDeviceProcAddr"), nullptr); + EXPECT_NE(layer.GetDeviceProcAddr(mock_device, "vkDestroyDevice"), nullptr); + // Instance commands must return nullptr via GetDeviceProcAddr even with valid device + EXPECT_EQ(layer.GetDeviceProcAddr(mock_device, "vkCreateDevice"), nullptr); + EXPECT_EQ(layer.GetDeviceProcAddr(mock_device, "vkDestroyInstance"), nullptr); + EXPECT_EQ(layer.GetDeviceProcAddr(mock_device, "vkNextLayerDevCmd"), mock_next_dev_cmd); + EXPECT_EQ(layer.GetDeviceProcAddr(mock_device, "vkUnimplementedCmd"), nullptr); +} diff --git a/layersvt/test/test_debugmarker.cpp b/layersvt/test/test_debugmarker.cpp index e1f2325d2f..43fd0afa7f 100644 --- a/layersvt/test/test_debugmarker.cpp +++ b/layersvt/test/test_debugmarker.cpp @@ -62,3 +62,148 @@ TEST_F(DebugMarkerTests, CombinedTest) { DebugMarker::Get().Clear(); 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"); + + const auto* manifest = DebugMarker::Get().GetManifest(); + ASSERT_NE(manifest, nullptr); + EXPECT_STREQ(manifest->GetConfig().layer_name, kLayerName); + EXPECT_STREQ(manifest->GetConfig().description, "layer: DebugMarker"); + + // Test EnumerateInstanceLayerProperties + uint32_t property_count = 0; + VkResult result = manifest->EnumerateInstanceLayerProperties(&property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + VkLayerProperties layer_properties{}; + result = manifest->EnumerateInstanceLayerProperties(&property_count, &layer_properties); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(layer_properties.layerName, kLayerName); + + // Test EnumerateDeviceLayerProperties + property_count = 0; + result = manifest->EnumerateDeviceLayerProperties(&property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + // Test EnumerateInstanceExtensionProperties + property_count = 0; + result = manifest->EnumerateInstanceExtensionProperties(kLayerName, &property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + std::vector extensions(property_count); + result = manifest->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 = manifest->EnumerateInstanceExtensionProperties("VK_LAYER_NONEXISTENT", &property_count, nullptr); + EXPECT_EQ(result, VK_ERROR_LAYER_NOT_PRESENT); + + // Test EnumerateDeviceExtensionProperties + property_count = 0; + result = manifest->EnumerateDeviceExtensionProperties(VK_NULL_HANDLE, kLayerName, &property_count, nullptr, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + extensions.resize(property_count); + result = manifest->EnumerateDeviceExtensionProperties(VK_NULL_HANDLE, kLayerName, &property_count, extensions.data(), nullptr); + 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 DeviceInstanceTracker integration"); + + auto& marker = DebugMarker::Get(); + EXPECT_NE(marker.GetManifest(), nullptr); + EXPECT_STREQ(marker.GetManifest()->GetConfig().layer_name, kLayerName); + + // Verify SetVkInstance / GetVkInstance forward to DeviceInstanceTracker + VkPhysicalDevice mock_physical_device = reinterpret_cast(0x1234); + VkInstance mock_instance = reinterpret_cast(0x5678); + + marker.SetVkInstance(mock_physical_device, mock_instance); + EXPECT_EQ(marker.GetVkInstance(mock_physical_device), mock_instance); + + marker.Clear(); + EXPECT_EQ(marker.GetVkInstance(mock_physical_device), VK_NULL_HANDLE); +} + +TEST_F(DebugMarkerTests, PostDestroyDeviceCleanupTest) { + TEST_DESCRIPTION("Verify that PostDestroyDevice cleans up tracked objects associated with that device"); + + auto& marker = DebugMarker::Get(); + marker.Clear(); + + VkDevice dev1 = reinterpret_cast(0x1000); + VkDevice dev2 = reinterpret_cast(0x2000); + + marker.SetDebugObjectName((uint64_t)dev1, VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1"); + marker.SetDebugObjectName((uint64_t)dev2, VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2"); + + EXPECT_TRUE(marker.HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1")); + EXPECT_TRUE(marker.HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); + + // Destroy dev1 - should remove Buffer1 but keep Buffer2 + marker.PostDestroyDevice(dev1); + + EXPECT_FALSE(marker.HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1")); + EXPECT_TRUE(marker.HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); + + // Destroy dev2 - should remove Buffer2 + marker.PostDestroyDevice(dev2); + EXPECT_FALSE(marker.HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); +} + +TEST_F(DebugMarkerTests, TemplateMethodDispatchTest) { + TEST_DESCRIPTION("Verify DebugMarker layer-specific hooks and LayerBase template method dispatching"); + + auto& marker = DebugMarker::Get(); + + // Layer-specific instance commands intercepted + EXPECT_NE(marker.GetKnownInstanceFunction("vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(marker.GetKnownInstanceFunction("vkDestroyDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(marker.GetKnownInstanceFunction("vkSubmitDebugUtilsMessageEXT"), nullptr); + + // Common lifecycle instance commands resolved via LayerBase fallback + EXPECT_NE(marker.GetKnownInstanceFunction("vkCreateInstance"), nullptr); + EXPECT_NE(marker.GetKnownInstanceFunction("vkDestroyInstance"), nullptr); + EXPECT_NE(marker.GetKnownInstanceFunction("vkEnumeratePhysicalDevices"), nullptr); + EXPECT_NE(marker.GetKnownInstanceFunction("vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(marker.GetKnownInstanceFunction("vkCreateDevice"), nullptr); + + // Layer-specific device commands intercepted + EXPECT_NE(marker.GetKnownDeviceFunction("vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_NE(marker.GetKnownDeviceFunction("vkCmdBeginDebugUtilsLabelEXT"), nullptr); + EXPECT_NE(marker.GetKnownDeviceFunction("vkSetDebugUtilsObjectNameEXT"), nullptr); + + // Common lifecycle device commands resolved via LayerBase fallback + EXPECT_EQ(marker.GetKnownDeviceFunction("vkCreateDevice"), nullptr); + EXPECT_NE(marker.GetKnownDeviceFunction("vkDestroyDevice"), nullptr); + EXPECT_NE(marker.GetKnownDeviceFunction("vkGetDeviceProcAddr"), nullptr); + + // Global commands resolvable with VK_NULL_HANDLE via GetInstanceProcAddr + EXPECT_NE(marker.GetInstanceProcAddr(VK_NULL_HANDLE, "vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(marker.GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"), nullptr); + EXPECT_NE(marker.GetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(marker.GetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"), nullptr); + + // Non-global commands return nullptr when passed VK_NULL_HANDLE + EXPECT_EQ(marker.GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_EQ(marker.GetInstanceProcAddr(VK_NULL_HANDLE, "vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_EQ(marker.GetDeviceProcAddr(VK_NULL_HANDLE, "vkCmdBeginDebugUtilsLabelEXT"), nullptr); + EXPECT_EQ(marker.GetInstanceProcAddr(VK_NULL_HANDLE, "vkNonExistentCmd"), nullptr); + EXPECT_EQ(marker.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(marker.GetInstanceProcAddr(mock_instance, "vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(marker.GetInstanceProcAddr(mock_instance, "vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_NE(marker.GetDeviceProcAddr(mock_device, "vkCmdBeginDebugUtilsLabelEXT"), nullptr); +}