diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 92538d5e3a..ae8c8844ec 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) @@ -116,7 +118,7 @@ if(BUILD_SCREENSHOT) vk_layer_table.cpp vk_layer_table.h screenshot/screenshot_layer.md - layer_keep_alive.cpp + common/layer_keep_alive.cpp screenshot/json/VkLayer_screenshot.json.in ) endif() @@ -136,7 +138,7 @@ if(BUILD_CPUTIMING) ../scripts/generators/cputiming_generator.py vk_layer_table.cpp vk_layer_table.h - layer_keep_alive.cpp + common/layer_keep_alive.cpp cpu_timing/VkLayer_CPUTiming.json.in ) @@ -178,7 +180,7 @@ if(BUILD_DEBUGMARKER) perfetto/perfetto.cc vk_layer_table.cpp vk_layer_table.h - layer_keep_alive.cpp + common/layer_keep_alive.cpp debug_marker/VkLayer_DebugMarker.json.in ) @@ -214,7 +216,7 @@ if(BUILD_DEVICEMEMORYREPORT) perfetto/perfetto.cc vk_layer_table.cpp vk_layer_table.h - layer_keep_alive.cpp + common/layer_keep_alive.cpp device_memory_report/VkLayer_DeviceMemoryReport.json.in ) diff --git a/layersvt/README.md b/layersvt/README.md index 7e870e2a68..44a9a040f5 100644 --- a/layersvt/README.md +++ b/layersvt/README.md @@ -22,6 +22,7 @@ Layers are activated at vkCreateInstance time. Layers can also be activated via Note that some layers are code-generated and will therefore exist in the directory (build_dir)/layers -include/vkLayer.h - header file for layer code. +- [common/README.md](common/README.md) - Common layer foundation library (`layersvt_common`) and guide for creating new layers. ### Print API Calls and Parameter Values (build dir)/layers/api_dump.cpp (name=VK_LAYER_LUNARG_api_dump) - print out API calls along with parameter values diff --git a/layersvt/common/CMakeLists.txt b/layersvt/common/CMakeLists.txt new file mode 100644 index 0000000000..64f74a84ed --- /dev/null +++ b/layersvt/common/CMakeLists.txt @@ -0,0 +1,55 @@ +# 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 + dispatch_table_manager.h + dispatch_table_manager.cpp + layer_manifest.h + layer_manifest.cpp + layer_base.h + layer_base.cpp + dispatch_downstream.h +) + +set_target_properties(layersvt_common PROPERTIES + FOLDER "layers/common" + POSITION_INDEPENDENT_CODE ON +) + +target_include_directories(layersvt_common PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +target_link_libraries(layersvt_common PUBLIC + Vulkan::Headers + Vulkan::UtilityHeaders +) +if (ANDROID) + target_sources(layersvt_common PRIVATE + layer_keep_alive.cpp + ) + target_link_libraries(layersvt_common PUBLIC ${CMAKE_DL_LIBS}) +endif() + +add_library(layersvt_entrypoints OBJECT + layer_entrypoints.cpp +) + +set_target_properties(layersvt_entrypoints PROPERTIES + FOLDER "layers/common" +) + +target_link_libraries(layersvt_entrypoints PRIVATE + layersvt_common +) diff --git a/layersvt/common/README.md b/layersvt/common/README.md new file mode 100644 index 0000000000..3031a99311 --- /dev/null +++ b/layersvt/common/README.md @@ -0,0 +1,308 @@ +# VulkanTools Common Layer Foundation (`layersvt_common`) + +The `layersvt_common` library provides a modern, thread-safe C++ foundation for developing Vulkan layers in the `VulkanTools` repository. It eliminates repetitive Vulkan loader dispatch boilerplate, centralizes dispatch table and device lifecycle management, and guarantees standard loader compliance across Android, Linux, and Windows. + +--- + +## 1. Architectural Overview + +``` + +-----------------------------------+ + | Vulkan Loader / Application | + +-----------------------------------+ + | + v + +-----------------------------------+ + | layersvt_entrypoints | + | (Exported C ABI vkGet*ProcAddr) | + +-----------------------------------+ + | + v + +-----------------------------------+ + | LayerBase (Singleton) | + | Template Method Dispatch Engine | + +-----------------------------------+ + / \ + v v + +---------------+ +--------------------+ + | LayerManifest | | DispatchTable- | + | Declarative | | Manager | + | Metadata | | Dispatch Tables & | + | & Extensions | | Physical Device | + | | | Tracking | + +---------------+ +--------------------+ + | + v + +-----------------------------------+ + | dispatch_downstream.h | + | (Template Metaprogrammed Forward) | + +-----------------------------------+ + | + v + +-----------------------------------+ + | Next Layer / Vulkan Driver | + +-----------------------------------+ +``` + +### Key Components + +* **`LayerBase`** ([`layer_base.h`](layer_base.h), [`layer_base.cpp`](layer_base.cpp)): + Base class employing the **Template Method** design pattern. It implements static intercept entry points (`vkGetInstanceProcAddr`, `vkGetDeviceProcAddr`, `vkCreateInstance`, `vkDestroyInstance`, `vkCreateDevice`, `vkDestroyDevice`, `vkEnumeratePhysicalDevices`, `vkEnumeratePhysicalDeviceGroups`, `vkEnumerateInstanceExtensionProperties`, `vkEnumerateDeviceExtensionProperties`, `vkGetPhysicalDeviceToolProperties`), executes virtual lifecycle and extension/tooling hooks (`ProcessDeviceExtensions`, `ProcessInstanceExtensions`, `ProcessToolProperties`), and delegates custom functions to derived layer overrides. +* **`LayerManifest`** ([`layer_manifest.h`](layer_manifest.h), [`layer_manifest.cpp`](layer_manifest.cpp)): + Passive declarative data struct describing layer metadata, supported Vulkan versions, advertised instance/device extensions, and tooling properties (`VK_EXT_tooling_info` / `VK_VERSION_1_3`). Downstream Vulkan querying, buffer sizing, and capability merging are managed by `LayerBase`. +* **`DispatchTableManager`** ([`dispatch_table_manager.h`](dispatch_table_manager.h), [`dispatch_table_manager.cpp`](dispatch_table_manager.cpp)): + Thread-safe registry for `VkuInstanceDispatchTable` and `VkuDeviceDispatchTable` keyed by dispatchable handle. Incorporates native `VkPhysicalDevice` to parent `VkInstance` tracking and single-lock atomic teardown during instance destruction. Tracks and forwards `VK_LOADER_DATA_CALLBACK` to initialize dispatchable handles created internally by layers. +* **`dispatch_downstream.h`** ([`dispatch_downstream.h`](dispatch_downstream.h)): + Header-only template metaprogramming helpers (`DispatchDownstream`, `DispatchDownstreamOr`, `DispatchDownstreamOrSuccess`) that deduce table types at compile time and forward commands downstream. +* **`layersvt_entrypoints`** ([`layer_entrypoints.cpp`](layer_entrypoints.cpp)): + CMake `OBJECT` library that exports standard C symbols (`vkGetInstanceProcAddr`, `vkGetDeviceProcAddr`, `vkNegotiateLoaderLayerInterfaceVersion`, and the four Android loader enumeration entry points) without macro duplication. + +--- + +## 2. Step-by-Step: Adding a New Layer + +Follow this 5-step guide to add a new layer (e.g. `MyCustomLayer`). + +### Step 1: Declare the Layer Class (`my_custom_layer.h`) + +Inherit from `layersvt::LayerBase`. Shadow `LayerBase::Get()` to return your derived layer instance, and override only the lifecycle hooks and command intercepts your layer needs: + +```cpp +#pragma once + +#include +#include + +namespace layersvt { + +class MyCustomLayer : public LayerBase { + public: + static MyCustomLayer& Get(); + + protected: + // Return declarative manifest + const LayerManifest* GetLayerManifest() const override; + + // Intercept custom or extension commands + PFN_vkVoidFunction GetLayerInstanceCommand(const char* command_name) override; + PFN_vkVoidFunction GetLayerDeviceCommand(const char* command_name) override; + + // Lifecycle hooks (override as needed) + void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator) override; + void PostCreateInstance(VkInstance instance, const VkInstanceCreateInfo* create_info, + const VkAllocationCallbacks* allocator) override; + void PreDestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) override; + + void PreCreateDevice(VkPhysicalDevice physical_device, VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator) override; + void PostCreateDevice(VkDevice device, VkPhysicalDevice physical_device, + const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator) override; + void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) override; +}; + +} // namespace layersvt +``` + +### Step 2: Implement the Layer (`my_custom_layer.cpp`) + +Implement the layer methods, configure the static manifest, and implement custom intercepted commands: + +```cpp +#include "my_custom_layer.h" +#include + +#include + +namespace layersvt { + +namespace { + +// 1. Static layer instance (instantiated in the shared library entrypoint, not in files linked to unit tests) +MyCustomLayer g_layer; + +// 2. Declarative layer manifest +const LayerManifest kManifest({ + .layer_name = "VK_LAYER_GOOGLE_MyCustomLayer", + .description = "Google Vulkan MyCustomLayer", + .spec_version = VK_API_VERSION_1_3, + .implementation_version = 1, + .instance_extensions = {}, + .device_extensions = { + VkExtensionProperties{"VK_EXT_custom_extension", 1}, + VkExtensionProperties{VK_EXT_TOOLING_INFO_EXTENSION_NAME, VK_EXT_TOOLING_INFO_SPEC_VERSION}, + }, + .tool_properties = VkPhysicalDeviceToolPropertiesEXT{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, + .pNext = nullptr, + .name = "MyCustomLayer", + .version = "1.0", + .purposes = VK_TOOL_PURPOSE_PROFILING_BIT_EXT, + .description = "Google Vulkan MyCustomLayer", + .layer = "VK_LAYER_GOOGLE_MyCustomLayer", + }, +}); + +// Custom intercepted Vulkan command +VKAPI_ATTR void VKAPI_CALL Hook_vkCmdDraw(VkCommandBuffer command_buffer, uint32_t vertex_count, + uint32_t instance_count, uint32_t first_vertex, + uint32_t first_instance) { + // Custom layer logic before dispatch ... + + // Forward downstream to the next layer/driver + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::CmdDraw>( + command_buffer, vertex_count, instance_count, first_vertex, first_instance); + + // Custom layer logic after dispatch ... +} + +} // namespace + +MyCustomLayer& MyCustomLayer::Get() { + assert(LayerBase::Get() != nullptr); + return *static_cast(LayerBase::Get()); +} + +const LayerManifest* MyCustomLayer::GetLayerManifest() const { + return &kManifest; +} + +PFN_vkVoidFunction MyCustomLayer::GetLayerDeviceCommand(const char* command_name) { + assert(command_name != nullptr); + if (std::strcmp(command_name, "vkCmdDraw") == 0) { + return reinterpret_cast(Hook_vkCmdDraw); + } + return nullptr; +} + +PFN_vkVoidFunction MyCustomLayer::GetLayerInstanceCommand(const char* /*command_name*/) { + return nullptr; +} + +void MyCustomLayer::PostCreateDevice(VkDevice device, VkPhysicalDevice physical_device, + const VkDeviceCreateInfo* /*create_info*/, + const VkAllocationCallbacks* /*allocator*/) { + // Setup per-device state ... +} + +void MyCustomLayer::PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* /*allocator*/) { + // Teardown per-device state before downstream destruction ... +} + +} // namespace layersvt +``` + +### Step 3: Configure CMake Target (`layersvt/CMakeLists.txt`) + +Add the shared library module target, linking `layersvt_common` and including `$`: + +```cmake +add_library(VkLayer_MyCustomLayer MODULE + my_custom_layer/my_custom_layer.cpp + my_custom_layer/my_custom_layer.h + $ +) + +target_include_directories(VkLayer_MyCustomLayer PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/common +) + +target_link_libraries(VkLayer_MyCustomLayer PRIVATE + layersvt_common + Vulkan::Headers + Vulkan::LayerSettings +) + +# Configure output filename and definition file on Windows +if (WIN32) + target_sources(VkLayer_MyCustomLayer PRIVATE my_custom_layer/VkLayer_MyCustomLayer.def) +endif() + +list(APPEND TOOL_LAYERS "VkLayer_MyCustomLayer") +``` + +### Step 4: JSON Manifest and Windows DEF + +1. **`my_custom_layer/VkLayer_MyCustomLayer.json.in`**: + Standard Vulkan layer manifest template configured by CMake: + ```json + { + "file_format_version": "1.2.0", + "layer": { + "name": "VK_LAYER_GOOGLE_MyCustomLayer", + "type": "GLOBAL", + "library_path": "@JSON_LIBRARY_PATH@", + "api_version": "1.3.0", + "implementation_version": "1", + "description": "Google Vulkan MyCustomLayer" + } + } + ``` +2. **`my_custom_layer/VkLayer_MyCustomLayer.def`** (Windows): + Export entry point symbols: + ```def + LIBRARY VkLayer_MyCustomLayer + EXPORTS + vkGetInstanceProcAddr + vkGetDeviceProcAddr + vkNegotiateLoaderLayerInterfaceVersion + vkEnumerateInstanceExtensionProperties + vkEnumerateInstanceLayerProperties + vkEnumerateDeviceExtensionProperties + vkEnumerateDeviceLayerProperties + ``` + +### Step 5: Add Unit Tests (`layersvt/test/test_mycustomlayer.cpp`) + +Write unit tests using GoogleTest and the test framework. To keep tests hermetic and restore clean state across fixtures, reset the global layer instance: + +```cpp +#include +#include "common/layer_base_test_peer.h" +#include "layer_test_helper.h" +#include "my_custom_layer/my_custom_layer.h" + +namespace layersvt { + +class MyCustomLayerTest : public ::testing::Test { + protected: + void SetUp() override { + // Re-create layer to restore pristine state + // (destructor cleans up tracker and dispatch table entries) + layer_test::ResetLayer(); + } +}; + +TEST_F(MyCustomLayerTest, ManifestValidation) { + const LayerManifest* manifest = LayerBaseTestPeer::GetLayerManifest(MyCustomLayer::Get()); + ASSERT_NE(manifest, nullptr); + EXPECT_STREQ(manifest->layer_name, "VK_LAYER_GOOGLE_MyCustomLayer"); +} + +} // namespace layersvt +``` + +Register the test in `layersvt/test/CMakeLists.txt`: +```cmake +LayerTest(MyCustomLayer) +target_sources(test_MyCustomLayer_layer PRIVATE ../my_custom_layer/my_custom_layer.cpp) +target_link_libraries(test_MyCustomLayer_layer layersvt_common) +``` +> **Note**: +> - Do **not** link `layersvt_entrypoints` into test executables that link `Vulkan::Loader`; tests link directly to `layersvt_common` and invoke `LayerBase` APIs via `LayerBaseTestPeer` to avoid symbol collisions with `Vulkan::Loader`. + +--- + +## 3. Best Practices & Conventions + +1. **Do Not Reimplement Common Dispatch Boilerplate**: + Never manually parse `VkLayerInstanceCreateInfo` / `VkLayerDeviceCreateInfo` link chains or allocate raw dispatch tables. `LayerBase` automatically unwraps loader chains, initializes `DispatchTableManager`, and tracks physical devices. +2. **Use `DispatchDownstream` for Downstream Forwarding**: + Use `DispatchDownstream<&VkuDeviceDispatchTable::CmdDraw>(...)` to invoke the next layer or driver. It automatically deduces instance vs. device dispatch tables from the member pointer at compile time and asserts that tables and function pointers are non-null. +3. **Null Handle Destruction is Safe**: + `LayerBase::DestroyInstance` and `LayerBase::DestroyDevice` immediately return on `VK_NULL_HANDLE` per Vulkan specification (Section 2.7), bypassing downstream dispatch and virtual hooks. Virtual `PreDestroy*` hooks are guaranteed to receive only valid non-null handles. +4. **Tool Properties Downstream Initialization**: + When querying downstream tooling properties in custom commands, always initialize `tool.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT` and `tool.pNext = nullptr` on each array element before passing buffers to downstream functions to satisfy Vulkan VUIDs. +5. **Thread Safety**: + `DispatchTableManager` is fully synchronized via mutexes. Layer-specific global states must similarly protect their own internal maps. Avoid recursive locks across downstream dispatch invocations. diff --git a/layersvt/common/dispatch_downstream.h b/layersvt/common/dispatch_downstream.h new file mode 100644 index 0000000000..10ade441d2 --- /dev/null +++ b/layersvt/common/dispatch_downstream.h @@ -0,0 +1,106 @@ +/* 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 "layer_base.h" +#include +#include +#include +#include +#include + +namespace layersvt { + +// Trait helper to extract the class type from a member pointer +template +struct MemberTraits { + static_assert(std::is_member_pointer_v, + "MemberPointer must be a pointer to a member of VkuInstanceDispatchTable or VkuDeviceDispatchTable"); +}; + +template +struct MemberTraits { + using ClassType = Class; + using MemberType = Member; +}; + +/** + * Forwards a required Vulkan command downstream using the dispatch table. + * Deduces instance vs. device table and asserts the entry point is non-null. + * Returns the result of calling the downstream Vulkan command. + */ +template +inline auto DispatchDownstream(Handle handle, Args&&... args) { + using TableType = typename MemberTraits::ClassType; + static_assert(std::is_same_v || + std::is_same_v, + "MemberPointer must be a member of VkuInstanceDispatchTable or VkuDeviceDispatchTable"); + TableType* table = nullptr; + if constexpr (std::is_same_v) { + table = LayerBase::GetInstanceDispatchTable(handle); + } else { + table = LayerBase::GetDeviceDispatchTable(handle); + } + + assert(table != nullptr && "Dispatch table must exist for valid handle"); + assert(table->*MemberPointer != nullptr && "Function pointer must exist in dispatch table"); + return (table->*MemberPointer)(handle, std::forward(args)...); +} + +/** + * Forwards a Vulkan command downstream, returning or invoking fallback if the entry point is null. + * Returns the downstream command result on success, or the evaluated fallback value if the table + * or command pointer is unavailable. + */ +template +inline auto DispatchDownstreamOr(Fallback&& fallback, Handle handle, Args&&... args) { + using TableType = typename MemberTraits::ClassType; + static_assert(std::is_same_v || + std::is_same_v, + "MemberPointer must be a member of VkuInstanceDispatchTable or VkuDeviceDispatchTable"); + using ReturnType = decltype((std::declval()->*MemberPointer)(handle, std::forward(args)...)); + TableType* table = nullptr; + if constexpr (std::is_same_v) { + table = LayerBase::GetInstanceDispatchTable(handle); + } else { + table = LayerBase::GetDeviceDispatchTable(handle); + } + + if (table && table->*MemberPointer) { + return static_cast((table->*MemberPointer)(handle, std::forward(args)...)); + } + + if constexpr (std::is_invocable_v) { + return static_cast(std::forward(fallback)()); + } else { + return static_cast(std::forward(fallback)); + } +} + +/** + * Forwards an optional Vulkan command returning VkResult downstream, falling back to VK_SUCCESS. + * Returns the downstream VkResult on success, or VK_SUCCESS if the downstream command is unavailable. + */ +template +inline VkResult DispatchDownstreamOrSuccess(Handle handle, Args&&... args) { + using TableType = typename MemberTraits::ClassType; + using ReturnType = decltype((std::declval()->*MemberPointer)(handle, std::forward(args)...)); + static_assert(std::is_same_v, + "DispatchDownstreamOrSuccess can only be used with Vulkan commands returning VkResult"); + return DispatchDownstreamOr(VK_SUCCESS, handle, std::forward(args)...); +} + +} // namespace layersvt diff --git a/layersvt/common/dispatch_table_manager.cpp b/layersvt/common/dispatch_table_manager.cpp new file mode 100644 index 0000000000..87519264c6 --- /dev/null +++ b/layersvt/common/dispatch_table_manager.cpp @@ -0,0 +1,163 @@ +/* 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 +#include +#include + +namespace layersvt { + +VkuInstanceDispatchTable* DispatchTableManager::InitInstanceTable(VkInstance instance, + PFN_vkGetInstanceProcAddr get_instance_proc_addr) { + assert(instance != VK_NULL_HANDLE); + assert(get_instance_proc_addr != nullptr); + auto table = std::make_unique(); + vkuInitInstanceDispatchTable(instance, table.get(), get_instance_proc_addr); + + Key key = GetDispatchKey(instance); + std::lock_guard lock(instance_mutex_); + auto [iterator, inserted] = instance_tables_.try_emplace(key, std::move(table)); + instance_keys_[key] = instance; + return iterator->second.get(); +} + +VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(VkInstance instance) const { + if (instance == VK_NULL_HANDLE) { + return nullptr; + } + Key key = GetDispatchKey(instance); + std::lock_guard lock(instance_mutex_); + auto table_iterator = instance_tables_.find(key); + if (table_iterator != instance_tables_.end()) { + return table_iterator->second.get(); + } + return nullptr; +} + +VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(VkPhysicalDevice physical_device) const { + if (physical_device == VK_NULL_HANDLE) { + return nullptr; + } + std::lock_guard lock(instance_mutex_); + auto device_iterator = physical_device_to_instance_map_.find(physical_device); + if (device_iterator != physical_device_to_instance_map_.end()) { + Key instance_key = GetDispatchKey(device_iterator->second); + auto table_iterator = instance_tables_.find(instance_key); + if (table_iterator != instance_tables_.end()) { + return table_iterator->second.get(); + } + } + return nullptr; +} + +void DispatchTableManager::DestroyInstanceTable(Key key) { + assert(key != Key{}); + std::lock_guard lock(instance_mutex_); + auto key_iterator = instance_keys_.find(key); + if (key_iterator != instance_keys_.end()) { + VkInstance instance = key_iterator->second; + std::erase_if(physical_device_to_instance_map_, + [instance](const auto& entry) { return entry.second == instance; }); + instance_keys_.erase(key_iterator); + } + instance_tables_.erase(key); +} + +void DispatchTableManager::SetVkInstance(VkPhysicalDevice physical_device, VkInstance instance) { + assert(physical_device != VK_NULL_HANDLE); + assert(instance != VK_NULL_HANDLE); + std::lock_guard lock(instance_mutex_); + physical_device_to_instance_map_[physical_device] = instance; +} + +void DispatchTableManager::RegisterPhysicalDevices(const VkPhysicalDevice* physical_devices, uint32_t count, + VkInstance instance) { + if (physical_devices == nullptr || count == 0) { + return; + } + assert(instance != VK_NULL_HANDLE); + std::lock_guard lock(instance_mutex_); + for (uint32_t i = 0; i < count; ++i) { + assert(physical_devices[i] != VK_NULL_HANDLE); + physical_device_to_instance_map_[physical_devices[i]] = instance; + } +} + +VkInstance DispatchTableManager::GetVkInstance(VkPhysicalDevice physical_device) const { + if (physical_device == VK_NULL_HANDLE) { + return VK_NULL_HANDLE; + } + std::lock_guard lock(instance_mutex_); + auto device_iterator = physical_device_to_instance_map_.find(physical_device); + if (device_iterator != physical_device_to_instance_map_.end()) { + return device_iterator->second; + } + return VK_NULL_HANDLE; +} + +VkuDeviceDispatchTable* DispatchTableManager::InitDeviceTable(VkDevice device, PFN_vkGetDeviceProcAddr get_device_proc_addr) { + assert(device != VK_NULL_HANDLE); + assert(get_device_proc_addr != nullptr); + auto table = std::make_unique(); + vkuInitDeviceDispatchTable(device, table.get(), get_device_proc_addr); + + Key key = GetDispatchKey(device); + std::lock_guard lock(device_mutex_); + auto [iterator, inserted] = device_tables_.try_emplace(key, std::move(table)); + return iterator->second.get(); +} + +VkuDeviceDispatchTable* DispatchTableManager::GetDeviceDispatchTable(const void* object) const { + if (object == nullptr) { + return nullptr; + } + Key key = GetDispatchKey(object); + std::lock_guard lock(device_mutex_); + auto table_iterator = device_tables_.find(key); + if (table_iterator != device_tables_.end()) { + return table_iterator->second.get(); + } + return nullptr; +} + +void DispatchTableManager::DestroyDeviceTable(Key key) { + assert(key != Key{}); + std::lock_guard lock(device_mutex_); + device_tables_.erase(key); + loader_callbacks_.erase(key); +} + +void DispatchTableManager::SetDeviceLoaderDataCallback(VkDevice device, PFN_vkSetDeviceLoaderData callback) { + assert(device != VK_NULL_HANDLE); + assert(callback != nullptr); + Key key = GetDispatchKey(device); + std::lock_guard lock(device_mutex_); + loader_callbacks_[key] = callback; +} + +PFN_vkSetDeviceLoaderData DispatchTableManager::GetDeviceLoaderDataCallback(VkDevice device) const { + assert(device != VK_NULL_HANDLE); + Key key = GetDispatchKey(device); + std::lock_guard lock(device_mutex_); + auto callback_iterator = loader_callbacks_.find(key); + if (callback_iterator != loader_callbacks_.end()) { + return callback_iterator->second; + } + return nullptr; +} + +} // namespace layersvt diff --git a/layersvt/common/dispatch_table_manager.h b/layersvt/common/dispatch_table_manager.h new file mode 100644 index 0000000000..db7f312f68 --- /dev/null +++ b/layersvt/common/dispatch_table_manager.h @@ -0,0 +1,153 @@ +/* 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 { + +/** + * Thread-safe manager for Vulkan instance and device dispatch tables and physical device tracking. + * + * Manages dispatch tables keyed by handle dispatch key, maintains associations between + * physical devices and parent instances, and tracks loader data callbacks + * (VK_LOADER_DATA_CALLBACK) to initialize dispatchable objects created internally. + */ +class DispatchTableManager final { + public: + DispatchTableManager() = default; + ~DispatchTableManager() = default; + + enum class Key : uintptr_t {}; + + /** + * Returns the dispatch key (first pointer-sized word) for a dispatchable Vulkan handle. + */ + [[nodiscard]] static Key GetDispatchKey(const void* object) noexcept { + assert(object != nullptr); + return static_cast(reinterpret_cast(*reinterpret_cast(object))); + } + + // Instance dispatch tables + + /** + * Initializes and stores an instance dispatch table using downstream vkGetInstanceProcAddr. + * Returns a non-null pointer to the stored dispatch table. + */ + VkuInstanceDispatchTable* InitInstanceTable(VkInstance instance, PFN_vkGetInstanceProcAddr get_instance_proc_addr); + + /** + * Looks up the instance dispatch table for a given instance handle. + * Returns a pointer to the stored table on success, or nullptr if not registered. + */ + [[nodiscard]] VkuInstanceDispatchTable* GetInstanceDispatchTable(VkInstance instance) const; + + /** + * Looks up the instance dispatch table for a given physical device handle. + * Resolves the parent instance and returns a pointer to its dispatch table on success, or nullptr if unregistered. + */ + [[nodiscard]] VkuInstanceDispatchTable* GetInstanceDispatchTable(VkPhysicalDevice physical_device) const; + + /** + * Overload for nullptr literal to resolve ambiguity between handle types. Always returns nullptr. + */ + [[nodiscard]] VkuInstanceDispatchTable* GetInstanceDispatchTable(std::nullptr_t) const noexcept { + return nullptr; + } + + /** + * Destroys the instance dispatch table and unmaps associated physical devices for the given dispatch key. + * Callers should capture the Key beforehand via GetDispatchKey(...) before + * downstream vkDestroyInstance invalidates the handle. + */ + void DestroyInstanceTable(Key key); + + // Physical device tracking + + /** + * Associates a physical device handle with its parent VkInstance. + */ + void SetVkInstance(VkPhysicalDevice physical_device, VkInstance instance); + + /** + * Associates multiple physical device handles with their parent VkInstance in a single atomic lock. + */ + void RegisterPhysicalDevices(const VkPhysicalDevice* physical_devices, uint32_t count, VkInstance instance); + + /** + * Retrieves the VkInstance associated with a physical device. + * Returns the parent VkInstance on success, or VK_NULL_HANDLE if not registered. + */ + [[nodiscard]] VkInstance GetVkInstance(VkPhysicalDevice physical_device) const; + + // Device dispatch tables + + /** + * Initializes and stores a device dispatch table using downstream vkGetDeviceProcAddr. + * Returns a non-null pointer to the stored dispatch table. + */ + VkuDeviceDispatchTable* InitDeviceTable(VkDevice device, PFN_vkGetDeviceProcAddr get_device_proc_addr); + + /** + * Looks up the device dispatch table for a dispatchable object. + * Returns a pointer to the stored table on success, or nullptr if object is null or unregistered. + */ + [[nodiscard]] VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object) const; + + /** + * Destroys the device dispatch table and loader callback for the given dispatch key. + * Callers should capture the Key beforehand via GetDispatchKey(...) before + * downstream vkDestroyDevice invalidates the handle. + */ + void DestroyDeviceTable(Key key); + + // Loader data callbacks (from VK_LOADER_DATA_CALLBACK) + + /** + * Registers the vkSetDeviceLoaderData callback for a logical device. + */ + void SetDeviceLoaderDataCallback(VkDevice device, PFN_vkSetDeviceLoaderData callback); + + /** + * Retrieves the vkSetDeviceLoaderData callback for a logical device. + * Returns the registered callback on success, or nullptr if unset. + */ + [[nodiscard]] PFN_vkSetDeviceLoaderData GetDeviceLoaderDataCallback(VkDevice device) const; + + private: + DispatchTableManager(const DispatchTableManager&) = delete; + DispatchTableManager& operator=(const DispatchTableManager&) = delete; + DispatchTableManager(DispatchTableManager&&) = delete; + DispatchTableManager& operator=(DispatchTableManager&&) = delete; + + mutable std::mutex instance_mutex_; + std::unordered_map> instance_tables_; + std::unordered_map instance_keys_; + std::unordered_map physical_device_to_instance_map_; + + mutable std::mutex device_mutex_; + std::unordered_map> device_tables_; + std::unordered_map loader_callbacks_; +}; + +} // namespace layersvt diff --git a/layersvt/common/layer_base.cpp b/layersvt/common/layer_base.cpp new file mode 100644 index 0000000000..bd14c2793f --- /dev/null +++ b/layersvt/common/layer_base.cpp @@ -0,0 +1,603 @@ +/* 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 "dispatch_downstream.h" +#include "dispatch_table_manager.h" +#include "layer_manifest.h" + +#include +#include +#include + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#if !defined(NDEBUG) +#include +#endif +#endif + +namespace layersvt { + +namespace { + +inline void AssertLayerInitialized() { + assert(LayerBase::Get() != nullptr && "LayerBase instance must be initialized"); +} + +bool IsGlobalCommand(const char* command_name) { + return std::strcmp(command_name, "vkGetInstanceProcAddr") == 0 || + std::strcmp(command_name, "vkCreateInstance") == 0 || + std::strcmp(command_name, "vkEnumerateInstanceExtensionProperties") == 0 || + std::strcmp(command_name, "vkEnumerateInstanceLayerProperties") == 0 || + std::strcmp(command_name, "vkEnumerateInstanceVersion") == 0; +} + +VkLayerInstanceCreateInfo* GetChainInfo(const VkInstanceCreateInfo& create_info, VkLayerFunction function) { + 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) { + 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); +} + +template +VkResult CopyEnumerationProperties(const std::vector& items, uint32_t* property_count, T* properties) { + assert(property_count != nullptr); + + const uint32_t total = static_cast(items.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::copy_n(items.begin(), copy_count, properties); + } + *property_count = copy_count; + + return (copy_count < total) ? VK_INCOMPLETE : VK_SUCCESS; +} + +#if defined(_WIN32) +void InitPlatformErrorHandling() { +#if !defined(NDEBUG) + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); +#endif + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); +} +#endif +} // namespace + +LayerBase::LayerBase() { +#if defined(_WIN32) + InitPlatformErrorHandling(); +#endif + layer_ = this; +} + +LayerBase::~LayerBase() { + if (layer_ == this) { + layer_ = nullptr; + } +} + +VkInstance LayerBase::GetVkInstance(VkPhysicalDevice physical_device) { + AssertLayerInitialized(); + return layer_->dispatch_table_manager_.GetVkInstance(physical_device); +} + +VkuInstanceDispatchTable* LayerBase::GetInstanceDispatchTable(VkInstance instance) { + AssertLayerInitialized(); + return layer_->dispatch_table_manager_.GetInstanceDispatchTable(instance); +} + +VkuInstanceDispatchTable* LayerBase::GetInstanceDispatchTable(VkPhysicalDevice physical_device) { + AssertLayerInitialized(); + return layer_->dispatch_table_manager_.GetInstanceDispatchTable(physical_device); +} + +VkuDeviceDispatchTable* LayerBase::GetDeviceDispatchTable(const void* object) { + AssertLayerInitialized(); + return layer_->dispatch_table_manager_.GetDeviceDispatchTable(object); +} + +PFN_vkSetDeviceLoaderData LayerBase::GetDeviceLoaderDataCallback(VkDevice device) { + AssertLayerInitialized(); + return layer_->dispatch_table_manager_.GetDeviceLoaderDataCallback(device); +} + +VkResult LayerBase::CreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, + VkInstance* instance) { + if (!create_info || !instance) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + AssertLayerInitialized(); + + 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; + } + + LayerBase* layer = Get(); + layer->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) { + layer->dispatch_table_manager_.InitInstanceTable(*instance, get_instance_proc_addr); + layer->PostCreateInstance(*instance, &modified_create_info, allocator); + } + return result; +} + +void LayerBase::DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) { + if (instance == VK_NULL_HANDLE) { + return; + } + + AssertLayerInitialized(); + LayerBase* layer = Get(); + layer->PreDestroyInstance(instance, allocator); + + auto key = DispatchTableManager::GetDispatchKey(instance); + DispatchDownstream<&VkuInstanceDispatchTable::DestroyInstance>(instance, allocator); + layer->dispatch_table_manager_.DestroyInstanceTable(key); +} + +VkResult LayerBase::EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices) { + VkResult result = DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>( + VK_ERROR_INITIALIZATION_FAILED, instance, physical_device_count, physical_devices); + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && physical_device_count != nullptr && + physical_devices != nullptr) { + LayerBase* layer = Get(); + layer->dispatch_table_manager_.RegisterPhysicalDevices(physical_devices, *physical_device_count, instance); + } + return result; +} + +VkResult LayerBase::EnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties) { + VkResult result = DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDeviceGroups>( + [&] { + return DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDeviceGroupsKHR>( + VK_ERROR_INITIALIZATION_FAILED, instance, physical_device_group_count, + physical_device_group_properties); + }, + instance, physical_device_group_count, physical_device_group_properties); + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && physical_device_group_count != nullptr && + physical_device_group_properties != nullptr) { + LayerBase* layer = Get(); + for (uint32_t i = 0; i < *physical_device_group_count; ++i) { + assert(physical_device_group_properties[i].physicalDeviceCount <= VK_MAX_DEVICE_GROUP_SIZE); + const uint32_t device_count = physical_device_group_properties[i].physicalDeviceCount; + layer->dispatch_table_manager_.RegisterPhysicalDevices(physical_device_group_properties[i].physicalDevices, + device_count, instance); + } + } + return result; +} + +VkResult LayerBase::CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device) { + if (physical_device == VK_NULL_HANDLE || !create_info || !device) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkInstance instance = 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; + } + + LayerBase* layer = Get(); + layer->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) { + layer->dispatch_table_manager_.InitDeviceTable(*device, get_device_proc_addr); + if (loader_callback) { + layer->dispatch_table_manager_.SetDeviceLoaderDataCallback(*device, loader_callback); + } + layer->PostCreateDevice(*device, physical_device, &modified_create_info, allocator); + } + return result; +} + +void LayerBase::DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) { + if (device == VK_NULL_HANDLE) { + return; + } + + AssertLayerInitialized(); + LayerBase* layer = Get(); + layer->PreDestroyDevice(device, allocator); + + auto key = DispatchTableManager::GetDispatchKey(device); + DispatchDownstream<&VkuDeviceDispatchTable::DestroyDevice>(device, allocator); + layer->dispatch_table_manager_.DestroyDeviceTable(key); +} + +VkResult LayerBase::EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) { + assert(property_count != nullptr); + AssertLayerInitialized(); + + LayerBase* layer = Get(); + const LayerManifest* manifest = layer->GetLayerManifest(); + if (!manifest) { + return VK_ERROR_INITIALIZATION_FAILED; + } + const char* my_layer_name = (manifest->layer_name != nullptr) ? manifest->layer_name : ""; + + if (layer_name == nullptr || my_layer_name[0] == '\0' || std::strcmp(layer_name, my_layer_name) != 0) { + *property_count = 0; + return VK_ERROR_LAYER_NOT_PRESENT; + } + + std::vector extensions = manifest->instance_extensions; + layer->ProcessInstanceExtensions(layer_name, extensions); + + return CopyEnumerationProperties(extensions, property_count, properties); +} + +VkResult LayerBase::EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) { + assert(property_count != nullptr); + AssertLayerInitialized(); + + LayerBase* layer = Get(); + const LayerManifest* manifest = layer->GetLayerManifest(); + if (!manifest) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + if (properties == nullptr) { + *property_count = 1; + return VK_SUCCESS; + } + + if (*property_count < 1) { + return VK_INCOMPLETE; + } + + *properties = manifest->GetLayerProperties(); + *property_count = 1; + return VK_SUCCESS; +} + +VkResult LayerBase::EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties) { + (void)physical_device; + return EnumerateInstanceLayerProperties(property_count, properties); +} + +VkResult LayerBase::EnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, VkExtensionProperties* properties) { + PFN_vkEnumerateDeviceExtensionProperties downstream = nullptr; + if (physical_device != VK_NULL_HANDLE) { + auto* table = GetInstanceDispatchTable(physical_device); + if (table != nullptr) { + downstream = table->EnumerateDeviceExtensionProperties; + } + } + return EnumerateDeviceExtensionPropertiesWithDownstream(physical_device, layer_name, property_count, properties, downstream); +} + +VkResult LayerBase::EnumerateDeviceExtensionPropertiesWithDownstream( + VkPhysicalDevice physical_device, const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties, PFN_vkEnumerateDeviceExtensionProperties downstream_function) { + assert(property_count != nullptr); + AssertLayerInitialized(); + + LayerBase* layer = Get(); + const LayerManifest* manifest = layer->GetLayerManifest(); + const char* my_layer_name = (manifest && manifest->layer_name) ? manifest->layer_name : ""; + + // When explicitly querying this layer's device extensions: + if (layer_name != nullptr && my_layer_name[0] != '\0' && std::strcmp(layer_name, my_layer_name) == 0) { + std::vector extensions = manifest->device_extensions; + layer->ProcessDeviceExtensions(physical_device, layer_name, extensions); + return CopyEnumerationProperties(extensions, property_count, properties); + } + + // If another layer is being queried, forward downstream or return VK_ERROR_LAYER_NOT_PRESENT + if (layer_name != nullptr) { + 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 + std::vector extensions; + if (downstream_function) { + uint32_t downstream_count = 0; + VkResult result = downstream_function(physical_device, nullptr, &downstream_count, nullptr); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + if (downstream_count > 0) { + uint32_t allocated_count = downstream_count; + extensions.resize(allocated_count); + result = downstream_function(physical_device, nullptr, &downstream_count, extensions.data()); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + extensions.resize(std::min(downstream_count, allocated_count)); + } + } + + if (manifest) { + for (const auto& layer_extension : manifest->device_extensions) { + bool duplicate = false; + for (const auto& existing : extensions) { + if (std::strcmp(existing.extensionName, layer_extension.extensionName) == 0) { + duplicate = true; + break; + } + } + if (!duplicate) { + extensions.push_back(layer_extension); + } + } + } + + layer->ProcessDeviceExtensions(physical_device, layer_name, extensions); + + return CopyEnumerationProperties(extensions, property_count, properties); +} + +VkResult LayerBase::GetPhysicalDeviceToolProperties(VkPhysicalDevice physical_device, uint32_t* tool_count, + VkPhysicalDeviceToolPropertiesEXT* tool_properties) { + PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream = nullptr; + if (physical_device != VK_NULL_HANDLE) { + auto* table = GetInstanceDispatchTable(physical_device); + if (table != nullptr) { + downstream = table->GetPhysicalDeviceToolPropertiesEXT; + if (!downstream) { + downstream = table->GetPhysicalDeviceToolProperties; + } + } + } + return GetPhysicalDeviceToolPropertiesWithDownstream(physical_device, tool_count, tool_properties, downstream); +} + +VkResult LayerBase::GetPhysicalDeviceToolPropertiesWithDownstream( + VkPhysicalDevice physical_device, uint32_t* tool_count, VkPhysicalDeviceToolPropertiesEXT* tool_properties, + PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream_function) { + assert(tool_count != nullptr); + AssertLayerInitialized(); + + LayerBase* layer = Get(); + const LayerManifest* manifest = layer->GetLayerManifest(); + + std::vector tools; + if (downstream_function) { + uint32_t downstream_count = 0; + VkResult result = downstream_function(physical_device, &downstream_count, nullptr); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + if (downstream_count > 0) { + uint32_t allocated_count = downstream_count; + tools.resize(allocated_count); + for (auto& tool : tools) { + tool.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT; + tool.pNext = nullptr; + } + result = downstream_function(physical_device, &downstream_count, tools.data()); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + return result; + } + tools.resize(std::min(downstream_count, allocated_count)); + } + } + + if (manifest && manifest->tool_properties.has_value()) { + tools.push_back(*manifest->tool_properties); + } + + layer->ProcessToolProperties(physical_device, tools); + + return CopyEnumerationProperties(tools, tool_count, tool_properties); +} + +void LayerBase::ProcessInstanceExtensions(const char*, std::vector&) const {} + +void LayerBase::ProcessDeviceExtensions(VkPhysicalDevice, const char*, std::vector&) const {} + +void LayerBase::ProcessToolProperties(VkPhysicalDevice, std::vector&) const {} + +bool LayerBase::HasToolProperties() const { + const LayerManifest* manifest = GetLayerManifest(); + return manifest && manifest->tool_properties.has_value(); +} + +void LayerBase::PreCreateInstance(VkInstanceCreateInfo*, const VkAllocationCallbacks*) {} +void LayerBase::PostCreateInstance(VkInstance, const VkInstanceCreateInfo*, const VkAllocationCallbacks*) {} +void LayerBase::PreDestroyInstance(VkInstance, const VkAllocationCallbacks*) {} + +void LayerBase::PreCreateDevice(VkPhysicalDevice, VkDeviceCreateInfo*, const VkAllocationCallbacks*) {} +void LayerBase::PostCreateDevice(VkDevice, VkPhysicalDevice, const VkDeviceCreateInfo*, const VkAllocationCallbacks*) {} +void LayerBase::PreDestroyDevice(VkDevice, const VkAllocationCallbacks*) {} + +PFN_vkVoidFunction LayerBase::GetLayerInstanceCommand(const char*) { return nullptr; } + +PFN_vkVoidFunction LayerBase::GetLayerDeviceCommand(const char*) { return nullptr; } + +PFN_vkVoidFunction LayerBase::GetKnownInstanceCommand(const char* command_name) { + assert(command_name != nullptr); + AssertLayerInitialized(); + LayerBase* layer = Get(); + PFN_vkVoidFunction custom_command = layer->GetLayerInstanceCommand(command_name); + if (custom_command != nullptr) { + return custom_command; + } + + if (std::strcmp(command_name, "vkGetInstanceProcAddr") == 0) { + return reinterpret_cast(GetInstanceProcAddr); + } + if (std::strcmp(command_name, "vkCreateInstance") == 0) { + return reinterpret_cast(CreateInstance); + } + if (std::strcmp(command_name, "vkDestroyInstance") == 0) { + return reinterpret_cast(DestroyInstance); + } + if (std::strcmp(command_name, "vkEnumeratePhysicalDevices") == 0) { + return reinterpret_cast(EnumeratePhysicalDevices); + } + if (std::strcmp(command_name, "vkEnumeratePhysicalDeviceGroups") == 0 || + std::strcmp(command_name, "vkEnumeratePhysicalDeviceGroupsKHR") == 0) { + return reinterpret_cast(EnumeratePhysicalDeviceGroups); + } + if (std::strcmp(command_name, "vkCreateDevice") == 0) { + return reinterpret_cast(CreateDevice); + } + if (std::strcmp(command_name, "vkEnumerateInstanceExtensionProperties") == 0) { + return reinterpret_cast(EnumerateInstanceExtensionProperties); + } + if (std::strcmp(command_name, "vkEnumerateInstanceLayerProperties") == 0) { + return reinterpret_cast(EnumerateInstanceLayerProperties); + } + if (std::strcmp(command_name, "vkEnumerateDeviceLayerProperties") == 0) { + return reinterpret_cast(EnumerateDeviceLayerProperties); + } + if (std::strcmp(command_name, "vkEnumerateDeviceExtensionProperties") == 0) { + return reinterpret_cast(EnumerateDeviceExtensionProperties); + } + if (std::strcmp(command_name, "vkGetPhysicalDeviceToolPropertiesEXT") == 0 || + std::strcmp(command_name, "vkGetPhysicalDeviceToolProperties") == 0) { + if (layer->HasToolProperties()) { + return reinterpret_cast(GetPhysicalDeviceToolProperties); + } + } + return nullptr; +} + +PFN_vkVoidFunction LayerBase::GetKnownDeviceCommand(const char* command_name) { + assert(command_name != nullptr); + AssertLayerInitialized(); + LayerBase* layer = Get(); + PFN_vkVoidFunction custom_command = layer->GetLayerDeviceCommand(command_name); + if (custom_command != nullptr) { + return custom_command; + } + + if (std::strcmp(command_name, "vkGetDeviceProcAddr") == 0) { + return reinterpret_cast(GetDeviceProcAddr); + } + if (std::strcmp(command_name, "vkDestroyDevice") == 0) { + return reinterpret_cast(DestroyDevice); + } + return nullptr; +} + +PFN_vkVoidFunction VKAPI_CALL LayerBase::GetInstanceProcAddr(VkInstance instance, const char* command_name) { + if (!command_name) { + return nullptr; + } + + AssertLayerInitialized(); + + if (instance == VK_NULL_HANDLE) { + if (!IsGlobalCommand(command_name)) { + return nullptr; + } + return GetKnownInstanceCommand(command_name); + } + + PFN_vkVoidFunction command = GetKnownInstanceCommand(command_name); + if (command != nullptr) { + return command; + } + + command = GetKnownDeviceCommand(command_name); + if (command != nullptr) { + return command; + } + + return DispatchDownstreamOr<&VkuInstanceDispatchTable::GetInstanceProcAddr>(nullptr, instance, command_name); +} + +PFN_vkVoidFunction VKAPI_CALL LayerBase::GetDeviceProcAddr(VkDevice device, const char* command_name) { + if (!command_name) { + return nullptr; + } + + if (device == VK_NULL_HANDLE) { + return nullptr; + } + + AssertLayerInitialized(); + PFN_vkVoidFunction command = GetKnownDeviceCommand(command_name); + if (command != nullptr) { + return command; + } + + return DispatchDownstreamOr<&VkuDeviceDispatchTable::GetDeviceProcAddr>(nullptr, device, command_name); +} + +} // namespace layersvt diff --git a/layersvt/common/layer_base.h b/layersvt/common/layer_base.h new file mode 100644 index 0000000000..abf6b1b353 --- /dev/null +++ b/layersvt/common/layer_base.h @@ -0,0 +1,261 @@ +/* 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 "dispatch_table_manager.h" +#include +#include +#include +#include +#include + +namespace layersvt { + +struct LayerManifest; +class LayerBaseTestPeer; + +/** + * Base class providing common infrastructure for Vulkan layer implementations. + * + * Implements the Template Method pattern for Vulkan API routing, centralizing loader + * negotiation, dispatch table tracking, handle mapping, and property enumeration: + * + * - Singleton Lifecycle: A single LayerBase instance is created at library load time + * (typically as a file-scope static object in the layer's translation unit). + * - Thread Safety: Internal registries (DispatchTableManager) are thread-safe. + * Overridden hooks called concurrently by Vulkan applications must maintain their + * own thread safety for layer-specific state. + * - Command Routing: Custom commands return function pointers via GetLayerInstanceCommand + * and GetLayerDeviceCommand; unhandled commands route to downstream dispatch tables. + * - Property Enumeration: Serves layer extensions and tool properties automatically + * from GetLayerManifest(), merging layer properties with downstream capabilities. + * - Lifecycle Hooks: PreCreate* / PostCreate* / PreDestroy* hooks bracket instance and + * device creation and destruction. PreDestroy* hooks are guaranteed non-null handles + * (null handle calls return immediately per Vulkan Spec 2.7). No PostDestroy* hooks + * exist because downstream destruction frees and invalidates handles before returning. + * + * For authoring guides, CMake build setup, and downstream dispatch examples, + * see layersvt/common/README.md. + */ +class LayerBase { + public: + LayerBase(); + virtual ~LayerBase(); + + LayerBase(const LayerBase&) = delete; + LayerBase& operator=(const LayerBase&) = delete; + LayerBase(LayerBase&&) = delete; + LayerBase& operator=(LayerBase&&) = delete; + + // Layer singleton management + /** + * Retrieves the currently active LayerBase singleton instance. + * Returns a pointer to the active LayerBase instance, or nullptr if no layer is initialized. + */ + [[nodiscard]] static LayerBase* Get() noexcept { return layer_; } + + /** + * Retrieves the parent VkInstance associated with a physical device. + * Returns the parent VkInstance on success, or VK_NULL_HANDLE if unregistered or invalid. + */ + [[nodiscard]] static VkInstance GetVkInstance(VkPhysicalDevice physical_device); + + /** + * Retrieves the loader data callback for initializing dispatchable handles created by layers. + * Returns the registered PFN_vkSetDeviceLoaderData callback on success, or nullptr if unset. + */ + [[nodiscard]] static PFN_vkSetDeviceLoaderData GetDeviceLoaderDataCallback(VkDevice device); + + protected: + /** + * Override to provide the layer's metadata, supported extensions, and tool properties. + * Enables automatic handling of layer and extension property enumeration queries. + * Returns the layer's LayerManifest, or nullptr if none is configured. + */ + [[nodiscard]] virtual const LayerManifest* GetLayerManifest() const { return nullptr; } + + // Extension and tooling hooks + + /** + * Customizes or filters instance extensions during vkEnumerateInstanceExtensionProperties. + */ + virtual void ProcessInstanceExtensions(const char* layer_name, + std::vector& extensions) const; + + /** + * Customizes or filters device extensions during vkEnumerateDeviceExtensionProperties. + */ + virtual void ProcessDeviceExtensions(VkPhysicalDevice physical_device, const char* layer_name, + std::vector& extensions) const; + + /** + * Customizes or filters tool properties during vkGetPhysicalDeviceToolProperties. + */ + virtual void ProcessToolProperties(VkPhysicalDevice physical_device, + std::vector& tools) const; + + /** + * Indicates whether this layer intercepts physical device tool properties. + * Default implementation returns true if the layer manifest defines tool_properties. + * Returns true if tool properties queries should be intercepted, or false to dispatch downstream. + */ + [[nodiscard]] virtual bool HasToolProperties() const; + + // Layer-specific command intercepts + + /** + * Override to intercept instance-level Vulkan commands. + * + * Returns a function pointer to the hook implementation, or nullptr to fall back + * to core Vulkan intercepts (e.g. vkCreateInstance, vkDestroyInstance, + * vkEnumeratePhysicalDevices) or downstream dispatch. + */ + virtual PFN_vkVoidFunction GetLayerInstanceCommand(const char* command_name); + + /** + * Override to intercept device-level Vulkan commands. + * + * Returns a function pointer to the hook implementation, or nullptr to fall back + * to core Vulkan intercepts (e.g. vkDestroyDevice) or downstream dispatch. + */ + virtual PFN_vkVoidFunction GetLayerDeviceCommand(const char* command_name); + + // Instance and device lifecycle hooks (template method pattern) + + /** + * Hook called immediately before vkCreateInstance dispatches downstream. + * Allows inspecting or modifying create_info (e.g. injecting extensions or pNext structs). + */ + virtual void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately after vkCreateInstance succeeds downstream. + * Use to initialize instance state, settings, or tracing. The instance dispatch table is ready. + */ + virtual void PostCreateInstance(VkInstance instance, const VkInstanceCreateInfo* create_info, + const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately before vkDestroyInstance dispatches downstream. + * Guaranteed to receive a valid, non-null VkInstance handle. + */ + virtual void PreDestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately before vkCreateDevice dispatches downstream. + * Allows inspecting or modifying create_info (e.g. injecting device extensions or pNext structs). + */ + virtual void PreCreateDevice(VkPhysicalDevice physical_device, VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately after vkCreateDevice succeeds downstream. + * Use to initialize per-device state or allocate layer resources. The device dispatch table is ready. + */ + virtual void PostCreateDevice(VkDevice device, VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately before vkDestroyDevice dispatches downstream. + * Guaranteed to receive a valid, non-null VkDevice handle. + */ + virtual void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); + + private: + [[nodiscard]] DispatchTableManager& GetDispatchTableManager() noexcept { return dispatch_table_manager_; } + [[nodiscard]] const DispatchTableManager& GetDispatchTableManager() const noexcept { return dispatch_table_manager_; } + + [[nodiscard]] static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkInstance instance); + [[nodiscard]] static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkPhysicalDevice physical_device); + [[nodiscard]] static VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object); + // Internal subsystems + // Managed automatically by LayerBase; derived layers access downstream + // dispatch via DispatchDownstream instead of querying these tables directly. + DispatchTableManager dispatch_table_manager_; + + static inline LayerBase* layer_ = nullptr; + + // Exported Vulkan layer entry points (implemented in layer_entrypoints.cpp) +#if defined(_WIN32) + friend VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL ::vkGetInstanceProcAddr(VkInstance instance, const char* command_name); + friend VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL ::vkGetDeviceProcAddr(VkDevice device, const char* command_name); + friend VKAPI_ATTR VkResult VKAPI_CALL ::vkEnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL ::vkEnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL ::vkEnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL ::vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, + VkExtensionProperties* properties); +#else + friend VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (::vkGetInstanceProcAddr)(VkInstance instance, const char* command_name); + friend VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (::vkGetDeviceProcAddr)(VkDevice device, const char* command_name); + friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateInstanceLayerProperties)(uint32_t* property_count, VkLayerProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateInstanceExtensionProperties)(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateDeviceLayerProperties)(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateDeviceExtensionProperties)(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, + VkExtensionProperties* properties); +#endif + + friend class LayerBaseTestPeer; + + template + friend auto DispatchDownstream(Handle handle, Args&&... args); + + template + friend auto DispatchDownstreamOr(Fallback&& fallback, Handle handle, Args&&... args); + + // Vulkan intercept commands (static C-compatible functions) + + static PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance, const char* command_name); + static PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice device, const char* command_name); + + static VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, + VkInstance* instance); + static void VKAPI_CALL DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator); + + static VkResult VKAPI_CALL EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices); + static VkResult VKAPI_CALL EnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties); + static VkResult VKAPI_CALL CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device); + static void VKAPI_CALL DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); + + static VkResult VKAPI_CALL EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties); + static VkResult VKAPI_CALL EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties); + static VkResult VKAPI_CALL EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties); + static VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, VkExtensionProperties* properties); + static VkResult EnumerateDeviceExtensionPropertiesWithDownstream( + VkPhysicalDevice physical_device, const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties, PFN_vkEnumerateDeviceExtensionProperties downstream_function); + static VkResult VKAPI_CALL GetPhysicalDeviceToolProperties(VkPhysicalDevice physical_device, uint32_t* tool_count, + VkPhysicalDeviceToolPropertiesEXT* tool_properties); + static VkResult GetPhysicalDeviceToolPropertiesWithDownstream( + VkPhysicalDevice physical_device, uint32_t* tool_count, VkPhysicalDeviceToolPropertiesEXT* tool_properties, + PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream_function); + + static PFN_vkVoidFunction GetKnownInstanceCommand(const char* command_name); + static PFN_vkVoidFunction GetKnownDeviceCommand(const char* command_name); +}; + +} // namespace layersvt diff --git a/layersvt/common/layer_entrypoints.cpp b/layersvt/common/layer_entrypoints.cpp new file mode 100644 index 0000000000..a5dab26ab5 --- /dev/null +++ b/layersvt/common/layer_entrypoints.cpp @@ -0,0 +1,89 @@ +/* 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 +#include +#include + +#ifndef VK_LAYER_EXPORT +#if defined(_WIN32) +// On Windows, layer DLL entry points are exported via .def module definition files. +// Omitting __declspec(dllexport) prevents MSVC C2375 linkage conflicts with Vulkan SDK headers. +#define VK_LAYER_EXPORT +#else +#define VK_LAYER_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +extern "C" { + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkNegotiateLoaderLayerInterfaceVersion( + VkNegotiateLayerInterface* version_interface) { + assert(version_interface != nullptr); + assert(version_interface->sType == LAYER_NEGOTIATE_INTERFACE_STRUCT); + + if (version_interface->loaderLayerInterfaceVersion >= 2) { + version_interface->loaderLayerInterfaceVersion = 2; + version_interface->pfnGetInstanceProcAddr = vkGetInstanceProcAddr; + version_interface->pfnGetDeviceProcAddr = vkGetDeviceProcAddr; + version_interface->pfnGetPhysicalDeviceProcAddr = nullptr; + return VK_SUCCESS; + } + + if (version_interface->loaderLayerInterfaceVersion == 1) { + version_interface->loaderLayerInterfaceVersion = 1; + return VK_SUCCESS; + } + + return VK_ERROR_INITIALIZATION_FAILED; +} + +VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr( + VkInstance instance, const char* command_name) { + return layersvt::LayerBase::GetInstanceProcAddr(instance, command_name); +} + +VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr( + VkDevice device, const char* command_name) { + return layersvt::LayerBase::GetDeviceProcAddr(device, command_name); +} + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties( + uint32_t* property_count, VkLayerProperties* properties) { + return layersvt::LayerBase::EnumerateInstanceLayerProperties(property_count, properties); +} + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties( + const char* layer_name, uint32_t* property_count, VkExtensionProperties* properties) { + return layersvt::LayerBase::EnumerateInstanceExtensionProperties( + layer_name, property_count, properties); +} + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( + VkPhysicalDevice physical_device, uint32_t* property_count, VkLayerProperties* properties) { + return layersvt::LayerBase::EnumerateDeviceLayerProperties( + physical_device, property_count, properties); +} + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties( + VkPhysicalDevice physical_device, const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) { + return layersvt::LayerBase::EnumerateDeviceExtensionProperties( + physical_device, layer_name, property_count, properties); +} + +} // extern "C" diff --git a/layersvt/layer_keep_alive.cpp b/layersvt/common/layer_keep_alive.cpp similarity index 54% rename from layersvt/layer_keep_alive.cpp rename to layersvt/common/layer_keep_alive.cpp index c375f0b637..616adee880 100644 --- a/layersvt/layer_keep_alive.cpp +++ b/layersvt/common/layer_keep_alive.cpp @@ -17,28 +17,19 @@ #if defined(__ANDROID__) #include +#include namespace { -// Anonymous namespace function is NOT exported, keeping it local to each shared object. -// We use a constructor attribute to trigger it when the library is loaded/opened. -void layer_keep_alive_func(); - -class KeepAlive { - public: - KeepAlive() { - Dl_info info; - // Attempt to find the filename of the library containing this code. - if (dladdr((void*)&layer_keep_alive_func, &info)) { - // Re-open with RTLD_NODELETE to force the library to stay resident. - dlopen(info.dli_fname, RTLD_NODELETE); - } +// Function with constructor attribute executes during library load. +// Re-open with RTLD_NODELETE to ensure the layer shared library stays resident in process memory +// across Vulkan loader queries. +__attribute__((constructor)) void LayerKeepAlive() { + Dl_info info{}; + if (dladdr(reinterpret_cast(&LayerKeepAlive), &info) != 0 && info.dli_fname != nullptr && + info.dli_fname[0] != '\0' && std::strstr(info.dli_fname, ".so") != nullptr) { + (void)dlopen(info.dli_fname, RTLD_NOW | RTLD_NODELETE); } -}; - -__attribute__((constructor)) void layer_keep_alive_func() { - static KeepAlive k; - (void)k; } } // namespace diff --git a/layersvt/common/layer_manifest.cpp b/layersvt/common/layer_manifest.cpp new file mode 100644 index 0000000000..a80542f279 --- /dev/null +++ b/layersvt/common/layer_manifest.cpp @@ -0,0 +1,34 @@ +/* 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 { + +VkLayerProperties LayerManifest::GetLayerProperties() const noexcept { + assert(layer_name != nullptr); + assert(description != nullptr); + VkLayerProperties properties{}; + std::strncpy(properties.layerName, layer_name, sizeof(properties.layerName) - 1); + properties.layerName[sizeof(properties.layerName) - 1] = '\0'; + std::strncpy(properties.description, description, sizeof(properties.description) - 1); + properties.description[sizeof(properties.description) - 1] = '\0'; + properties.specVersion = spec_version; + properties.implementationVersion = implementation_version; + return properties; +} + +} // namespace layersvt diff --git a/layersvt/common/layer_manifest.h b/layersvt/common/layer_manifest.h new file mode 100644 index 0000000000..88fea33aa2 --- /dev/null +++ b/layersvt/common/layer_manifest.h @@ -0,0 +1,45 @@ +/* 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 + +namespace layersvt { + +/** + * Declarative metadata describing a Vulkan layer's identity, versions, + * exposed extensions, and optional tooling properties. + */ +struct LayerManifest { + 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; + + /** + * Converts manifest metadata into a standard VkLayerProperties structure. + * Returns the populated VkLayerProperties instance. + */ + [[nodiscard]] VkLayerProperties GetLayerProperties() const noexcept; +}; + +} // namespace layersvt diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index cd635acb9e..99fa92c24e 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -86,3 +86,27 @@ if (TARGET VkLayer_DeviceMemoryReport) endif() endif() +add_executable(test_common_layer + common/test_dispatch_downstream.cpp + common/test_dispatch_table_manager.cpp + common/test_layer_base.cpp + common/test_layer_entrypoints.cpp + common/test_layer_manifest.cpp + layer_test_main.cpp + $ +) +target_link_libraries(test_common_layer PRIVATE + layersvt_common + GTest::gtest + Vulkan::Headers + Vulkan::UtilityHeaders +) +target_include_directories(test_common_layer PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) +add_test(NAME test_common_layer COMMAND test_common_layer) +set_target_properties(test_common_layer PROPERTIES FOLDER "layers/common/Test") + +if(WIN32 AND (QT_TARGET_TYPE STREQUAL STATIC_LIBRARY)) + set_property(TARGET test_common_layer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() diff --git a/layersvt/test/common/layer_base_test_peer.h b/layersvt/test/common/layer_base_test_peer.h new file mode 100644 index 0000000000..ad4c8dc5b3 --- /dev/null +++ b/layersvt/test/common/layer_base_test_peer.h @@ -0,0 +1,116 @@ +/* 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 "common/layer_base.h" +#include +#include + +namespace layersvt { + +class LayerBaseTestPeer { + public: + static PFN_vkVoidFunction GetKnownInstanceCommand(const char* command_name) { + return LayerBase::GetKnownInstanceCommand(command_name); + } + static PFN_vkVoidFunction GetKnownDeviceCommand(const char* command_name) { + return LayerBase::GetKnownDeviceCommand(command_name); + } + + static VkResult EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices) { + return LayerBase::EnumeratePhysicalDevices(instance, physical_device_count, physical_devices); + } + + static VkResult EnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties) { + return LayerBase::EnumeratePhysicalDeviceGroups(instance, physical_device_group_count, physical_device_group_properties); + } + static VkResult CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device) { + return LayerBase::CreateDevice(physical_device, create_info, allocator, device); + } + + static void DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) { + LayerBase::DestroyDevice(device, allocator); + } + + static VkResult EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) { + return LayerBase::EnumerateInstanceExtensionProperties(layer_name, property_count, properties); + } + static VkResult EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) { + return LayerBase::EnumerateInstanceLayerProperties(property_count, properties); + } + static VkResult EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties) { + return LayerBase::EnumerateDeviceLayerProperties(physical_device, property_count, properties); + } + static VkResult EnumerateDeviceExtensionProperties( + VkPhysicalDevice physical_device, const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties, + PFN_vkEnumerateDeviceExtensionProperties downstream_function = nullptr) { + if (downstream_function != nullptr) { + return LayerBase::EnumerateDeviceExtensionPropertiesWithDownstream( + physical_device, layer_name, property_count, properties, downstream_function); + } + return LayerBase::EnumerateDeviceExtensionProperties( + physical_device, layer_name, property_count, properties); + } + static VkResult GetPhysicalDeviceToolProperties( + VkPhysicalDevice physical_device, uint32_t* tool_count, + VkPhysicalDeviceToolPropertiesEXT* tool_properties, + PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream_function = nullptr) { + if (downstream_function != nullptr) { + return LayerBase::GetPhysicalDeviceToolPropertiesWithDownstream( + physical_device, tool_count, tool_properties, downstream_function); + } + return LayerBase::GetPhysicalDeviceToolProperties( + physical_device, tool_count, tool_properties); + } + static const LayerManifest* GetLayerManifest(const LayerBase& layer) { return layer.GetLayerManifest(); } + static DispatchTableManager& GetDispatchTableManager(LayerBase& layer) { return layer.GetDispatchTableManager(); } + static const DispatchTableManager& GetDispatchTableManager(const LayerBase& layer) { return layer.GetDispatchTableManager(); } + + static VkInstance GetVkInstance(VkPhysicalDevice physical_device) { return LayerBase::GetVkInstance(physical_device); } + + static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkInstance instance) { + return LayerBase::GetInstanceDispatchTable(instance); + } + static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkPhysicalDevice physical_device) { + return LayerBase::GetInstanceDispatchTable(physical_device); + } + + static VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object) { return LayerBase::GetDeviceDispatchTable(object); } + + static PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* command_name) { + return LayerBase::GetInstanceProcAddr(instance, command_name); + } + static PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* command_name) { + return LayerBase::GetDeviceProcAddr(device, command_name); + } + + static VkResult CreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, + VkInstance* instance) { + return LayerBase::CreateInstance(create_info, allocator, instance); + } + + static void DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) { + LayerBase::DestroyInstance(instance, allocator); + } +}; + +} // namespace layersvt diff --git a/layersvt/test/common/test_dispatch_downstream.cpp b/layersvt/test/common/test_dispatch_downstream.cpp new file mode 100644 index 0000000000..5b3a62a4c6 --- /dev/null +++ b/layersvt/test/common/test_dispatch_downstream.cpp @@ -0,0 +1,117 @@ +/* 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/dispatch_downstream.h" +#include "common/layer_base.h" +#include "layer_base_test_peer.h" +#include + +using namespace layersvt; + +TEST(DispatchDownstreamTest, DispatchDownstream) { + LayerBase layer; + auto& dispatch_table_manager = LayerBaseTestPeer::GetDispatchTableManager(layer); + + void* mock_instance_vtable = reinterpret_cast(static_cast(0x1111)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + + void* mock_device_vtable = reinterpret_cast(static_cast(0x2222)); + auto mock_device = reinterpret_cast(&mock_device_vtable); + + // 1. Unregistered handles (no dispatch table present) + // DispatchDownstreamOrSuccess returns VK_SUCCESS fallback for VkResult commands + uint32_t count = 0; + VkResult instance_result = + DispatchDownstreamOrSuccess<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(mock_instance, &count, nullptr); + EXPECT_EQ(instance_result, VK_SUCCESS); + + VkResult device_result = DispatchDownstreamOrSuccess<&VkuDeviceDispatchTable::DeviceWaitIdle>(mock_device); + EXPECT_EQ(device_result, VK_SUCCESS); + + // void return type safely no-ops with DispatchDownstreamOr + DispatchDownstreamOr<&VkuInstanceDispatchTable::DestroyInstance>([] {}, mock_instance, nullptr); + DispatchDownstreamOr<&VkuDeviceDispatchTable::DestroyDevice>([] {}, mock_device, nullptr); + + // DispatchDownstreamOr returns custom fallback value or lambda + EXPECT_EQ((DispatchDownstreamOr<&VkuDeviceDispatchTable::DeviceWaitIdle>(VK_TIMEOUT, mock_device)), VK_TIMEOUT); + EXPECT_EQ((DispatchDownstreamOr<&VkuDeviceDispatchTable::DeviceWaitIdle>([] { return VK_NOT_READY; }, mock_device)), + VK_NOT_READY); + + // 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((DispatchDownstreamOrSuccess<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(mock_instance, &count, nullptr)), + VK_SUCCESS); + EXPECT_EQ((DispatchDownstreamOrSuccess<&VkuDeviceDispatchTable::DeviceWaitIdle>(mock_device)), VK_SUCCESS); + DispatchDownstreamOr<&VkuInstanceDispatchTable::DestroyInstance>([] {}, mock_instance, nullptr); + DispatchDownstreamOr<&VkuDeviceDispatchTable::DestroyDevice>([] {}, mock_device, nullptr); + + EXPECT_EQ((DispatchDownstreamOr<&VkuDeviceDispatchTable::DeviceWaitIdle>(VK_TIMEOUT, mock_device)), VK_TIMEOUT); + + // 3. Initialized tables with valid mock function pointers (downstream forwarding) + static bool instance_function_called = false; + static bool device_function_called = false; + instance_function_called = false; + device_function_called = false; + + auto* instance_table = dispatch_table_manager.GetInstanceDispatchTable(mock_instance); + ASSERT_NE(instance_table, nullptr); + instance_table->EnumeratePhysicalDevices = [](VkInstance, uint32_t* physical_device_count, VkPhysicalDevice*) -> VkResult { + instance_function_called = true; + if (physical_device_count) *physical_device_count = 42; + return VK_INCOMPLETE; + }; + + count = 0; + EXPECT_EQ((DispatchDownstream<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(mock_instance, &count, nullptr)), + VK_INCOMPLETE); + EXPECT_TRUE(instance_function_called); + EXPECT_EQ(count, 42u); + + auto* device_table = dispatch_table_manager.GetDeviceDispatchTable(mock_device); + ASSERT_NE(device_table, nullptr); + device_table->DeviceWaitIdle = [](VkDevice) -> VkResult { + device_function_called = true; + return VK_NOT_READY; + }; + + EXPECT_EQ((DispatchDownstream<&VkuDeviceDispatchTable::DeviceWaitIdle>(mock_device)), VK_NOT_READY); + EXPECT_TRUE(device_function_called); + + // 4. Physical device dispatch and fallback behavior + void* mock_physical_device_vtable = reinterpret_cast(static_cast(0x3333)); + auto mock_physical_device = reinterpret_cast(&mock_physical_device_vtable); + + // Unmapped physical device must return fallback rather than crashing + VkPhysicalDeviceProperties properties{}; + DispatchDownstreamOr<&VkuInstanceDispatchTable::GetPhysicalDeviceProperties>( + [] {}, mock_physical_device, &properties); + + // Mapped physical device forwards downstream through instance table + dispatch_table_manager.SetVkInstance(mock_physical_device, mock_instance); + + static bool physical_device_function_called = false; + physical_device_function_called = false; + instance_table->GetPhysicalDeviceProperties = [](VkPhysicalDevice, VkPhysicalDeviceProperties* physical_device_properties) { + physical_device_function_called = true; + if (physical_device_properties) physical_device_properties->apiVersion = VK_API_VERSION_1_3; + }; + + DispatchDownstream<&VkuInstanceDispatchTable::GetPhysicalDeviceProperties>(mock_physical_device, &properties); + EXPECT_TRUE(physical_device_function_called); + EXPECT_EQ(properties.apiVersion, static_cast(VK_API_VERSION_1_3)); +} + diff --git a/layersvt/test/common/test_dispatch_table_manager.cpp b/layersvt/test/common/test_dispatch_table_manager.cpp new file mode 100644 index 0000000000..9781c4889c --- /dev/null +++ b/layersvt/test/common/test_dispatch_table_manager.cpp @@ -0,0 +1,309 @@ +/* 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/dispatch_table_manager.h" +#include +#include +#include +#include +#include + +using namespace layersvt; + +TEST(DispatchTableManagerTest, GetDispatchKey) { + void* mock_vtable = reinterpret_cast(static_cast(0xDEADBEEF)); + void* mock_object = &mock_vtable; + + EXPECT_EQ(DispatchTableManager::GetDispatchKey(mock_object), + static_cast(reinterpret_cast(mock_vtable))); +} + +TEST(DispatchTableManagerTest, LoaderDataCallback) { + DispatchTableManager dispatch_table_manager; + + void* mock_device_vtable = reinterpret_cast(static_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(DispatchTableManager::GetDispatchKey(mock_device)); + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(mock_device), nullptr); +} + +TEST(DispatchTableManagerTest, InstanceAndDeviceTableLifecycle) { + DispatchTableManager dispatch_table_manager; + + void* mock_instance_vtable = reinterpret_cast(static_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(DispatchTableManager::GetDispatchKey(mock_instance)); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_instance), nullptr); + + void* mock_device_vtable = reinterpret_cast(static_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(DispatchTableManager::GetDispatchKey(mock_device)); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(mock_device), nullptr); +} + +TEST(DispatchTableManagerTest, ReinitPreservesExistingTablePointerStability) { + DispatchTableManager dispatch_table_manager; + + void* mock_device_vtable = reinterpret_cast(static_cast(0x3333)); + auto mock_device = reinterpret_cast(&mock_device_vtable); + PFN_vkGetDeviceProcAddr get_device_proc_addr = [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }; + + auto* first_table = dispatch_table_manager.InitDeviceTable(mock_device, get_device_proc_addr); + auto* second_table = dispatch_table_manager.InitDeviceTable(mock_device, get_device_proc_addr); + EXPECT_EQ(second_table, first_table); +} + +TEST(DispatchTableManagerTest, ConcurrentAccess) { + DispatchTableManager dispatch_table_manager; + + constexpr int kNumberOfThreads = 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(kNumberOfThreads); + for (int thread_index = 0; thread_index < kNumberOfThreads; ++thread_index) { + mock_data[thread_index].instance_vtable = reinterpret_cast(static_cast(0x10000 + thread_index * 0x100)); + mock_data[thread_index].instance = reinterpret_cast(&mock_data[thread_index].instance_vtable); + mock_data[thread_index].device_vtable = reinterpret_cast(static_cast(0x20000 + thread_index * 0x100)); + mock_data[thread_index].device = reinterpret_cast(&mock_data[thread_index].device_vtable); + } + + PFN_vkSetDeviceLoaderData dummy_callback = [](VkDevice, void*) -> VkResult { return VK_SUCCESS; }; + + for (int thread_index = 0; thread_index < kNumberOfThreads; ++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* instance_table = dispatch_table_manager.InitInstanceTable( + my_data.instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(instance_table, nullptr); + + auto* device_table = dispatch_table_manager.InitDeviceTable( + my_data.device, [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(device_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), instance_table); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(my_data.device), device_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) % kNumberOfThreads; + (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(DispatchTableManager::GetDispatchKey(my_data.device)); + dispatch_table_manager.DestroyInstanceTable(DispatchTableManager::GetDispatchKey(my_data.instance)); + } + } + }); + } + + start_flag.store(true); + for (auto& thread : threads) { + thread.join(); + } +} + +TEST(DispatchTableManagerTest, BasicPhysicalDeviceTracking) { + DispatchTableManager dispatch_table_manager; + + auto mock_instance = reinterpret_cast(static_cast(0x1000)); + auto mock_physical_device1 = reinterpret_cast(static_cast(0x2001)); + auto mock_physical_device2 = reinterpret_cast(static_cast(0x2002)); + + EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device1), VK_NULL_HANDLE); + + dispatch_table_manager.SetVkInstance(mock_physical_device1, mock_instance); + dispatch_table_manager.SetVkInstance(mock_physical_device2, mock_instance); + + EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device1), mock_instance); + EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device2), mock_instance); +} + +TEST(DispatchTableManagerTest, RegisterPhysicalDevicesBatch) { + DispatchTableManager dispatch_table_manager; + + auto mock_instance = reinterpret_cast(static_cast(0x1000)); + std::vector physical_devices = { + reinterpret_cast(static_cast(0x2001)), + reinterpret_cast(static_cast(0x2002)), + reinterpret_cast(static_cast(0x2003)), + }; + + dispatch_table_manager.RegisterPhysicalDevices(physical_devices.data(), static_cast(physical_devices.size()), mock_instance); + + EXPECT_EQ(dispatch_table_manager.GetVkInstance(physical_devices[0]), mock_instance); + EXPECT_EQ(dispatch_table_manager.GetVkInstance(physical_devices[1]), mock_instance); + EXPECT_EQ(dispatch_table_manager.GetVkInstance(physical_devices[2]), mock_instance); +} + +TEST(DispatchTableManagerTest, PhysicalDeviceResolvesInstanceDispatchTable) { + DispatchTableManager dispatch_table_manager; + + void* mock_instance_vtable = reinterpret_cast(static_cast(0x1111)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x2222)); + + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_physical_device), nullptr); + + auto* instance_table = dispatch_table_manager.InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(instance_table, nullptr); + + dispatch_table_manager.SetVkInstance(mock_physical_device, mock_instance); + + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_physical_device), instance_table); +} + +TEST(DispatchTableManagerTest, AtomicTeardownOfPhysicalDevicesOnInstanceDestroy) { + DispatchTableManager dispatch_table_manager; + + void* mock_instance_vtable = reinterpret_cast(static_cast(0x1111)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device1 = reinterpret_cast(static_cast(0x2001)); + auto mock_physical_device2 = reinterpret_cast(static_cast(0x2002)); + + auto* instance_table = dispatch_table_manager.InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + ASSERT_NE(instance_table, nullptr); + + dispatch_table_manager.SetVkInstance(mock_physical_device1, mock_instance); + dispatch_table_manager.SetVkInstance(mock_physical_device2, mock_instance); + + EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device1), mock_instance); + EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device2), mock_instance); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_physical_device1), instance_table); + + auto dispatch_key = DispatchTableManager::GetDispatchKey(mock_instance); + dispatch_table_manager.DestroyInstanceTable(dispatch_key); + + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_instance), nullptr); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_physical_device1), nullptr); + EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device1), VK_NULL_HANDLE); + EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device2), VK_NULL_HANDLE); +} + +TEST(DispatchTableManagerTest, NullHandleSafety) { + DispatchTableManager dispatch_table_manager; + + EXPECT_EQ(dispatch_table_manager.GetVkInstance(VK_NULL_HANDLE), VK_NULL_HANDLE); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(static_cast(VK_NULL_HANDLE)), nullptr); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(static_cast(VK_NULL_HANDLE)), nullptr); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(nullptr), nullptr); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(static_cast(nullptr)), nullptr); +} + +TEST(DispatchTableManagerTest, ConcurrentPhysicalDevicesAndLifecycle) { + DispatchTableManager dispatch_table_manager; + + constexpr int kNumberOfThreads = 8; + constexpr int kIterations = 300; + std::atomic start_flag{false}; + std::vector threads; + + struct ThreadPhysicalMockData { + void* instance_vtable; + VkInstance instance; + VkPhysicalDevice physical_device1; + VkPhysicalDevice physical_device2; + }; + + std::vector mock_data(kNumberOfThreads); + for (int thread_index = 0; thread_index < kNumberOfThreads; ++thread_index) { + mock_data[thread_index].instance_vtable = reinterpret_cast(static_cast(0x30000 + thread_index * 0x100)); + mock_data[thread_index].instance = reinterpret_cast(&mock_data[thread_index].instance_vtable); + mock_data[thread_index].physical_device1 = reinterpret_cast(static_cast(0x40000 + thread_index * 0x20)); + mock_data[thread_index].physical_device2 = reinterpret_cast(static_cast(0x40001 + thread_index * 0x20)); + } + + for (int thread_index = 0; thread_index < kNumberOfThreads; ++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) { + auto* instance_table = dispatch_table_manager.InitInstanceTable( + my_data.instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(instance_table, nullptr); + + dispatch_table_manager.SetVkInstance(my_data.physical_device1, my_data.instance); + dispatch_table_manager.SetVkInstance(my_data.physical_device2, my_data.instance); + + EXPECT_EQ(dispatch_table_manager.GetVkInstance(my_data.physical_device1), my_data.instance); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(my_data.physical_device1), instance_table); + + int neighbor_index = (thread_index + 1) % kNumberOfThreads; + (void)dispatch_table_manager.GetVkInstance(mock_data[neighbor_index].physical_device1); + (void)dispatch_table_manager.GetInstanceDispatchTable(mock_data[neighbor_index].physical_device1); + + if ((i % 10) == 0) { + dispatch_table_manager.DestroyInstanceTable(DispatchTableManager::GetDispatchKey(my_data.instance)); + EXPECT_EQ(dispatch_table_manager.GetVkInstance(my_data.physical_device1), VK_NULL_HANDLE); + } + } + }); + } + + start_flag.store(true); + for (auto& thread : threads) { + thread.join(); + } +} + diff --git a/layersvt/test/common/test_layer_base.cpp b/layersvt/test/common/test_layer_base.cpp new file mode 100644 index 0000000000..ba0e154415 --- /dev/null +++ b/layersvt/test/common/test_layer_base.cpp @@ -0,0 +1,786 @@ +/* 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/layer_base.h" +#include "layer_base_test_peer.h" +#include "layer_test_helper.h" +#include +#include +#include + +using namespace layersvt; + +class LifecycleTestLayer : public LayerBase { + public: + bool pre_create_instance_called = false; + bool post_create_instance_called = false; + bool pre_destroy_instance_called = false; + + bool pre_create_device_called = false; + bool post_create_device_called = false; + bool pre_destroy_device_called = false; + + const VkAllocationCallbacks* captured_post_create_instance_allocator = nullptr; + const VkAllocationCallbacks* captured_post_create_device_allocator = nullptr; + + void PreCreateInstance(VkInstanceCreateInfo*, const VkAllocationCallbacks*) override { pre_create_instance_called = true; } + void PostCreateInstance(VkInstance, const VkInstanceCreateInfo*, const VkAllocationCallbacks* allocator) override { + post_create_instance_called = true; + captured_post_create_instance_allocator = allocator; + } + void PreDestroyInstance(VkInstance, const VkAllocationCallbacks*) override { pre_destroy_instance_called = true; } + + void PreCreateDevice(VkPhysicalDevice, VkDeviceCreateInfo*, const VkAllocationCallbacks*) override { + pre_create_device_called = true; + } + void PostCreateDevice(VkDevice, VkPhysicalDevice, const VkDeviceCreateInfo*, const VkAllocationCallbacks* allocator) override { + post_create_device_called = true; + captured_post_create_device_allocator = allocator; + } + void PreDestroyDevice(VkDevice, const VkAllocationCallbacks*) override { pre_destroy_device_called = true; } +}; + +TEST(LayerBaseTest, HookInvocations) { + LifecycleTestLayer layer; + EXPECT_FALSE(layer.pre_create_instance_called); + EXPECT_FALSE(layer.pre_create_device_called); + EXPECT_FALSE(layer.post_create_device_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.PostCreateDevice(VK_NULL_HANDLE, VK_NULL_HANDLE, nullptr, nullptr); + EXPECT_TRUE(layer.post_create_device_called); +} + +TEST(LayerBaseTest, CreateInstanceWithMockChain) { + static void* mock_instance_vtable = reinterpret_cast(static_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, "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; + LifecycleTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + + VkAllocationCallbacks mock_allocator{}; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, &mock_allocator, &instance), VK_SUCCESS); + EXPECT_EQ(instance, mock_instance_handle); + EXPECT_TRUE(layer.pre_create_instance_called); + EXPECT_TRUE(layer.post_create_instance_called); + EXPECT_EQ(layer.captured_post_create_instance_allocator, &mock_allocator); + EXPECT_NE(LayerBaseTestPeer::GetDispatchTableManager(layer).GetInstanceDispatchTable(instance), nullptr); + + LayerBaseTestPeer::DestroyInstance(instance, nullptr); + EXPECT_TRUE(layer.pre_destroy_instance_called); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetInstanceDispatchTable(instance), nullptr); +} + +TEST(LayerBaseTest, CreateInstanceNullHandling) { + LifecycleTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + // Null create info + EXPECT_EQ(LayerBaseTestPeer::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(LayerBaseTestPeer::CreateInstance(&instance_create_info, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, PreCreateNotInvokedOnMissingChain) { + LifecycleTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + EXPECT_EQ(LayerBaseTestPeer::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(LayerBaseTestPeer::CreateInstance(nullptr, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); + EXPECT_FALSE(layer.inspected); + + VkInstanceCreateInfo instance_create_info{}; + EXPECT_EQ(LayerBaseTestPeer::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(static_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; + }); + } + 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; + + MutatingLayer layer; + VkInstance instance = VK_NULL_HANDLE; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, nullptr, &instance), VK_SUCCESS); + EXPECT_EQ(received_flags, 0xABCDu); + LayerBaseTestPeer::DestroyInstance(instance, nullptr); +} + +TEST(LayerBaseTest, PreCreateDeviceMutation) { + class MutatingLayer : public LayerBase { + public: + void PreCreateDevice(VkPhysicalDevice, VkDeviceCreateInfo* create_info, const VkAllocationCallbacks*) override { + if (create_info) { + create_info->flags = 0x5678; + } + } + }; + + static VkDeviceCreateFlags received_flags = 0; + static void* mock_device_vtable = reinterpret_cast(static_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* create_info, + const VkAllocationCallbacks*, VkDevice* device_handle) -> VkResult { + received_flags = create_info->flags; + *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}}; + + VkDeviceCreateInfo device_create_info{}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = &chain_info; + + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x5555)); + + MutatingLayer layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + VkDevice device = VK_NULL_HANDLE; + + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), VK_SUCCESS); + EXPECT_EQ(received_flags, 0x5678u); + + LayerBaseTestPeer::DestroyDevice(device, nullptr); +} + +TEST(LayerBaseTest, TeardownOrdering) { + class TeardownOrderLayer : public LayerBase { + public: + VkInstance captured_instance_in_pre_destroy = VK_NULL_HANDLE; + VkPhysicalDevice mock_physical_device = reinterpret_cast(static_cast(0x9999)); + + void PreDestroyInstance(VkInstance, const VkAllocationCallbacks*) override { + captured_instance_in_pre_destroy = LayerBaseTestPeer::GetVkInstance(mock_physical_device); + } + }; + + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x9999)); + + TeardownOrderLayer layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).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; + }); + LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + + LayerBaseTestPeer::DestroyInstance(mock_instance, nullptr); + + // Verify PreDestroyInstance could still query the physical device mapping + EXPECT_EQ(layer.captured_instance_in_pre_destroy, mock_instance); + // After DestroyInstance finishes, mapping is cleaned up + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device), VK_NULL_HANDLE); +} + +TEST(LayerBaseTest, CreateDeviceWithMockChain) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x5555)); + + static void* mock_device_vtable = reinterpret_cast(static_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; + + LifecycleTestLayer layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + VkDevice device = VK_NULL_HANDLE; + + VkAllocationCallbacks mock_allocator{}; + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, &device_create_info, &mock_allocator, &device), + VK_SUCCESS); + EXPECT_EQ(device, mock_device_handle); + EXPECT_TRUE(layer.pre_create_device_called); + EXPECT_TRUE(layer.post_create_device_called); + EXPECT_EQ(layer.captured_post_create_device_allocator, &mock_allocator); + EXPECT_NE(LayerBaseTestPeer::GetDispatchTableManager(layer).GetDeviceDispatchTable(device), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetDeviceLoaderDataCallback(device), mock_loader_callback); + + LayerBaseTestPeer::DestroyDevice(device, nullptr); + EXPECT_TRUE(layer.pre_destroy_device_called); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetDeviceDispatchTable(device), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetDeviceLoaderDataCallback(device), nullptr); +} + +TEST(LayerBaseTest, CreateDeviceNullHandling) { + LayerBase layer; + VkDevice device = VK_NULL_HANDLE; + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x5555)); + LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + + // Null create info + EXPECT_EQ(LayerBaseTestPeer::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(LayerBaseTestPeer::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(static_cast(0xBAADF00D)); + // Untracked physical device must fail cleanly without crashing + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_untracked_physical_device, &create_info, nullptr, &device), + VK_ERROR_INITIALIZATION_FAILED); + + // Null device pointer must fail cleanly + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_untracked_physical_device, &create_info, nullptr, nullptr), + VK_ERROR_INITIALIZATION_FAILED); + + // VK_NULL_HANDLE physical device must fail cleanly + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(VK_NULL_HANDLE, &create_info, nullptr, &device), + VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, EnumeratePhysicalDevicesMapping) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x7777)); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDevices") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* physical_device_count, VkPhysicalDevice* physical_devices) -> VkResult { + if (!physical_device_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_devices) { + *physical_device_count = 1; + return VK_SUCCESS; + } + physical_devices[0] = reinterpret_cast(static_cast(0x7777)); + *physical_device_count = 1; + return VK_SUCCESS; + }); + } + return nullptr; + }); + + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDevices(mock_instance, &count, nullptr), VK_SUCCESS); + ASSERT_EQ(count, 1u); + + std::vector physical_devices(count); + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDevices(mock_instance, &count, physical_devices.data()), VK_SUCCESS); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device), mock_instance); +} + +class HookTestLayer : public LayerBase { + public: + using LayerBase::LayerBase; + using LayerBase::PostCreateDevice; + using LayerBase::PostCreateInstance; + using LayerBase::PreCreateDevice; + using LayerBase::PreCreateInstance; + using LayerBase::PreDestroyDevice; + using LayerBase::PreDestroyInstance; +}; + +TEST(LayerBaseTest, DefaultHooksExecution) { + HookTestLayer base; + + // 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, nullptr); + base.PreDestroyInstance(VK_NULL_HANDLE, nullptr); + + VkDeviceCreateInfo device_create_info{}; + base.PreCreateDevice(VK_NULL_HANDLE, &device_create_info, nullptr); + base.PostCreateDevice(VK_NULL_HANDLE, VK_NULL_HANDLE, &device_create_info, nullptr); + base.PreDestroyDevice(VK_NULL_HANDLE, nullptr); +} + +TEST(LayerBaseTest, EnumeratePhysicalDeviceGroupsMapping) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device1 = reinterpret_cast(static_cast(0x8881)); + auto mock_physical_device2 = reinterpret_cast(static_cast(0x8882)); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDeviceGroups") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_groups) -> VkResult { + if (!physical_device_group_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_device_groups) { + *physical_device_group_count = 1; + return VK_SUCCESS; + } + physical_device_groups[0].physicalDeviceCount = 2; + physical_device_groups[0].physicalDevices[0] = + reinterpret_cast(static_cast(0x8881)); + physical_device_groups[0].physicalDevices[1] = + reinterpret_cast(static_cast(0x8882)); + *physical_device_group_count = 1; + return VK_SUCCESS; + }); + } + return nullptr; + }); + + void* missing_instance_vtable = reinterpret_cast(static_cast(0xBAADF00D)); + auto missing_instance = reinterpret_cast(&missing_instance_vtable); + // Missing table or function + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(missing_instance, nullptr, nullptr), + VK_ERROR_INITIALIZATION_FAILED); + + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(mock_instance, &count, nullptr), VK_SUCCESS); + ASSERT_EQ(count, 1u); + + std::vector groups(count); + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(mock_instance, &count, groups.data()), VK_SUCCESS); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device1), mock_instance); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device2), mock_instance); +} + +TEST(LayerBaseTest, EnumeratePhysicalDeviceGroupsKHRFallbackMapping) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x8883)); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDeviceGroupsKHR") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_groups) -> VkResult { + if (!physical_device_group_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_device_groups) { + *physical_device_group_count = 1; + return VK_SUCCESS; + } + physical_device_groups[0].physicalDeviceCount = 1; + physical_device_groups[0].physicalDevices[0] = + reinterpret_cast(static_cast(0x8883)); + *physical_device_group_count = 1; + return VK_SUCCESS; + }); + } + return nullptr; + }); + + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(mock_instance, &count, nullptr), VK_SUCCESS); + ASSERT_EQ(count, 1u); + + std::vector groups(count); + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(mock_instance, &count, groups.data()), VK_SUCCESS); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device), mock_instance); +} + +TEST(LayerBaseTest, EnumeratePhysicalDevicesNullTable) { + LayerBase layer; + void* missing_instance_vtable = reinterpret_cast(static_cast(0xBAADF00D)); + auto missing_instance = reinterpret_cast(&missing_instance_vtable); + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::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; + + LifecycleTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, CreateDeviceNullFpCreateDevice) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x5555)); + + LifecycleTestLayer layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).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; + + VkDevice device = VK_NULL_HANDLE; + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), + VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, LayerTracking) { + EXPECT_EQ(LayerBase::Get(), nullptr); + { + LayerBase layer; + EXPECT_EQ(LayerBase::Get(), &layer); + } + EXPECT_EQ(LayerBase::Get(), nullptr); + + { + LayerBase layer1; + EXPECT_EQ(LayerBase::Get(), &layer1); + } + EXPECT_EQ(LayerBase::Get(), nullptr); +} + +TEST(LayerBaseTest, GetKnownCommandsCommon) { + LayerBase layer; + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateInstance"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkDestroyInstance"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumeratePhysicalDevices"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumeratePhysicalDeviceGroups"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkNonExistentInstanceFunction"), nullptr); + + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetDeviceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkDestroyDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkNonExistentDeviceFunction"), nullptr); +} + +class TestDerivedLayer : public LayerBase { + public: + static inline auto mock_custom_instance_function = + reinterpret_cast(static_cast(0x12345678)); + static inline auto mock_custom_device_function = + reinterpret_cast(static_cast(0x87654321)); + + protected: + PFN_vkVoidFunction GetLayerInstanceCommand(const char* command_name) override { + if (std::strcmp(command_name, "vkCustomInstanceCmd") == 0) { + return mock_custom_instance_function; + } + return nullptr; + } + + PFN_vkVoidFunction GetLayerDeviceCommand(const char* command_name) override { + if (std::strcmp(command_name, "vkCustomDeviceCmd") == 0) { + return mock_custom_device_function; + } + return nullptr; + } +}; + +TEST(LayerBaseTest, LayerSpecificOverrideHooks) { + TestDerivedLayer layer; + + // Custom commands handled by virtual hooks + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkCustomInstanceCmd"), TestDerivedLayer::mock_custom_instance_function); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkCustomDeviceCmd"), TestDerivedLayer::mock_custom_device_function); + + // Common commands still handled by base template method fallback + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateInstance"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDevice"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetDeviceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkDestroyDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkCreateDevice"), nullptr); + + // Unhandled commands return nullptr + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkUnknownCmd"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkUnknownCmd"), nullptr); +} + +TEST(LayerBaseTest, ProcAddrDispatchChain) { + TestDerivedLayer layer; + auto& dispatch_table_manager = LayerBaseTestPeer::GetDispatchTableManager(layer); + + // 1. Global commands can be queried with VK_NULL_HANDLE + EXPECT_NE(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"), nullptr); + + // Non-global commands must return nullptr when instance is VK_NULL_HANDLE + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkDestroyInstance"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCustomInstanceCmd"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCustomDeviceCmd"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkNextLayerCmd"), nullptr); + + // GetDeviceProcAddr with VK_NULL_HANDLE must always return nullptr + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkGetDeviceProcAddr"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkDestroyDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkCustomDeviceCmd"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkNextLayerCmd"), nullptr); + + // 2. Querying with valid instance handle + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + static auto mock_next_instance_command = reinterpret_cast(static_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_instance_command; + } + return nullptr; + }); + + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCustomInstanceCmd"), + TestDerivedLayer::mock_custom_instance_function); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCustomDeviceCmd"), + TestDerivedLayer::mock_custom_device_function); + EXPECT_NE(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCreateDevice"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkDestroyInstance"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkNextLayerInstCmd"), mock_next_instance_command); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkUnimplementedCmd"), nullptr); + + // 3. Querying with valid device handle + void* mock_device_vtable = reinterpret_cast(static_cast(0x55667788)); + auto mock_device = reinterpret_cast(&mock_device_vtable); + static auto mock_next_device_command = reinterpret_cast(static_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_device_command; + } + return nullptr; + }); + + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkCustomDeviceCmd"), + TestDerivedLayer::mock_custom_device_function); + EXPECT_NE(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkGetDeviceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkDestroyDevice"), nullptr); + // Instance commands must return nullptr via GetDeviceProcAddr even with valid device + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkDestroyInstance"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkNextLayerDevCmd"), mock_next_device_command); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkUnimplementedCmd"), nullptr); +} + +TEST(LayerBaseTest, DestroyNullHandles) { + class DestroyTrackingLayer : public LayerBase { + public: + int pre_destroy_instance_calls = 0; + int pre_destroy_device_calls = 0; + + protected: + void PreDestroyInstance(VkInstance, const VkAllocationCallbacks*) override { + ADD_FAILURE() << "PreDestroyInstance should not be called for VK_NULL_HANDLE"; + ++pre_destroy_instance_calls; + } + void PreDestroyDevice(VkDevice, const VkAllocationCallbacks*) override { + ADD_FAILURE() << "PreDestroyDevice should not be called for VK_NULL_HANDLE"; + ++pre_destroy_device_calls; + } + }; + + DestroyTrackingLayer layer; + // Vulkan specification mandates destroying VK_NULL_HANDLE is a valid no-op. + // Virtual PreDestroy hooks are bypassed when destroying VK_NULL_HANDLE. + LayerBaseTestPeer::DestroyInstance(VK_NULL_HANDLE, nullptr); + EXPECT_EQ(layer.pre_destroy_instance_calls, 0); + + LayerBaseTestPeer::DestroyDevice(VK_NULL_HANDLE, nullptr); + EXPECT_EQ(layer.pre_destroy_device_calls, 0); +} + +TEST(LayerBaseTest, PhysicalDeviceResolvesInstanceTable) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + void* mock_physical_device_vtable = reinterpret_cast(static_cast(0x55667788)); + auto mock_physical_device = reinterpret_cast(&mock_physical_device_vtable); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + + EXPECT_NE(LayerBaseTestPeer::GetInstanceDispatchTable(mock_physical_device), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceDispatchTable(mock_physical_device), + LayerBaseTestPeer::GetInstanceDispatchTable(mock_instance)); +} + +TEST(LayerBaseTest, EnumeratePhysicalDeviceGroupsKHRResolution) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + + PFN_vkVoidFunction function_khr = + LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkEnumeratePhysicalDeviceGroupsKHR"); + PFN_vkVoidFunction function_core = + LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkEnumeratePhysicalDeviceGroups"); + EXPECT_NE(function_khr, nullptr); + EXPECT_EQ(function_khr, function_core); +} + +TEST(LayerBaseTest, ResetLayerMultipleInvocations) { + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(/*destroy=*/true); + EXPECT_EQ(LayerBase::Get(), nullptr); +} + +TEST(LayerBaseTest, ResetLayerCrossType) { + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(/*destroy=*/true); + EXPECT_EQ(LayerBase::Get(), nullptr); +} diff --git a/layersvt/test/common/test_layer_entrypoints.cpp b/layersvt/test/common/test_layer_entrypoints.cpp new file mode 100644 index 0000000000..da36c7d69e --- /dev/null +++ b/layersvt/test/common/test_layer_entrypoints.cpp @@ -0,0 +1,80 @@ +/* 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/layer_base.h" +#include "common/layer_manifest.h" +#include +#include +#include + +namespace layersvt { +namespace { + +class EntrypointsTestLayer : public LayerBase { + public: + explicit EntrypointsTestLayer(const LayerManifest* manifest) : manifest_(manifest) {} + ~EntrypointsTestLayer() override = default; + + [[nodiscard]] const LayerManifest* GetLayerManifest() const override { return manifest_; } + + private: + const LayerManifest* manifest_; +}; + +TEST(LayerEntrypointsTest, ForwardingCalls) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Entrypoints", + }; + EntrypointsTestLayer test_layer(&manifest); + uint32_t property_count = 0; + + EXPECT_EQ(::vkEnumerateInstanceLayerProperties(&property_count, nullptr), VK_SUCCESS); + EXPECT_EQ(::vkEnumerateInstanceExtensionProperties("VK_LAYER_TEST_Entrypoints", &property_count, nullptr), VK_SUCCESS); + EXPECT_EQ(::vkEnumerateInstanceExtensionProperties(nullptr, &property_count, nullptr), VK_ERROR_LAYER_NOT_PRESENT); + EXPECT_EQ(::vkEnumerateDeviceLayerProperties(VK_NULL_HANDLE, &property_count, nullptr), VK_SUCCESS); + EXPECT_EQ(::vkEnumerateDeviceExtensionProperties(VK_NULL_HANDLE, nullptr, &property_count, nullptr), VK_SUCCESS); + + EXPECT_NE(::vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkGetInstanceProcAddr"), nullptr); + EXPECT_EQ(::vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkUnknownFunction"), nullptr); + EXPECT_EQ(::vkGetDeviceProcAddr(VK_NULL_HANDLE, "vkUnknownFunction"), nullptr); + + VkNegotiateLayerInterface version_interface{ + .sType = LAYER_NEGOTIATE_INTERFACE_STRUCT, + .pNext = nullptr, + .loaderLayerInterfaceVersion = 2, + }; + EXPECT_EQ(::vkNegotiateLoaderLayerInterfaceVersion(&version_interface), VK_SUCCESS); + EXPECT_EQ(version_interface.loaderLayerInterfaceVersion, 2u); + EXPECT_NE(version_interface.pfnGetInstanceProcAddr, nullptr); + EXPECT_NE(version_interface.pfnGetDeviceProcAddr, nullptr); + + VkNegotiateLayerInterface version_one_interface{ + .sType = LAYER_NEGOTIATE_INTERFACE_STRUCT, + .pNext = nullptr, + .loaderLayerInterfaceVersion = 1, + }; + EXPECT_EQ(::vkNegotiateLoaderLayerInterfaceVersion(&version_one_interface), VK_SUCCESS); + EXPECT_EQ(version_one_interface.loaderLayerInterfaceVersion, 1u); + + VkNegotiateLayerInterface unsupported_version_interface{ + .sType = LAYER_NEGOTIATE_INTERFACE_STRUCT, + .pNext = nullptr, + .loaderLayerInterfaceVersion = 0, + }; + EXPECT_EQ(::vkNegotiateLoaderLayerInterfaceVersion(&unsupported_version_interface), VK_ERROR_INITIALIZATION_FAILED); +} + +} // namespace +} // namespace layersvt diff --git a/layersvt/test/common/test_layer_manifest.cpp b/layersvt/test/common/test_layer_manifest.cpp new file mode 100644 index 0000000000..ea1bdb7cba --- /dev/null +++ b/layersvt/test/common/test_layer_manifest.cpp @@ -0,0 +1,562 @@ +/* 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/layer_manifest.h" +#include "common/layer_base.h" +#include "layer_base_test_peer.h" +#include +#include +#include + +using namespace layersvt; + +TEST(LayerManifestTest, LayerProperties) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + .description = "Sample Test Layer", + .spec_version = VK_API_VERSION_1_3, + .implementation_version = 42, + }; + VkLayerProperties layer_properties = manifest.GetLayerProperties(); + + 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); +} + +class ManifestTestLayer : public LayerBase { + public: + explicit ManifestTestLayer(const LayerManifest* manifest) : manifest_(manifest) {} + const LayerManifest* GetLayerManifest() const override { return manifest_; } + + private: + const LayerManifest* manifest_; +}; + +TEST(LayerBaseEnumerationTest, LayerProperties) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + .description = "Sample Test Layer", + .spec_version = VK_API_VERSION_1_3, + .implementation_version = 42, + }; + ManifestTestLayer layer(&manifest); + + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::EnumerateInstanceLayerProperties(&count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + + VkLayerProperties layer_properties{}; + result = LayerBaseTestPeer::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(LayerBaseEnumerationTest, InstanceExtensions) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + .instance_extensions = { + {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, + }, + }; + ManifestTestLayer layer(&manifest); + + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_TEST_Sample", &count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector extensions(count); + result = LayerBaseTestPeer::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 with nullptr or unknown layer name must return VK_ERROR_LAYER_NOT_PRESENT per LLP_LAYER_15 + count = 5; + result = LayerBaseTestPeer::EnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + EXPECT_EQ(result, VK_ERROR_LAYER_NOT_PRESENT); + EXPECT_EQ(count, 0u); + + count = 5; + result = LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_UNKNOWN", &count, nullptr); + EXPECT_EQ(result, VK_ERROR_LAYER_NOT_PRESENT); + EXPECT_EQ(count, 0u); +} + +TEST(LayerBaseEnumerationTest, DeviceExtensionsDownstreamMerge) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + .device_extensions = { + {"VK_EXT_custom_layer_extension", 1}, + }, + }; + ManifestTestLayer layer(&manifest); + + // 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 = LayerBaseTestPeer::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 = LayerBaseTestPeer::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(LayerBaseEnumerationTest, DeviceExtensionsDownstreamIncomplete) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + }; + ManifestTestLayer layer(&manifest); + + // Mock downstream driver returning 5 extensions + auto mock_downstream = [](VkPhysicalDevice, const char*, uint32_t* count, VkExtensionProperties* properties) -> VkResult { + if (!properties) { + *count = 5; + return VK_SUCCESS; + } + uint32_t to_copy = std::min(*count, 5u); + for (uint32_t i = 0; i < to_copy; ++i) { + std::snprintf(properties[i].extensionName, VK_MAX_EXTENSION_NAME_SIZE, "VK_EXT_driver_%u", i); + properties[i].specVersion = 1; + } + *count = to_copy; + return (to_copy < 5u) ? VK_INCOMPLETE : VK_SUCCESS; + }; + + uint32_t count = 1; + VkExtensionProperties property{}; + VkResult result = LayerBaseTestPeer::EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, &property, mock_downstream); + EXPECT_EQ(result, VK_INCOMPLETE); + EXPECT_EQ(count, 1u); +} + +TEST(LayerBaseEnumerationTest, 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 manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + .tool_properties = layer_tool_properties, + }; + ManifestTestLayer layer(&manifest); + + // Mock downstream reporting 1 driver tool + auto mock_downstream_tool = [](VkPhysicalDevice, uint32_t* count, VkPhysicalDeviceToolPropertiesEXT* properties) -> VkResult { + if (!properties) { + *count = 1; + return VK_SUCCESS; + } + EXPECT_EQ(properties[0].sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT); + EXPECT_EQ(properties[0].pNext, nullptr); + std::strncpy(properties[0].name, "DriverTool", VK_MAX_EXTENSION_NAME_SIZE); + *count = 1; + return VK_SUCCESS; + }; + + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::GetPhysicalDeviceToolProperties(nullptr, &count, nullptr, mock_downstream_tool); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + + std::vector tools(count); + result = LayerBaseTestPeer::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(LayerBaseEnumerationTest, DeviceLayerPropertiesOverload) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + }; + ManifestTestLayer layer(&manifest); + + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::EnumerateDeviceLayerProperties( + reinterpret_cast(static_cast(0x123)), &count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); +} + +TEST(LayerBaseEnumerationTest, DeviceExtensionsNullDownstream) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + .device_extensions = { + {"VK_EXT_standalone_extension", 1}, + }, + }; + ManifestTestLayer layer(&manifest); + + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, nullptr, nullptr), VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector extensions(count); + EXPECT_EQ(LayerBaseTestPeer::EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, extensions.data(), nullptr), VK_SUCCESS); + EXPECT_EQ(count, 1u); + EXPECT_STREQ(extensions[0].extensionName, "VK_EXT_standalone_extension"); +} + +TEST(LayerBaseEnumerationTest, QueryDifferentLayerName) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + }; + ManifestTestLayer layer(&manifest); + + uint32_t count = 5; + EXPECT_EQ(LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_OTHER", &count, nullptr), VK_ERROR_LAYER_NOT_PRESENT); + EXPECT_EQ(count, 0u); + + count = 5; + EXPECT_EQ(LayerBaseTestPeer::EnumerateDeviceExtensionProperties(nullptr, "VK_LAYER_OTHER", &count, nullptr, nullptr), + VK_ERROR_LAYER_NOT_PRESENT); + EXPECT_EQ(count, 0u); +} + +TEST(LayerBaseEnumerationTest, DeviceExtensionsMatchingLayerName) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + .device_extensions = { + {"VK_EXT_custom_ext1", 1}, + {"VK_EXT_custom_ext2", 2}, + }, + }; + ManifestTestLayer layer(&manifest); + + // 1. Query count with matching layer name (returns layer's own device extension count) + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::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 = LayerBaseTestPeer::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 = LayerBaseTestPeer::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(LayerBaseEnumerationTest, DeviceExtensionsForwardDifferentLayerName) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + }; + ManifestTestLayer layer(&manifest); + + 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 = LayerBaseTestPeer::EnumerateDeviceExtensionProperties( + nullptr, "VK_LAYER_DOWNSTREAM", &count, nullptr, mock_downstream); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector extensions(count); + result = LayerBaseTestPeer::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(LayerBaseEnumerationTest, ToolPropertiesErrorPropagation) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Sample", + }; + ManifestTestLayer layer(&manifest); + + auto error_downstream_tool = [](VkPhysicalDevice, uint32_t*, VkPhysicalDeviceToolPropertiesEXT*) -> VkResult { + return VK_ERROR_OUT_OF_HOST_MEMORY; + }; + + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::GetPhysicalDeviceToolProperties(nullptr, &count, nullptr, error_downstream_tool), + VK_ERROR_OUT_OF_HOST_MEMORY); +} + +TEST(LayerBaseHooksTest, ProcessDeviceExtensionsFiltering) { + class FilteringTestLayer : public LayerBase { + public: + explicit FilteringTestLayer(const LayerManifest* manifest) : manifest_(manifest) {} + const LayerManifest* GetLayerManifest() const override { return manifest_; } + + protected: + void ProcessDeviceExtensions(VkPhysicalDevice, const char* layer_name, + std::vector& extensions) const override { + if (layer_name == nullptr) { + std::erase_if(extensions, [](const VkExtensionProperties& extension) { + return std::strcmp(extension.extensionName, "VK_EXT_disallowed") == 0; + }); + } + } + + private: + const LayerManifest* manifest_; + }; + + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Filtering", + .device_extensions = { + {"VK_EXT_allowed_1", 1}, + {"VK_EXT_disallowed", 1}, + {"VK_EXT_allowed_2", 1}, + }, + }; + FilteringTestLayer layer(&manifest); + + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, nullptr, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + + std::vector extensions(count); + result = LayerBaseTestPeer::EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, extensions.data(), nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + EXPECT_STREQ(extensions[0].extensionName, "VK_EXT_allowed_1"); + EXPECT_STREQ(extensions[1].extensionName, "VK_EXT_allowed_2"); +} + +TEST(LayerBaseHooksTest, ProcessInstanceExtensionsAugmenting) { + class AugmentingTestLayer : public LayerBase { + public: + explicit AugmentingTestLayer(const LayerManifest* manifest) : manifest_(manifest) {} + const LayerManifest* GetLayerManifest() const override { return manifest_; } + + protected: + void ProcessInstanceExtensions(const char* layer_name, + std::vector& extensions) const override { + if (layer_name == nullptr || std::strcmp(layer_name, "VK_LAYER_TEST_Augmenting") == 0) { + VkExtensionProperties dynamic_extension{}; + std::strncpy(dynamic_extension.extensionName, "VK_EXT_dynamic_instance_ext", VK_MAX_EXTENSION_NAME_SIZE); + dynamic_extension.specVersion = 2; + extensions.push_back(dynamic_extension); + } + } + + private: + const LayerManifest* manifest_; + }; + + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Augmenting", + .instance_extensions = { + {"VK_EXT_static_instance_ext", 1}, + }, + }; + AugmentingTestLayer layer(&manifest); + + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_TEST_Augmenting", &count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + + std::vector extensions(count); + result = LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_TEST_Augmenting", &count, extensions.data()); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 2u); + EXPECT_STREQ(extensions[0].extensionName, "VK_EXT_static_instance_ext"); + EXPECT_STREQ(extensions[1].extensionName, "VK_EXT_dynamic_instance_ext"); +} + +TEST(LayerBaseHooksTest, ProcessToolPropertiesCustomizing) { + class ToolCustomizingLayer : public LayerBase { + public: + explicit ToolCustomizingLayer(const LayerManifest* manifest) : manifest_(manifest) {} + const LayerManifest* GetLayerManifest() const override { return manifest_; } + + protected: + void ProcessToolProperties(VkPhysicalDevice, + std::vector& tools) const override { + for (auto& tool : tools) { + std::strncpy(tool.description, "Customized Description", VK_MAX_DESCRIPTION_SIZE); + } + } + + private: + const LayerManifest* manifest_; + }; + + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Tool", + .tool_properties = VkPhysicalDeviceToolPropertiesEXT{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, + .pNext = nullptr, + .name = "TestTool", + .version = "1.0", + .purposes = VK_TOOL_PURPOSE_PROFILING_BIT_EXT, + .description = "Original Description", + .layer = "VK_LAYER_TEST_Tool", + }, + }; + ToolCustomizingLayer layer(&manifest); + + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::GetPhysicalDeviceToolProperties(nullptr, &count, nullptr, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 1u); + + std::vector tools(count); + result = LayerBaseTestPeer::GetPhysicalDeviceToolProperties(nullptr, &count, tools.data(), nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(tools[0].description, "Customized Description"); +} + +TEST(LayerBaseTest, GetKnownCommandsCommonWithManifest) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Common", + }; + ManifestTestLayer layer(&manifest); + + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateInstanceLayerProperties"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateDeviceLayerProperties"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateDeviceExtensionProperties"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkEnumerateDeviceLayerProperties"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkEnumerateDeviceExtensionProperties"), nullptr); + + // Without tool_properties in manifest, tooling functions return nullptr + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetPhysicalDeviceToolPropertiesEXT"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetPhysicalDeviceToolProperties"), nullptr); +} + +TEST(LayerBaseTest, GetKnownCommandsWithToolProperties) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Common", + .tool_properties = VkPhysicalDeviceToolPropertiesEXT{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, + .pNext = nullptr, + .name = "VK_LAYER_TEST_Common", + .version = "1", + .purposes = VK_TOOL_PURPOSE_TRACING_BIT_EXT, + .description = "Test layer", + .layer = "VK_LAYER_TEST_Common", + }, + }; + ManifestTestLayer layer(&manifest); + + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetPhysicalDeviceToolPropertiesEXT"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetPhysicalDeviceToolProperties"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetPhysicalDeviceToolPropertiesEXT"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetPhysicalDeviceToolProperties"), nullptr); +} + +TEST(LayerBaseTest, GetPhysicalDeviceToolPropertiesDispatch) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Common", + .tool_properties = VkPhysicalDeviceToolPropertiesEXT{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, + .pNext = nullptr, + .name = "VK_LAYER_TEST_Common", + .version = "1", + .purposes = VK_TOOL_PURPOSE_TRACING_BIT_EXT, + .description = "Test layer", + .layer = "VK_LAYER_TEST_Common", + }, + }; + ManifestTestLayer layer(&manifest); + + // Test with null physical device (no downstream lookup) + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::GetPhysicalDeviceToolProperties(VK_NULL_HANDLE, &count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + ASSERT_EQ(count, 1u); + + std::vector tools(count); + result = LayerBaseTestPeer::GetPhysicalDeviceToolProperties(VK_NULL_HANDLE, &count, tools.data()); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(tools[0].name, "VK_LAYER_TEST_Common"); +} + +TEST(LayerBaseEnumerationTest, EmptyExtensionsWithBuffer) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Empty", + }; + ManifestTestLayer layer(&manifest); + + uint32_t count = 5; + VkExtensionProperties properties[5]{}; + VkResult result = LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_TEST_Empty", &count, properties); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 0u); + + count = 5; + result = LayerBaseTestPeer::EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, properties, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 0u); +} + diff --git a/layersvt/test/layer_test_helper.h b/layersvt/test/layer_test_helper.h index f1421dbb06..8baa275e19 100644 --- a/layersvt/test/layer_test_helper.h +++ b/layersvt/test/layer_test_helper.h @@ -26,6 +26,7 @@ #include #include #include +#include namespace layer_test { @@ -75,4 +76,31 @@ class VulkanInstanceBuilder { std::vector _extension_names; }; +namespace detail { +inline void (*&GetActiveLayerDeleter())() { + static void (*active_layer_deleter)() = nullptr; + return active_layer_deleter; +} +} // namespace detail + +/** + * Instantiates a pristine layer instance for test fixtures. + * Automatically registers the new object as the active singleton (LayerBase::Get()). + * Resets any previously active layer instance across template types before constructing. + * Pass destroy = true to tear down the active instance and restore LayerBase::Get() to nullptr. + */ +template +inline void ResetLayer(bool destroy = false) { + static std::unique_ptr test_instance; + if (detail::GetActiveLayerDeleter() != nullptr) { + detail::GetActiveLayerDeleter()(); + detail::GetActiveLayerDeleter() = nullptr; + } + test_instance.reset(); + if (!destroy) { + test_instance = std::make_unique(); + detail::GetActiveLayerDeleter() = []() { test_instance.reset(); }; + } +} + } // namespace layer_test