From 92cdcce78c76843f857dfbaeab7bab47b89ae468 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Tue, 8 Sep 2026 18:23:40 +0100 Subject: [PATCH 01/10] layersvt: Add DispatchTableManager to layersvt_common Introduce DispatchTableManager to provide thread-safe storage, initialization, and lookup for Vulkan instance and device dispatch tables. Key components: - Thread-safe storage with std::mutex - GetDispatchKey() helper for dispatchable handles - Loader callback tracking (VK_LOADER_DATA_CALLBACK) Bug: Test: new tests - DispatchTableManagerTest#GetDispatchKey, DispatchTableManagerTest#LoaderDataCallback, DispatchTableManagerTest#InstanceAndDeviceTableLifecycle, DispatchTableManagerTest#ConcurrentAccess Change-Id: I73cf682789015e4d41ab6f42f03964f16a6a6964 --- layersvt/CMakeLists.txt | 2 + layersvt/common/CMakeLists.txt | 32 ++++ layersvt/common/dispatch_table_manager.cpp | 99 +++++++++++ layersvt/common/dispatch_table_manager.h | 122 ++++++++++++++ layersvt/test/CMakeLists.txt | 16 ++ .../common/test_dispatch_table_manager.cpp | 158 ++++++++++++++++++ 6 files changed, 429 insertions(+) create mode 100644 layersvt/common/CMakeLists.txt create mode 100644 layersvt/common/dispatch_table_manager.cpp create mode 100644 layersvt/common/dispatch_table_manager.h create mode 100644 layersvt/test/common/test_dispatch_table_manager.cpp diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 92538d5e3a..75d74cb3a3 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) diff --git a/layersvt/common/CMakeLists.txt b/layersvt/common/CMakeLists.txt new file mode 100644 index 0000000000..371235f545 --- /dev/null +++ b/layersvt/common/CMakeLists.txt @@ -0,0 +1,32 @@ +# 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 +) + +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 +) diff --git a/layersvt/common/dispatch_table_manager.cpp b/layersvt/common/dispatch_table_manager.cpp new file mode 100644 index 0000000000..8d72eda344 --- /dev/null +++ b/layersvt/common/dispatch_table_manager.cpp @@ -0,0 +1,99 @@ +/* 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 + +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)); + return iterator->second.get(); +} + +VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(VkInstance instance) const { + assert(instance != VK_NULL_HANDLE); + 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; +} + +void DispatchTableManager::DestroyInstanceTable(Key key) { + assert(key != Key{}); + std::lock_guard lock(instance_mutex_); + instance_tables_.erase(key); +} + +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 { + 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..1e52823f4e --- /dev/null +++ b/layersvt/common/dispatch_table_manager.h @@ -0,0 +1,122 @@ +/* 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. + * + * Also tracks the loader data callback (VK_LOADER_DATA_CALLBACK) to allow + * layers 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 dispatchable instance. + * Returns a pointer to the stored table on success, or nullptr if not registered. + */ + [[nodiscard]] VkuInstanceDispatchTable* GetInstanceDispatchTable(VkInstance instance) const; + + /** + * Destroys the instance dispatch table for the given dispatch key. + * Callers should capture the Key beforehand via GetDispatchKey(...) before + * downstream vkDestroyInstance invalidates the handle. + */ + void DestroyInstanceTable(Key key); + + // 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 not registered. + */ + [[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; + + // Note on concurrency: Dispatch table lookups perform very fast hash map lookups. + // std::mutex is intentionally preferred over std::shared_mutex because the atomic + // increment/decrement operations and cacheline bouncing of shared reader locks + // (std::shared_lock) introduce more overhead than short, uncontended mutex acquisitions. + mutable std::mutex instance_mutex_; + std::unordered_map> instance_tables_; + + mutable std::mutex device_mutex_; + std::unordered_map> device_tables_; + std::unordered_map loader_callbacks_; +}; +} // namespace layersvt diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index cd635acb9e..91202477b2 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -86,3 +86,19 @@ if (TARGET VkLayer_DeviceMemoryReport) endif() endif() +add_executable(test_common_layer + common/test_dispatch_table_manager.cpp + layer_test_main.cpp +) +target_link_libraries(test_common_layer PRIVATE + layersvt_common + GTest::gtest + Vulkan::Headers + Vulkan::UtilityHeaders +) +add_test(NAME test_common_layer COMMAND test_common_layer) +set_target_properties(test_common_layer PROPERTIES FOLDER "layers/common/Test") + +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/test_dispatch_table_manager.cpp b/layersvt/test/common/test_dispatch_table_manager.cpp new file mode 100644 index 0000000000..10f20ef44b --- /dev/null +++ b/layersvt/test/common/test_dispatch_table_manager.cpp @@ -0,0 +1,158 @@ +/* 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 + +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(); + } +} From 8558ab588714a44f8fe04421fe23bcb871f044d6 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Tue, 8 Sep 2026 18:24:13 +0100 Subject: [PATCH 02/10] layersvt: Add physical device tracking to DispatchTableManager Extend DispatchTableManager to maintain thread-safe associations between VkPhysicalDevice handles and parent VkInstance handles alongside dispatch tables: - Add SetVkInstance, RegisterPhysicalDevices, GetVkInstance, MapPhysicalDevices, and UnmapPhysicalDevices. - Implement single-lock, atomic teardown in DestroyInstanceTable to clean up the instance dispatch table and unmap all associated physical devices under instance_mutex_ in one critical section. - Add strongly-typed GetInstanceDispatchTable(VkPhysicalDevice) and GetInstanceDispatchTable(std::nullptr_t) overloads. - Guard GetDeviceDispatchTable against null object handles. - Add comprehensive physical device tracking and concurrency unit tests. Bug: Test: new tests - DispatchTableManagerTest#SetAndGetVkInstance, DispatchTableManagerTest#RegisterPhysicalDevices, DispatchTableManagerTest#MapPhysicalDevices, DispatchTableManagerTest#UnmapPhysicalDevices, DispatchTableManagerTest#GetInstanceDispatchTablePhysicalDevice, DispatchTableManagerTest#ConcurrentPhysicalDevices Change-Id: I5b05dc0b42701d659485c3966c4ca6be6a6a6964 --- layersvt/common/dispatch_table_manager.cpp | 66 +++++++- layersvt/common/dispatch_table_manager.h | 53 ++++-- .../common/test_dispatch_table_manager.cpp | 151 ++++++++++++++++++ 3 files changed, 258 insertions(+), 12 deletions(-) diff --git a/layersvt/common/dispatch_table_manager.cpp b/layersvt/common/dispatch_table_manager.cpp index 8d72eda344..87519264c6 100644 --- a/layersvt/common/dispatch_table_manager.cpp +++ b/layersvt/common/dispatch_table_manager.cpp @@ -14,7 +14,10 @@ */ #include "dispatch_table_manager.h" +#include +#include #include +#include namespace layersvt { @@ -28,11 +31,14 @@ VkuInstanceDispatchTable* DispatchTableManager::InitInstanceTable(VkInstance ins 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 { - assert(instance != VK_NULL_HANDLE); + if (instance == VK_NULL_HANDLE) { + return nullptr; + } Key key = GetDispatchKey(instance); std::lock_guard lock(instance_mutex_); auto table_iterator = instance_tables_.find(key); @@ -42,12 +48,67 @@ VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(VkInsta 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); @@ -61,6 +122,9 @@ VkuDeviceDispatchTable* DispatchTableManager::InitDeviceTable(VkDevice device, P } 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); diff --git a/layersvt/common/dispatch_table_manager.h b/layersvt/common/dispatch_table_manager.h index 1e52823f4e..db7f312f68 100644 --- a/layersvt/common/dispatch_table_manager.h +++ b/layersvt/common/dispatch_table_manager.h @@ -27,10 +27,11 @@ namespace layersvt { /** - * Thread-safe manager for Vulkan instance and device dispatch tables. + * Thread-safe manager for Vulkan instance and device dispatch tables and physical device tracking. * - * Also tracks the loader data callback (VK_LOADER_DATA_CALLBACK) to allow - * layers to initialize dispatchable objects created internally. + * 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: @@ -48,7 +49,7 @@ class DispatchTableManager final { } // Instance dispatch tables - + /** * Initializes and stores an instance dispatch table using downstream vkGetInstanceProcAddr. * Returns a non-null pointer to the stored dispatch table. @@ -56,18 +57,49 @@ class DispatchTableManager final { VkuInstanceDispatchTable* InitInstanceTable(VkInstance instance, PFN_vkGetInstanceProcAddr get_instance_proc_addr); /** - * Looks up the instance dispatch table for a dispatchable instance. + * 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; /** - * Destroys the instance dispatch table for the given dispatch key. + * 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 /** @@ -78,7 +110,7 @@ class DispatchTableManager final { /** * Looks up the device dispatch table for a dispatchable object. - * Returns a pointer to the stored table on success, or nullptr if not registered. + * Returns a pointer to the stored table on success, or nullptr if object is null or unregistered. */ [[nodiscard]] VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object) const; @@ -108,15 +140,14 @@ class DispatchTableManager final { DispatchTableManager(DispatchTableManager&&) = delete; DispatchTableManager& operator=(DispatchTableManager&&) = delete; - // Note on concurrency: Dispatch table lookups perform very fast hash map lookups. - // std::mutex is intentionally preferred over std::shared_mutex because the atomic - // increment/decrement operations and cacheline bouncing of shared reader locks - // (std::shared_lock) introduce more overhead than short, uncontended mutex acquisitions. 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/test/common/test_dispatch_table_manager.cpp b/layersvt/test/common/test_dispatch_table_manager.cpp index 10f20ef44b..9781c4889c 100644 --- a/layersvt/test/common/test_dispatch_table_manager.cpp +++ b/layersvt/test/common/test_dispatch_table_manager.cpp @@ -16,6 +16,7 @@ #include "common/dispatch_table_manager.h" #include #include +#include #include #include @@ -156,3 +157,153 @@ TEST(DispatchTableManagerTest, ConcurrentAccess) { 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(); + } +} + From 982779281b086c8c7c821d0692d9ab03b832aea0 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Tue, 8 Sep 2026 18:28:42 +0100 Subject: [PATCH 03/10] layersvt: Add Android layer keep-alive self-pinning to layersvt_common Link layer_keep_alive.cpp into layersvt_common OBJECT library on Android: - Implement anonymous constructor calling dlopen(..., RTLD_NODELETE) to ensure the layer shared object stays resident in process memory across Vulkan loader queries. - Linking as part of CMake OBJECT library ensures the constructor is preserved by the static linker without requiring explicit header declarations or runtime call sites. Bug: Test: n/a Change-Id: I62267c72611916d0c28fdbec203d27b96a6a6964 --- layersvt/CMakeLists.txt | 8 +++---- layersvt/common/CMakeLists.txt | 6 +++++ layersvt/{ => common}/layer_keep_alive.cpp | 27 ++++++++-------------- 3 files changed, 19 insertions(+), 22 deletions(-) rename layersvt/{ => common}/layer_keep_alive.cpp (54%) diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 75d74cb3a3..ae8c8844ec 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -118,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() @@ -138,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 ) @@ -180,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 ) @@ -216,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/common/CMakeLists.txt b/layersvt/common/CMakeLists.txt index 371235f545..42aa0596d1 100644 --- a/layersvt/common/CMakeLists.txt +++ b/layersvt/common/CMakeLists.txt @@ -30,3 +30,9 @@ 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() 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 From 57fc032dddfff78ee97ae884a44f2296ff32b48d Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Wed, 9 Sep 2026 16:15:15 +0100 Subject: [PATCH 04/10] layersvt: Add downstream command dispatch templates and LayerBase routing Introduce DispatchDownstream template helpers in dispatch_downstream.h and establish LayerBase command intercept routing and singleton tracking: - Implement DispatchDownstream, DispatchDownstreamOr, and DispatchDownstreamOrSuccess to enable type-safe forwarding of Vulkan commands to downstream dispatch tables. - Establish LayerBase singleton lifecycle (Get()) and accessor to DispatchTableManager. - Implement GetInstanceProcAddr and GetDeviceProcAddr with GetKnownInstanceCommand and GetKnownDeviceCommand dispatch tables, supporting virtual layer hook overrides (GetLayerInstanceCommand and GetLayerDeviceCommand). Bug: Test: new tests - DispatchDownstreamTest#DispatchDownstream, LayerBaseTest#LayerTracking, LayerBaseTest#GetKnownCommandsCommonWithoutManifest, LayerBaseTest#LayerSpecificOverrideHooks, LayerBaseTest#ProcAddrDispatchChain Change-Id: I47a06c52a0a410f498c86084855369896388f87d --- layersvt/common/CMakeLists.txt | 3 + layersvt/common/dispatch_downstream.h | 106 +++++++++++++ layersvt/common/layer_base.cpp | 150 ++++++++++++++++++ layersvt/common/layer_base.h | 95 +++++++++++ layersvt/test/CMakeLists.txt | 5 + layersvt/test/common/layer_base_test_peer.h | 55 +++++++ .../test/common/test_dispatch_downstream.cpp | 117 ++++++++++++++ layersvt/test/common/test_layer_base.cpp | 149 +++++++++++++++++ layersvt/test/layer_test_helper.h | 28 ++++ 9 files changed, 708 insertions(+) create mode 100644 layersvt/common/dispatch_downstream.h create mode 100644 layersvt/common/layer_base.cpp create mode 100644 layersvt/common/layer_base.h create mode 100644 layersvt/test/common/layer_base_test_peer.h create mode 100644 layersvt/test/common/test_dispatch_downstream.cpp create mode 100644 layersvt/test/common/test_layer_base.cpp diff --git a/layersvt/common/CMakeLists.txt b/layersvt/common/CMakeLists.txt index 42aa0596d1..8321374b00 100644 --- a/layersvt/common/CMakeLists.txt +++ b/layersvt/common/CMakeLists.txt @@ -15,6 +15,9 @@ add_library(layersvt_common OBJECT dispatch_table_manager.h dispatch_table_manager.cpp + layer_base.h + layer_base.cpp + dispatch_downstream.h ) set_target_properties(layersvt_common PROPERTIES 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/layer_base.cpp b/layersvt/common/layer_base.cpp new file mode 100644 index 0000000000..f94b6e776a --- /dev/null +++ b/layersvt/common/layer_base.cpp @@ -0,0 +1,150 @@ +/* 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 +#include + +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; +} + +} // namespace + +LayerBase::LayerBase() { + 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_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); + } + 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); + } + 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..b00373ed22 --- /dev/null +++ b/layersvt/common/layer_base.h @@ -0,0 +1,95 @@ +/* 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 + +namespace layersvt { + +class LayerBaseTestPeer; + +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_; } + + protected: + // 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 and 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 and downstream dispatch. + */ + virtual PFN_vkVoidFunction GetLayerDeviceCommand(const char* command_name); + + static VkInstance GetVkInstance(VkPhysicalDevice physical_device); + + 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; + + 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 PFN_vkVoidFunction GetKnownInstanceCommand(const char* command_name); + static PFN_vkVoidFunction GetKnownDeviceCommand(const char* command_name); +}; + +} // namespace layersvt diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index 91202477b2..9b8db76ee0 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -87,7 +87,9 @@ if (TARGET VkLayer_DeviceMemoryReport) endif() add_executable(test_common_layer + common/test_dispatch_downstream.cpp common/test_dispatch_table_manager.cpp + common/test_layer_base.cpp layer_test_main.cpp ) target_link_libraries(test_common_layer PRIVATE @@ -96,6 +98,9 @@ target_link_libraries(test_common_layer PRIVATE 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") 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..ce17bb912f --- /dev/null +++ b/layersvt/test/common/layer_base_test_peer.h @@ -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. + */ + +#pragma once + +#include "common/layer_base.h" +#include +#include + +namespace layersvt { + +class LayerBaseTestPeer { + public: + static PFN_vkVoidFunction GetKnownInstanceCommand(const char* name) { + return LayerBase::GetKnownInstanceCommand(name); + } + static PFN_vkVoidFunction GetKnownDeviceCommand(const char* name) { + return LayerBase::GetKnownDeviceCommand(name); + } + + 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* name) { + return LayerBase::GetInstanceProcAddr(instance, name); + } + static PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* name) { + return LayerBase::GetDeviceProcAddr(device, name); + } +}; + +} // 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_layer_base.cpp b/layersvt/test/common/test_layer_base.cpp new file mode 100644 index 0000000000..d523104f81 --- /dev/null +++ b/layersvt/test/common/test_layer_base.cpp @@ -0,0 +1,149 @@ +/* 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; + +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_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkNonExistentInstanceFunction"), nullptr); + + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetDeviceProcAddr"), 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 command handled by base fallback + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetDeviceProcAddr"), 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); + + // 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_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); + // 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); +} 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 From e30d42d22bbff5de82eca273ce8757758a2d861c Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Wed, 9 Sep 2026 16:16:49 +0100 Subject: [PATCH 05/10] layersvt: Add instance and device lifecycle management to LayerBase Implement Vulkan loader chain traversal and Template Method lifecycle hooks for instance and device creation and teardown: - Implement GetChainInfo helper to inspect and unwrap VkLayerInstanceCreateInfo (VK_LAYER_LINK_INFO) and VkLayerDeviceCreateInfo (VK_LAYER_LINK_INFO and VK_LOADER_DATA_CALLBACK). - Add extensible pre/post virtual lifecycle hooks (PreCreateInstance, PostCreateInstance, PreDestroyInstance, PreCreateDevice, PostCreateDevice, PreDestroyDevice). - Implement CreateInstance, DestroyInstance, CreateDevice, and DestroyDevice intercepts managing dispatch table initialization, loader callback registration, and teardown ordering. Bug: Test: new tests - LayerBaseTest#HookInvocations, LayerBaseTest#CreateInstanceWithMockChain, LayerBaseTest#CreateInstanceNullHandling, LayerBaseTest#PreCreateNotInvokedOnMissingChain, LayerBaseTest#CreateInstanceNullSafetyInHook, LayerBaseTest#PreCreateInstanceMutation, LayerBaseTest#PreCreateDeviceMutation, LayerBaseTest#TeardownOrdering, LayerBaseTest#CreateDeviceWithMockChain, LayerBaseTest#CreateDeviceNullHandling, LayerBaseTest#CreateDeviceInvalidInputSafety, LayerBaseTest#CreateInstanceNullFpCreateInstance, LayerBaseTest#CreateDeviceNullFpCreateDevice, LayerBaseTest#DefaultHooksExecution, LayerBaseTest#DestroyNullHandles Change-Id: Iefeb5682a4c19b9da9073e17145d0f506a6a6964 --- layersvt/common/layer_base.cpp | 178 ++++++++ layersvt/common/layer_base.h | 58 ++- layersvt/test/common/layer_base_test_peer.h | 17 + layersvt/test/common/test_layer_base.cpp | 460 +++++++++++++++++++- 4 files changed, 710 insertions(+), 3 deletions(-) diff --git a/layersvt/common/layer_base.cpp b/layersvt/common/layer_base.cpp index f94b6e776a..caf57994b1 100644 --- a/layersvt/common/layer_base.cpp +++ b/layersvt/common/layer_base.cpp @@ -20,6 +20,20 @@ #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 { @@ -36,9 +50,41 @@ bool IsGlobalCommand(const char* command_name) { 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); +} + +#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; } @@ -68,6 +114,126 @@ VkuDeviceDispatchTable* LayerBase::GetDeviceDispatchTable(const void* object) { 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::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); +} + +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; } @@ -84,6 +250,15 @@ PFN_vkVoidFunction LayerBase::GetKnownInstanceCommand(const char* command_name) 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, "vkCreateDevice") == 0) { + return reinterpret_cast(CreateDevice); + } return nullptr; } @@ -99,6 +274,9 @@ PFN_vkVoidFunction LayerBase::GetKnownDeviceCommand(const char* command_name) { if (std::strcmp(command_name, "vkGetDeviceProcAddr") == 0) { return reinterpret_cast(GetDeviceProcAddr); } + if (std::strcmp(command_name, "vkDestroyDevice") == 0) { + return reinterpret_cast(DestroyDevice); + } return nullptr; } diff --git a/layersvt/common/layer_base.h b/layersvt/common/layer_base.h index b00373ed22..ffdb14dc56 100644 --- a/layersvt/common/layer_base.h +++ b/layersvt/common/layer_base.h @@ -47,7 +47,7 @@ class LayerBase { * Override to intercept instance-level Vulkan commands. * * Returns a function pointer to the hook implementation, or nullptr to fall back - * to core Vulkan intercepts and downstream dispatch. + * to core Vulkan intercepts (e.g. vkCreateInstance, vkDestroyInstance) or downstream dispatch. */ virtual PFN_vkVoidFunction GetLayerInstanceCommand(const char* command_name); @@ -55,11 +55,57 @@ class LayerBase { * Override to intercept device-level Vulkan commands. * * Returns a function pointer to the hook implementation, or nullptr to fall back - * to core Vulkan intercepts and downstream dispatch. + * 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); + static VkInstance GetVkInstance(VkPhysicalDevice physical_device); + /** + * Retrieves the loader data callback for initializing dispatchable handles created by layers. + * Returns the registered PFN_vkSetDeviceLoaderData on success, or nullptr if unset. + */ + [[nodiscard]] static PFN_vkSetDeviceLoaderData GetDeviceLoaderDataCallback(VkDevice device); private: [[nodiscard]] DispatchTableManager& GetDispatchTableManager() noexcept { return dispatch_table_manager_; } @@ -88,6 +134,14 @@ class LayerBase { 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 CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device); + static void VKAPI_CALL DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); + static PFN_vkVoidFunction GetKnownInstanceCommand(const char* command_name); static PFN_vkVoidFunction GetKnownDeviceCommand(const char* command_name); }; diff --git a/layersvt/test/common/layer_base_test_peer.h b/layersvt/test/common/layer_base_test_peer.h index ce17bb912f..83dbd1daa2 100644 --- a/layersvt/test/common/layer_base_test_peer.h +++ b/layersvt/test/common/layer_base_test_peer.h @@ -30,6 +30,14 @@ class LayerBaseTestPeer { return LayerBase::GetKnownDeviceCommand(name); } + 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 DispatchTableManager& GetDispatchTableManager(LayerBase& layer) { return layer.GetDispatchTableManager(); } static const DispatchTableManager& GetDispatchTableManager(const LayerBase& layer) { return layer.GetDispatchTableManager(); } @@ -50,6 +58,15 @@ class LayerBaseTestPeer { static PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* name) { return LayerBase::GetDeviceProcAddr(device, 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_layer_base.cpp b/layersvt/test/common/test_layer_base.cpp index d523104f81..c0c6f671b6 100644 --- a/layersvt/test/common/test_layer_base.cpp +++ b/layersvt/test/common/test_layer_base.cpp @@ -22,6 +22,425 @@ 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); +} + +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, 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); { @@ -40,9 +459,13 @@ TEST(LayerBaseTest, LayerTracking) { 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("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); } @@ -77,9 +500,13 @@ TEST(LayerBaseTest, LayerSpecificOverrideHooks) { EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkCustomInstanceCmd"), TestDerivedLayer::mock_custom_instance_function); EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkCustomDeviceCmd"), TestDerivedLayer::mock_custom_device_function); - // Common command handled by base fallback + // 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); @@ -92,6 +519,7 @@ TEST(LayerBaseTest, ProcAddrDispatchChain) { // 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); @@ -123,6 +551,8 @@ TEST(LayerBaseTest, ProcAddrDispatchChain) { 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); @@ -141,9 +571,37 @@ TEST(LayerBaseTest, ProcAddrDispatchChain) { 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); +} From b4701eff07a20975444a91130846f8958f72ace6 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Wed, 9 Sep 2026 16:21:56 +0100 Subject: [PATCH 06/10] layersvt: Add physical device tracking and group enumeration to LayerBase Integrate physical device enumeration and device group mapping into LayerBase: - Implement EnumeratePhysicalDevices and EnumeratePhysicalDeviceGroups (with Vulkan 1.0 VK_KHR_device_group fallback) intercepts to query downstream and populate DispatchTableManager mappings. - Register enumeration commands in GetKnownInstanceCommand dispatch table. Bug: Test: new tests - LayerBaseTest#EnumeratePhysicalDevicesMapping, LayerBaseTest#EnumeratePhysicalDeviceGroupsMapping, LayerBaseTest#EnumeratePhysicalDevicesNullTable, LayerBaseTest#PhysicalDeviceResolvesInstanceTable, LayerBaseTest#EnumeratePhysicalDeviceGroupsKHRResolution Change-Id: I4e94b8e21051b7f08b3e8c2536ca20956a6a6964 --- layersvt/common/layer_base.cpp | 41 +++++ layersvt/common/layer_base.h | 14 +- layersvt/test/common/layer_base_test_peer.h | 9 + layersvt/test/common/test_layer_base.cpp | 179 ++++++++++++++++++++ 4 files changed, 241 insertions(+), 2 deletions(-) diff --git a/layersvt/common/layer_base.cpp b/layersvt/common/layer_base.cpp index caf57994b1..ff47d1b092 100644 --- a/layersvt/common/layer_base.cpp +++ b/layersvt/common/layer_base.cpp @@ -164,6 +164,40 @@ void LayerBase::DestroyInstance(VkInstance instance, const VkAllocationCallbacks 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) { @@ -256,6 +290,13 @@ PFN_vkVoidFunction LayerBase::GetKnownInstanceCommand(const char* command_name) 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); } diff --git a/layersvt/common/layer_base.h b/layersvt/common/layer_base.h index ffdb14dc56..a5f2ff8fcb 100644 --- a/layersvt/common/layer_base.h +++ b/layersvt/common/layer_base.h @@ -47,7 +47,8 @@ class LayerBase { * 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) or downstream dispatch. + * to core Vulkan intercepts (e.g. vkCreateInstance, vkDestroyInstance, + * vkEnumeratePhysicalDevices) or downstream dispatch. */ virtual PFN_vkVoidFunction GetLayerInstanceCommand(const char* command_name); @@ -100,7 +101,12 @@ class LayerBase { */ virtual void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); - static VkInstance GetVkInstance(VkPhysicalDevice physical_device); + /** + * Retrieves the parent VkInstance associated with a physical device. + * Returns the parent VkInstance on success, or VK_NULL_HANDLE if unregistered. + */ + [[nodiscard]] static VkInstance GetVkInstance(VkPhysicalDevice physical_device); + /** * Retrieves the loader data callback for initializing dispatchable handles created by layers. * Returns the registered PFN_vkSetDeviceLoaderData on success, or nullptr if unset. @@ -138,6 +144,10 @@ class LayerBase { 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); diff --git a/layersvt/test/common/layer_base_test_peer.h b/layersvt/test/common/layer_base_test_peer.h index 83dbd1daa2..594f9d8516 100644 --- a/layersvt/test/common/layer_base_test_peer.h +++ b/layersvt/test/common/layer_base_test_peer.h @@ -30,6 +30,15 @@ class LayerBaseTestPeer { return LayerBase::GetKnownDeviceCommand(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); diff --git a/layersvt/test/common/test_layer_base.cpp b/layersvt/test/common/test_layer_base.cpp index c0c6f671b6..ba0e154415 100644 --- a/layersvt/test/common/test_layer_base.cpp +++ b/layersvt/test/common/test_layer_base.cpp @@ -376,6 +376,39 @@ TEST(LayerBaseTest, CreateDeviceInvalidInputSafety) { 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; @@ -402,6 +435,97 @@ TEST(LayerBaseTest, DefaultHooksExecution) { 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; @@ -441,6 +565,7 @@ TEST(LayerBaseTest, CreateDeviceNullFpCreateDevice) { EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), VK_ERROR_INITIALIZATION_FAILED); } + TEST(LayerBaseTest, LayerTracking) { EXPECT_EQ(LayerBase::Get(), nullptr); { @@ -461,6 +586,8 @@ TEST(LayerBaseTest, GetKnownCommandsCommon) { 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); @@ -605,3 +732,55 @@ TEST(LayerBaseTest, DestroyNullHandles) { 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); +} From 625d80eff178946ba62bcf873c3a8b3c38f160a1 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Tue, 8 Sep 2026 18:37:03 +0100 Subject: [PATCH 07/10] layersvt: Add LayerManifest and extension enumeration to LayerBase Introduce LayerManifest aggregate struct and integrate layer/extension property enumeration into LayerBase. Key capabilities: - Static layer and extension property definitions via LayerManifest - Downstream extension query and merging in LayerBase - Virtual extension and tooling filtering/augmentation hooks - Tool properties querying with Vulkan 1.3+ / VK_EXT_tooling_info support Bug: Test: new tests - LayerManifestTest, LayerBaseEnumerationTest, LayerBaseHooksTest, LayerBaseTest Change-Id: I96c027dad43270617323b2a61fe206196a6a6964 --- layersvt/common/CMakeLists.txt | 2 + layersvt/common/layer_base.cpp | 236 +++++++- layersvt/common/layer_base.h | 55 ++ layersvt/common/layer_manifest.cpp | 34 ++ layersvt/common/layer_manifest.h | 45 ++ layersvt/test/CMakeLists.txt | 1 + layersvt/test/common/layer_base_test_peer.h | 51 +- layersvt/test/common/test_layer_manifest.cpp | 562 +++++++++++++++++++ 8 files changed, 977 insertions(+), 9 deletions(-) create mode 100644 layersvt/common/layer_manifest.cpp create mode 100644 layersvt/common/layer_manifest.h create mode 100644 layersvt/test/common/test_layer_manifest.cpp diff --git a/layersvt/common/CMakeLists.txt b/layersvt/common/CMakeLists.txt index 8321374b00..db90422a20 100644 --- a/layersvt/common/CMakeLists.txt +++ b/layersvt/common/CMakeLists.txt @@ -15,6 +15,8 @@ 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 diff --git a/layersvt/common/layer_base.cpp b/layersvt/common/layer_base.cpp index ff47d1b092..bd14c2793f 100644 --- a/layersvt/common/layer_base.cpp +++ b/layersvt/common/layer_base.cpp @@ -16,7 +16,9 @@ #include "layer_base.h" #include "dispatch_downstream.h" #include "dispatch_table_manager.h" +#include "layer_manifest.h" +#include #include #include @@ -66,6 +68,25 @@ VkLayerDeviceCreateInfo* GetChainInfo(const VkDeviceCreateInfo& create_info, VkL 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) @@ -78,7 +99,6 @@ void InitPlatformErrorHandling() { SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); } #endif - } // namespace LayerBase::LayerBase() { @@ -260,6 +280,202 @@ void LayerBase::DestroyDevice(VkDevice device, const VkAllocationCallbacks* allo 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*) {} @@ -300,6 +516,24 @@ PFN_vkVoidFunction LayerBase::GetKnownInstanceCommand(const char* command_name) 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; } diff --git a/layersvt/common/layer_base.h b/layersvt/common/layer_base.h index a5f2ff8fcb..a075f64486 100644 --- a/layersvt/common/layer_base.h +++ b/layersvt/common/layer_base.h @@ -17,10 +17,12 @@ #include "dispatch_table_manager.h" #include +#include #include namespace layersvt { +struct LayerManifest; class LayerBaseTestPeer; class LayerBase { @@ -41,6 +43,43 @@ class LayerBase { [[nodiscard]] static LayerBase* Get() noexcept { return layer_; } protected: + // Layer extension interface + + // Layer manifest + + /** + * 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. + * Returns true if tool properties are intercepted, or false otherwise. + */ + [[nodiscard]] virtual bool HasToolProperties() const; + // Layer-specific command intercepts /** @@ -152,6 +191,22 @@ class LayerBase { 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); }; 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 9b8db76ee0..6edaa5f25d 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -90,6 +90,7 @@ add_executable(test_common_layer common/test_dispatch_downstream.cpp common/test_dispatch_table_manager.cpp common/test_layer_base.cpp + common/test_layer_manifest.cpp layer_test_main.cpp ) target_link_libraries(test_common_layer PRIVATE diff --git a/layersvt/test/common/layer_base_test_peer.h b/layersvt/test/common/layer_base_test_peer.h index 594f9d8516..ad4c8dc5b3 100644 --- a/layersvt/test/common/layer_base_test_peer.h +++ b/layersvt/test/common/layer_base_test_peer.h @@ -23,11 +23,11 @@ namespace layersvt { class LayerBaseTestPeer { public: - static PFN_vkVoidFunction GetKnownInstanceCommand(const char* name) { - return LayerBase::GetKnownInstanceCommand(name); + static PFN_vkVoidFunction GetKnownInstanceCommand(const char* command_name) { + return LayerBase::GetKnownInstanceCommand(command_name); } - static PFN_vkVoidFunction GetKnownDeviceCommand(const char* name) { - return LayerBase::GetKnownDeviceCommand(name); + static PFN_vkVoidFunction GetKnownDeviceCommand(const char* command_name) { + return LayerBase::GetKnownDeviceCommand(command_name); } static VkResult EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, @@ -47,6 +47,41 @@ class LayerBaseTestPeer { 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(); } @@ -61,11 +96,11 @@ class LayerBaseTestPeer { static VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object) { return LayerBase::GetDeviceDispatchTable(object); } - static PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* name) { - return LayerBase::GetInstanceProcAddr(instance, name); + static PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* command_name) { + return LayerBase::GetInstanceProcAddr(instance, command_name); } - static PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* name) { - return LayerBase::GetDeviceProcAddr(device, 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, 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); +} + From 08303cc47d32ee521f7ed635c77e21fa4852d6cf Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Tue, 8 Sep 2026 18:37:20 +0100 Subject: [PATCH 08/10] layersvt: Add exported C Vulkan layer entry points to layersvt_common Implement layersvt_entrypoints OBJECT library for shared layer export: - Export standard C ABI entry points (vkGetInstanceProcAddr, vkGetDeviceProcAddr, and enumeration functions) delegating directly to LayerBase static methods. - Package as an OBJECT library target in CMake so Vulkan layer shared libraries can include $ without duplicate symbol conflicts in test executables. Bug: Test: new tests - LayerEntrypointsTest#ForwardingCalls Change-Id: I0081acc9edf66f54efafe68639637ac66a6a6964 --- layersvt/common/CMakeLists.txt | 12 +++ layersvt/common/layer_base.h | 13 +++ layersvt/common/layer_entrypoints.cpp | 89 +++++++++++++++++++ layersvt/test/CMakeLists.txt | 2 + .../test/common/test_layer_entrypoints.cpp | 80 +++++++++++++++++ 5 files changed, 196 insertions(+) create mode 100644 layersvt/common/layer_entrypoints.cpp create mode 100644 layersvt/test/common/test_layer_entrypoints.cpp diff --git a/layersvt/common/CMakeLists.txt b/layersvt/common/CMakeLists.txt index db90422a20..64f74a84ed 100644 --- a/layersvt/common/CMakeLists.txt +++ b/layersvt/common/CMakeLists.txt @@ -41,3 +41,15 @@ if (ANDROID) ) 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/layer_base.h b/layersvt/common/layer_base.h index a075f64486..d95d4f7f59 100644 --- a/layersvt/common/layer_base.h +++ b/layersvt/common/layer_base.h @@ -16,6 +16,7 @@ #pragma once #include "dispatch_table_manager.h" +#include #include #include #include @@ -166,6 +167,18 @@ class LayerBase { static inline LayerBase* layer_ = nullptr; + // Exported Vulkan layer entry points (implemented in layer_entrypoints.cpp) + 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); + friend class LayerBaseTestPeer; template 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/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index 6edaa5f25d..99fa92c24e 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -90,8 +90,10 @@ 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 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 From 5eb549e9566c57713d9847e9b766aa069eb4f2ea Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Wed, 9 Sep 2026 16:22:39 +0100 Subject: [PATCH 09/10] layersvt: Add usage documentation and public utilities to layer_base.h Document LayerBase architectural patterns and class contract: - Add class contract documentation to layer_base.h covering singleton lifecycle, thread-safety invariants, command routing, and lifecycle hooks, referencing common README.md for authoring tutorials. - Include vk_dispatch_table.h directly in layer_base.h. - Promote GetVkInstance and GetDeviceLoaderDataCallback to public methods so namespace-scope hook functions in derived layers can access them without boilerplate wrappers. - Document HasToolProperties contract and clean up redundant comments. Bug: Test: n/a Change-Id: I837a28e1837acb68903c02e196238b16a6a6964 --- layersvt/common/layer_base.h | 66 +++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/layersvt/common/layer_base.h b/layersvt/common/layer_base.h index d95d4f7f59..abf6b1b353 100644 --- a/layersvt/common/layer_base.h +++ b/layersvt/common/layer_base.h @@ -16,6 +16,7 @@ #pragma once #include "dispatch_table_manager.h" +#include #include #include #include @@ -26,6 +27,29 @@ 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(); @@ -43,11 +67,19 @@ class LayerBase { */ [[nodiscard]] static LayerBase* Get() noexcept { return layer_; } - protected: - // Layer extension interface + /** + * 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); - // Layer manifest + /** + * 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. @@ -77,7 +109,8 @@ class LayerBase { /** * Indicates whether this layer intercepts physical device tool properties. - * Returns true if tool properties are intercepted, or false otherwise. + * 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; @@ -141,18 +174,6 @@ class LayerBase { */ virtual void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); - /** - * Retrieves the parent VkInstance associated with a physical device. - * Returns the parent VkInstance on success, or VK_NULL_HANDLE if unregistered. - */ - [[nodiscard]] static VkInstance GetVkInstance(VkPhysicalDevice physical_device); - - /** - * Retrieves the loader data callback for initializing dispatchable handles created by layers. - * Returns the registered PFN_vkSetDeviceLoaderData on success, or nullptr if unset. - */ - [[nodiscard]] static PFN_vkSetDeviceLoaderData GetDeviceLoaderDataCallback(VkDevice device); - private: [[nodiscard]] DispatchTableManager& GetDispatchTableManager() noexcept { return dispatch_table_manager_; } [[nodiscard]] const DispatchTableManager& GetDispatchTableManager() const noexcept { return dispatch_table_manager_; } @@ -168,6 +189,18 @@ class LayerBase { 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); @@ -178,6 +211,7 @@ class LayerBase { friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateDeviceExtensionProperties)(VkPhysicalDevice physical_device, const char* layer_name, uint32_t* property_count, VkExtensionProperties* properties); +#endif friend class LayerBaseTestPeer; From 2e27ce4a3def6b792996f2a8b375c9141d670a5c Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Tue, 8 Sep 2026 19:04:34 +0100 Subject: [PATCH 10/10] layersvt: Add common layer library documentation in README.md Document layersvt_common foundation library and new layer authoring: - Add comprehensive guide in layersvt/common/README.md covering LayerBase subclassing, declarative LayerManifest configuration, downstream command interception via DispatchDownstream, CMake integration via layersvt_entrypoints, and unit testing conventions. - Update layersvt/README.md to reference layersvt_common documentation. Bug: Test: n/a Change-Id: Ic951a67b73e3321a7cbe8be4d9fa41c46a6a6964 --- layersvt/README.md | 1 + layersvt/common/README.md | 308 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 layersvt/common/README.md 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/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.