From 92cdcce78c76843f857dfbaeab7bab47b89ae468 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Tue, 8 Sep 2026 18:23:40 +0100 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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. From ba6debc8482e17e14b1c66fa9c61f43eccaa29a2 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Wed, 2 Sep 2026 16:13:19 +0100 Subject: [PATCH 11/12] layersvt: Refactor DebugMarker to inherit from LayerBase Refactor the DebugMarker layer to inherit from LayerBase, replacing handwritten Vulkan lifecycle boilerplate and dispatch bookkeeping with shared layersvt_common infrastructure: - Inherit DebugMarker from LayerBase and configure LayerManifest with VK_EXT_debug_marker and VK_EXT_debug_utils extensions. - Replace manual instance and device dispatch tables and physical device tracking with DispatchTableManager and DeviceInstanceTracker. - Remove handwritten vkCreateInstance, vkDestroyInstance, vkCreateDevice, vkDestroyDevice, and extension enumeration boilerplate from debug_marker_handwritten_dispatch.cpp. - Implement GetLayerSpecificInstanceFunction and GetLayerSpecificDeviceFunction virtual hooks to dispatch debug marker and debug utils commands via DispatchDownstream. - Delete debug_marker_handwritten_functions.h header. - Add unit tests in test_debugmarker.cpp covering manifest queries, LayerBase lifecycle, and dispatch fallback. Bug: Test: new tests - DebugMarkerTests Change-Id: I5ad00681e85f15d0779ad5dcea806b8e6a6a6964 --- layersvt/CMakeLists.txt | 8 +- layersvt/debug_marker/VkLayer_DebugMarker.def | 2 + layersvt/debug_marker/debug_marker.cpp | 79 ++++-- layersvt/debug_marker/debug_marker.h | 49 ++-- .../debug_marker_handwritten_dispatch.cpp | 109 -------- .../debug_marker_handwritten_functions.h | 250 ------------------ ...andwritten_functions_vk_ext_debug_marker.h | 28 +- ...handwritten_functions_vk_ext_debug_utils.h | 53 ++-- layersvt/test/CMakeLists.txt | 4 + layersvt/test/layer_test_helper.h | 2 +- layersvt/test/test_debugmarker.cpp | 156 ++++++++++- 11 files changed, 268 insertions(+), 472 deletions(-) delete mode 100644 layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp delete mode 100644 layersvt/debug_marker/debug_marker_handwritten_functions.h diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index ae8c8844ec..06d2f09a89 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -169,8 +169,7 @@ if(BUILD_DEBUGMARKER) add_library(VkLayer_DebugMarker MODULE) set_target_properties(VkLayer_DebugMarker PROPERTIES FOLDER "layers/debugmarker") target_sources(VkLayer_DebugMarker PRIVATE - debug_marker/debug_marker_handwritten_dispatch.cpp - debug_marker/debug_marker_handwritten_functions.h + $ debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h debug_marker/debug_marker.h @@ -178,9 +177,6 @@ if(BUILD_DEBUGMARKER) debug_marker/debug_marker_perfetto.h debug_marker/debug_marker_perfetto.cpp perfetto/perfetto.cc - vk_layer_table.cpp - vk_layer_table.h - common/layer_keep_alive.cpp debug_marker/VkLayer_DebugMarker.json.in ) @@ -190,6 +186,8 @@ if(BUILD_DEBUGMARKER) ${CMAKE_CURRENT_BINARY_DIR} ) + target_link_libraries(VkLayer_DebugMarker PRIVATE layersvt_common) + if(CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|DragonFly|GNU") if (BUILD_WSI_XCB_SUPPORT) target_compile_definitions(VkLayer_DebugMarker PRIVATE VK_USE_PLATFORM_XLIB_KHR) diff --git a/layersvt/debug_marker/VkLayer_DebugMarker.def b/layersvt/debug_marker/VkLayer_DebugMarker.def index a00a872d02..8f69252213 100644 --- a/layersvt/debug_marker/VkLayer_DebugMarker.def +++ b/layersvt/debug_marker/VkLayer_DebugMarker.def @@ -18,3 +18,5 @@ vkGetInstanceProcAddr vkGetDeviceProcAddr vkEnumerateInstanceLayerProperties vkEnumerateInstanceExtensionProperties +vkEnumerateDeviceLayerProperties +vkEnumerateDeviceExtensionProperties diff --git a/layersvt/debug_marker/debug_marker.cpp b/layersvt/debug_marker/debug_marker.cpp index 6f21ff4795..e57f7d024b 100644 --- a/layersvt/debug_marker/debug_marker.cpp +++ b/layersvt/debug_marker/debug_marker.cpp @@ -15,23 +15,42 @@ #include "debug_marker.h" #include "debug_marker_perfetto.h" +#include "debug_marker_handwritten_functions_vk_ext_debug_marker.h" +#include "debug_marker_handwritten_functions_vk_ext_debug_utils.h" +#include "common/device_instance_tracker.h" #include "perfetto/perfetto.h" +#include -DebugMarker& DebugMarker::Get() { - static DebugMarker instance; - return instance; -} +namespace { +DebugMarker g_layer; +} // namespace -void DebugMarker::SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance) { - std::lock_guard lock(mutex_); - vk_instance_map_[phys_dev] = instance; +DebugMarker::DebugMarker() = default; + +const layersvt::LayerManifest* DebugMarker::GetLayerManifest() const { + static const layersvt::LayerManifest manifest(layersvt::LayerManifest::Config{ + .layer_name = "VK_LAYER_GOOGLE_DebugMarker", + .description = "layer: DebugMarker", + .spec_version = VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), + .implementation_version = VK_MAKE_VERSION(0, 1, 0), + .instance_extensions = + { + {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, + }, + .device_extensions = + { + {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, + }, + .tool_properties = std::nullopt, + }); + return &manifest; } -VkInstance DebugMarker::GetVkInstance(VkPhysicalDevice phys_dev) { - std::lock_guard lock(mutex_); - auto it = vk_instance_map_.find(phys_dev); - if (it != vk_instance_map_.end()) return it->second; - return VK_NULL_HANDLE; +void DebugMarker::PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) { + (void)pCreateInfo; + (void)pAllocator; + static std::once_flag perfetto_initialization_flag; + std::call_once(perfetto_initialization_flag, []() { InitializeDebugMarkerPerfetto(); }); } void DebugMarker::SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name) { @@ -72,11 +91,6 @@ void DebugMarker::EmitAllDebugMarkers() { } } -void DebugMarker::Clear() { - std::lock_guard lock(mutex_); - vk_instance_map_.clear(); - debug_object_names_.clear(); -} bool DebugMarker::HasDebugObjectName(int32_t type, uint64_t handle, const std::string& name) { std::lock_guard lock(mutex_); @@ -84,3 +98,34 @@ bool DebugMarker::HasDebugObjectName(int32_t type, uint64_t handle, const std::s if (it == debug_object_names_.end()) return false; return it->second.name == name; } + +PFN_vkVoidFunction DebugMarker::GetLayerInstanceCommand(const char* name) { + if (!name) return nullptr; + if (strcmp(name, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); + if (strcmp(name, "vkDestroyDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkDestroyDebugUtilsMessengerEXT); + if (strcmp(name, "vkSubmitDebugUtilsMessageEXT") == 0) return reinterpret_cast(vkSubmitDebugUtilsMessageEXT); + return nullptr; +} + +PFN_vkVoidFunction DebugMarker::GetLayerDeviceCommand(const char* name) { + if (!name) return nullptr; + + // VK_EXT_debug_marker + if (strcmp(name, "vkCmdDebugMarkerBeginEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerBeginEXT); + if (strcmp(name, "vkCmdDebugMarkerEndEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerEndEXT); + if (strcmp(name, "vkCmdDebugMarkerInsertEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerInsertEXT); + if (strcmp(name, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectNameEXT); + if (strcmp(name, "vkDebugMarkerSetObjectTagEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectTagEXT); + + // VK_EXT_debug_utils + if (strcmp(name, "vkCmdBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdBeginDebugUtilsLabelEXT); + if (strcmp(name, "vkCmdEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdEndDebugUtilsLabelEXT); + if (strcmp(name, "vkCmdInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdInsertDebugUtilsLabelEXT); + if (strcmp(name, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectNameEXT); + if (strcmp(name, "vkSetDebugUtilsObjectTagEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectTagEXT); + if (strcmp(name, "vkQueueBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueBeginDebugUtilsLabelEXT); + if (strcmp(name, "vkQueueEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueEndDebugUtilsLabelEXT); + if (strcmp(name, "vkQueueInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueInsertDebugUtilsLabelEXT); + + return nullptr; +} diff --git a/layersvt/debug_marker/debug_marker.h b/layersvt/debug_marker/debug_marker.h index b705cefbfb..aeaacd4ef7 100644 --- a/layersvt/debug_marker/debug_marker.h +++ b/layersvt/debug_marker/debug_marker.h @@ -17,11 +17,14 @@ #include #include -#include #include #include +#include "common/dispatch_downstream.h" +#include "common/layer_base.h" +#include "common/layer_manifest.h" + /** * The DebugMarker class is responsible for storing and managing debug marker * information associated with Vulkan objects and emitting them to Perfetto traces. @@ -44,20 +47,21 @@ * we write all currently known object names to the trace. We retain the names in memory * because a user might start another Perfetto session later, requiring us to emit * all object names again. - * * A potential issue exists if an application constantly creates and destroys * objects without bound, as we currently do not remove names for destroyed objects. * Support for removing names on object destruction can be added later if needed. * - * This class is a singleton and provides thread-safe access to its state. + * This class is a singleton, inherits from LayerBase, and provides thread-safe access to its state. */ -class DebugMarker { +class DebugMarker : public layersvt::LayerBase { public: - /** - * @brief Returns the singleton instance of the DebugMarker class. - * @return Reference to the DebugMarker singleton. - */ - static DebugMarker& Get(); + DebugMarker(); + ~DebugMarker() override = default; + + static DebugMarker& Get() { + assert(LayerBase::Get() != nullptr && "LayerBase instance must be initialized"); + return *static_cast(LayerBase::Get()); + } /** * @brief Sets or updates the name associated with a Vulkan object. @@ -72,12 +76,6 @@ class DebugMarker { * @brief Emits all stored debug markers to the tracing system. */ void EmitAllDebugMarkers(); - - /** - * @brief Clears all stored debug markers and instance mappings. - * @note This function is for testing only. - */ - void Clear(); /** * @brief Checks if a debug name is stored for a given object. @@ -85,20 +83,15 @@ class DebugMarker { */ bool HasDebugObjectName(int32_t type, uint64_t handle, const std::string& name); + protected: /** - * @brief Associates a Vulkan physical device with its corresponding instance. - * @param phys_dev The Vulkan physical device. - * @param instance The Vulkan instance. + * Lifecycle hook called before vkCreateInstance. */ - void SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance); - - /** - * @brief Retrieves the Vulkan instance associated with a given physical device. - * @param phys_dev The Vulkan physical device. - * @return The associated Vulkan instance. - */ - VkInstance GetVkInstance(VkPhysicalDevice phys_dev); + void PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) override; + const layersvt::LayerManifest* GetLayerManifest() const override; + PFN_vkVoidFunction GetLayerInstanceCommand(const char* name) override; + PFN_vkVoidFunction GetLayerDeviceCommand(const char* name) override; private: struct DebugObjectName { @@ -113,10 +106,6 @@ class DebugMarker { }; std::mutex mutex_; - /** - * @brief Maps a physical device handle to its corresponding Vulkan instance handle. - */ - std::unordered_map vk_instance_map_; /** * @brief Maps a pair of (object_type, object_handle) to its debug name information. * We use a pair as the key because handles are not guaranteed to be unique across different object types. diff --git a/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp b/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp deleted file mode 100644 index a320032500..0000000000 --- a/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* Copyright (C) 2026 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "debug_marker_handwritten_functions.h" -#include "debug_marker_handwritten_functions_vk_ext_debug_marker.h" -#include "debug_marker_handwritten_functions_vk_ext_debug_utils.h" -#include "vk_layer_table.h" -#include - -extern "C" { - -static PFN_vkVoidFunction debug_marker_known_instance_functions(const char* pName) { - if (strcmp(pName, "vkGetInstanceProcAddr") == 0) return reinterpret_cast(vkGetInstanceProcAddr); - if (strcmp(pName, "vkCreateInstance") == 0) return reinterpret_cast(vkCreateInstance); - if (strcmp(pName, "vkDestroyInstance") == 0) return reinterpret_cast(vkDestroyInstance); - if (strcmp(pName, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast(vkEnumeratePhysicalDevices); - if (strcmp(pName, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast(vkEnumeratePhysicalDeviceGroups); - if (strcmp(pName, "vkEnumerateInstanceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateInstanceExtensionProperties); - if (strcmp(pName, "vkEnumerateInstanceLayerProperties") == 0) return reinterpret_cast(vkEnumerateInstanceLayerProperties); - if (strcmp(pName, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); - if (strcmp(pName, "vkDestroyDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkDestroyDebugUtilsMessengerEXT); - if (strcmp(pName, "vkSubmitDebugUtilsMessageEXT") == 0) return reinterpret_cast(vkSubmitDebugUtilsMessageEXT); - return nullptr; -} - -static PFN_vkVoidFunction debug_marker_known_device_functions(const char* pName) { - if (strcmp(pName, "vkGetDeviceProcAddr") == 0) return reinterpret_cast(vkGetDeviceProcAddr); - if (strcmp(pName, "vkCreateDevice") == 0) return reinterpret_cast(vkCreateDevice); - if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast(vkEnumerateDeviceLayerProperties); - if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); - - // VK_EXT_debug_marker - if (strcmp(pName, "vkCmdDebugMarkerBeginEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerBeginEXT); - if (strcmp(pName, "vkCmdDebugMarkerEndEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerEndEXT); - if (strcmp(pName, "vkCmdDebugMarkerInsertEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerInsertEXT); - if (strcmp(pName, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectNameEXT); - if (strcmp(pName, "vkDebugMarkerSetObjectTagEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectTagEXT); - - // VK_EXT_debug_utils - if (strcmp(pName, "vkCmdBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdBeginDebugUtilsLabelEXT); - if (strcmp(pName, "vkCmdEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdEndDebugUtilsLabelEXT); - if (strcmp(pName, "vkCmdInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdInsertDebugUtilsLabelEXT); - if (strcmp(pName, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectNameEXT); - if (strcmp(pName, "vkSetDebugUtilsObjectTagEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectTagEXT); - if (strcmp(pName, "vkQueueBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueBeginDebugUtilsLabelEXT); - if (strcmp(pName, "vkQueueEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueEndDebugUtilsLabelEXT); - if (strcmp(pName, "vkQueueInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueInsertDebugUtilsLabelEXT); - - return nullptr; -} - -EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* pName) { - PFN_vkVoidFunction func = debug_marker_known_instance_functions(pName); - if (func) { - return func; - } - - // If it's a device function, we can also return it here if we want to support GIPA for device functions. - func = debug_marker_known_device_functions(pName); - if (func) { - return func; - } - - if (instance == nullptr) { - return nullptr; - } - - auto table = instance_dispatch_table(instance); - if (table == NULL) { - return nullptr; - } - - if (table->GetInstanceProcAddr == NULL) { - return nullptr; - } - - return table->GetInstanceProcAddr(instance, pName); -} - -EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char* pName) { - PFN_vkVoidFunction func = debug_marker_known_device_functions(pName); - if (func) { - return func; - } - - if (device == nullptr) { - return nullptr; - } - - if (device_dispatch_table(device)->GetDeviceProcAddr == NULL) { - return nullptr; - } - - return device_dispatch_table(device)->GetDeviceProcAddr(device, pName); -} - -} // extern "C" diff --git a/layersvt/debug_marker/debug_marker_handwritten_functions.h b/layersvt/debug_marker/debug_marker_handwritten_functions.h deleted file mode 100644 index 18fe280c05..0000000000 --- a/layersvt/debug_marker/debug_marker_handwritten_functions.h +++ /dev/null @@ -1,250 +0,0 @@ -/* Copyright (C) 2026 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include "vk_layer_table.h" -#include "debug_marker.h" -#include "debug_marker_perfetto.h" - -// This file contains handwritten implementations for core Vulkan functions -// (instance/device creation and physical device enumeration) required for the layer's -// infrastructure and state management: -// -// - vkCreateInstance: Initializes Perfetto tracing, the instance dispatch table, and performs eager physical device enumeration. -// - vkEnumeratePhysicalDevices / vkEnumeratePhysicalDeviceGroups: Tracks the mapping -// between physical devices and instances to support dispatch table lookups. -// - vkCreateDevice: Initializes the device dispatch table for intercepted devices. -// -// Extension-specific functions (e.g., VK_EXT_debug_marker, VK_EXT_debug_utils) -// are located in separate dedicated header files. - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) - -#if defined(__GNUC__) && __GNUC__ >= 4 -#define EXPORT_FUNCTION __attribute__((visibility("default"))) -#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) -#define EXPORT_FUNCTION __attribute__((visibility("default"))) -#else -#define EXPORT_FUNCTION -#endif - -static std::once_flag g_perfetto_init_flag; - - -extern "C" { - -VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, - VkInstance* pInstance) { - std::call_once(g_perfetto_init_flag, []() { InitializeDebugMarkerPerfetto(); }); - - // Get the function pointer - VkLayerInstanceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); - assert(chain_info->u.pLayerInfo != 0); - PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; - assert(fpGetInstanceProcAddr != 0); - PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); - if (fpCreateInstance == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - // Call the function and create the dispatch table - chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; - VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance); - if (result == VK_SUCCESS) { - initInstanceTable(*pInstance, fpGetInstanceProcAddr); - - // Eagerly enumerate physical devices and map them to the instance. - // This ensures we have the mapping even if the app bypasses our enumeration hooks. - PFN_vkEnumeratePhysicalDevices fpEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)fpGetInstanceProcAddr(*pInstance, "vkEnumeratePhysicalDevices"); - if (fpEnumeratePhysicalDevices) { - uint32_t count = 0; - fpEnumeratePhysicalDevices(*pInstance, &count, nullptr); - if (count > 0) { - std::vector devices(count); - fpEnumeratePhysicalDevices(*pInstance, &count, devices.data()); - for (uint32_t i = 0; i < count; ++i) { - DebugMarker::Get().SetVkInstance(devices[i], *pInstance); - } - } - } - } - - return result; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) { - if (instance_dispatch_table(instance)->EnumeratePhysicalDevices == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices); - - if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pPhysicalDevices != nullptr) { - for (uint32_t i = 0; i < *pPhysicalDeviceCount; ++i) { - DebugMarker::Get().SetVkInstance(pPhysicalDevices[i], instance); - } - } - return result; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties) { - if (instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups(instance, pPhysicalDeviceGroupCount, pPhysicalDeviceGroupProperties); - - if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pPhysicalDeviceGroupProperties != nullptr) { - for (uint32_t i = 0; i < *pPhysicalDeviceGroupCount; ++i) { - for (uint32_t j = 0; j < pPhysicalDeviceGroupProperties[i].physicalDeviceCount; ++j) { - DebugMarker::Get().SetVkInstance(pPhysicalDeviceGroupProperties[i].physicalDevices[j], instance); - } - } - } - return result; -} - -VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator) { - dispatch_key key = get_dispatch_key(instance); - instance_dispatch_table(instance)->DestroyInstance(instance, pAllocator); - destroy_instance_dispatch_table(key); -} - -VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - // Get the function pointer - VkLayerDeviceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); - assert(chain_info->u.pLayerInfo != 0); - PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; - PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; - VkInstance vk_instance = DebugMarker::Get().GetVkInstance(physicalDevice); - PFN_vkCreateDevice fpCreateDevice = (PFN_vkCreateDevice)fpGetInstanceProcAddr(vk_instance, "vkCreateDevice"); - if (fpCreateDevice == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - // Call the function and create the dispatch table - chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; - VkResult result = fpCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); - if (result == VK_SUCCESS) { - initDeviceTable(*pDevice, fpGetDeviceProcAddr); - } - - return result; -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - static const VkExtensionProperties instanceExtensions[] = { - {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, - }; - - if (pLayerName != nullptr && strcmp(pLayerName, "VK_LAYER_GOOGLE_DebugMarker") == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); - } - - return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, - VkLayerProperties* pProperties) { - static const VkLayerProperties layerProperties[] = {{ - "VK_LAYER_GOOGLE_DebugMarker", - VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), // specVersion - VK_MAKE_VERSION(0, 1, 0), // implementationVersion - "layer: DebugMarker", - }}; - - return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, - uint32_t* pPropertyCount, - VkLayerProperties* pProperties) { - static const VkLayerProperties layerProperties[] = {{ - "VK_LAYER_GOOGLE_DebugMarker", - VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), - VK_MAKE_VERSION(0, 1, 0), - "layer: DebugMarker", - }}; - - return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, - const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - static const VkExtensionProperties deviceExtensions[] = { - {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, - }; - - if (pLayerName != nullptr && strcmp(pLayerName, "VK_LAYER_GOOGLE_DebugMarker") == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); - } - - VkInstance vk_instance = DebugMarker::Get().GetVkInstance(physicalDevice); - - // Manually append device extension. This should not be necessary, but the Android vulkan - // loader does not expose extensions from implicit layer (b/143293104). - if (pProperties == nullptr) { - VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - (*pPropertyCount) += ARRAY_SIZE(deviceExtensions); - } - return res; - } - - if (*pPropertyCount > 0) { - uint32_t requestedCount = *pPropertyCount; - VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - uint32_t originalCount = *pPropertyCount; - uint32_t additionalCount = 0; - - for (uint32_t i = 0; i < ARRAY_SIZE(deviceExtensions); ++i) { - bool found = false; - for (uint32_t j = 0; j < originalCount; ++j) { - if (strcmp(pProperties[j].extensionName, deviceExtensions[i].extensionName) == 0) { - found = true; - break; - } - } - if (!found) { - if (originalCount + additionalCount < requestedCount) { - pProperties[originalCount + additionalCount] = deviceExtensions[i]; - } - additionalCount++; - } - } - *pPropertyCount = originalCount + additionalCount; - if (*pPropertyCount > requestedCount) { - *pPropertyCount = requestedCount; - } - } - return res; - } - return VK_SUCCESS; -} - - -} // extern "C" diff --git a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h index 84db032b13..937ebff9e7 100644 --- a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h +++ b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h @@ -16,7 +16,7 @@ #pragma once #include -#include "vk_layer_table.h" +#include "common/dispatch_table_manager.h" #include "debug_marker.h" // This file contains handwritten functions for the VK_EXT_debug_marker extension. @@ -77,41 +77,31 @@ extern "C" { // Required for VK_EXT_debug_marker VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerBeginEXT(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo) { - if (device_dispatch_table(commandBuffer)->CmdDebugMarkerBeginEXT) { - device_dispatch_table(commandBuffer)->CmdDebugMarkerBeginEXT(commandBuffer, pMarkerInfo); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::CmdDebugMarkerBeginEXT>(commandBuffer, pMarkerInfo); } // Required for VK_EXT_debug_marker VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerEndEXT(VkCommandBuffer commandBuffer) { - if (device_dispatch_table(commandBuffer)->CmdDebugMarkerEndEXT) { - device_dispatch_table(commandBuffer)->CmdDebugMarkerEndEXT(commandBuffer); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::CmdDebugMarkerEndEXT>(commandBuffer); } // Required for VK_EXT_debug_marker VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerInsertEXT(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo) { - if (device_dispatch_table(commandBuffer)->CmdDebugMarkerInsertEXT) { - device_dispatch_table(commandBuffer)->CmdDebugMarkerInsertEXT(commandBuffer, pMarkerInfo); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::CmdDebugMarkerInsertEXT>(commandBuffer, pMarkerInfo); } // Required for VK_EXT_debug_marker. Tracks object name state. VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo) { - DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)getVkObjectType(pNameInfo->objectType), pNameInfo->object, pNameInfo->pObjectName); - if (device_dispatch_table(device)->DebugMarkerSetObjectNameEXT) { - VkResult result = device_dispatch_table(device)->DebugMarkerSetObjectNameEXT(device, pNameInfo); - return result; + if (pNameInfo) { + DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)getVkObjectType(pNameInfo->objectType), pNameInfo->object, pNameInfo->pObjectName); } - return VK_SUCCESS; + return layersvt::DispatchDownstream<&VkuDeviceDispatchTable::DebugMarkerSetObjectNameEXT>(device, pNameInfo); } // Required for VK_EXT_debug_marker VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectTagEXT(VkDevice device, const VkDebugMarkerObjectTagInfoEXT* pTagInfo) { - if (device_dispatch_table(device)->DebugMarkerSetObjectTagEXT) { - return device_dispatch_table(device)->DebugMarkerSetObjectTagEXT(device, pTagInfo); - } - return VK_SUCCESS; + return layersvt::DispatchDownstream<&VkuDeviceDispatchTable::DebugMarkerSetObjectTagEXT>(device, pTagInfo); } } // extern "C" + diff --git a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h index 591a6eba82..b29b841180 100644 --- a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h +++ b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_utils.h @@ -16,7 +16,7 @@ #pragma once #include -#include "vk_layer_table.h" +#include "common/dispatch_table_manager.h" #include "debug_marker.h" extern "C" { @@ -28,84 +28,61 @@ extern "C" { // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkCmdBeginDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo) { - if (device_dispatch_table(commandBuffer)->CmdBeginDebugUtilsLabelEXT) { - device_dispatch_table(commandBuffer)->CmdBeginDebugUtilsLabelEXT(commandBuffer, pLabelInfo); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::CmdBeginDebugUtilsLabelEXT>(commandBuffer, pLabelInfo); } // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkCmdEndDebugUtilsLabelEXT(VkCommandBuffer commandBuffer) { - if (device_dispatch_table(commandBuffer)->CmdEndDebugUtilsLabelEXT) { - device_dispatch_table(commandBuffer)->CmdEndDebugUtilsLabelEXT(commandBuffer); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::CmdEndDebugUtilsLabelEXT>(commandBuffer); } // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkCmdInsertDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo) { - if (device_dispatch_table(commandBuffer)->CmdInsertDebugUtilsLabelEXT) { - device_dispatch_table(commandBuffer)->CmdInsertDebugUtilsLabelEXT(commandBuffer, pLabelInfo); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::CmdInsertDebugUtilsLabelEXT>(commandBuffer, pLabelInfo); } // Required for VK_EXT_debug_utils. Tracks object name state. VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { - DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)pNameInfo->objectType, pNameInfo->objectHandle, pNameInfo->pObjectName); - if (device_dispatch_table(device)->SetDebugUtilsObjectNameEXT) { - VkResult result = device_dispatch_table(device)->SetDebugUtilsObjectNameEXT(device, pNameInfo); - return result; + if (pNameInfo) { + DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)pNameInfo->objectType, pNameInfo->objectHandle, pNameInfo->pObjectName); } - return VK_SUCCESS; + return layersvt::DispatchDownstream<&VkuDeviceDispatchTable::SetDebugUtilsObjectNameEXT>(device, pNameInfo); } // Required for VK_EXT_debug_utils VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectTagEXT(VkDevice device, const VkDebugUtilsObjectTagInfoEXT* pTagInfo) { - if (device_dispatch_table(device)->SetDebugUtilsObjectTagEXT) { - return device_dispatch_table(device)->SetDebugUtilsObjectTagEXT(device, pTagInfo); - } - return VK_SUCCESS; + return layersvt::DispatchDownstream<&VkuDeviceDispatchTable::SetDebugUtilsObjectTagEXT>(device, pTagInfo); } // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkQueueBeginDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo) { - if (device_dispatch_table(queue)->QueueBeginDebugUtilsLabelEXT) { - device_dispatch_table(queue)->QueueBeginDebugUtilsLabelEXT(queue, pLabelInfo); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::QueueBeginDebugUtilsLabelEXT>(queue, pLabelInfo); } // Required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkQueueEndDebugUtilsLabelEXT(VkQueue queue) { - if (device_dispatch_table(queue)->QueueEndDebugUtilsLabelEXT) { - device_dispatch_table(queue)->QueueEndDebugUtilsLabelEXT(queue); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::QueueEndDebugUtilsLabelEXT>(queue); } // Passthrough required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkQueueInsertDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo) { - if (device_dispatch_table(queue)->QueueInsertDebugUtilsLabelEXT) { - device_dispatch_table(queue)->QueueInsertDebugUtilsLabelEXT(queue, pLabelInfo); - } + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::QueueInsertDebugUtilsLabelEXT>(queue, pLabelInfo); } // Passthrough required for VK_EXT_debug_utils VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger) { - if (instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT) { - return instance_dispatch_table(instance)->CreateDebugUtilsMessengerEXT(instance, pCreateInfo, pAllocator, pMessenger); - } - return VK_SUCCESS; + return layersvt::DispatchDownstream<&VkuInstanceDispatchTable::CreateDebugUtilsMessengerEXT>(instance, pCreateInfo, pAllocator, pMessenger); } // Passthrough required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator) { - if (instance_dispatch_table(instance)->DestroyDebugUtilsMessengerEXT) { - instance_dispatch_table(instance)->DestroyDebugUtilsMessengerEXT(instance, messenger, pAllocator); - } + layersvt::DispatchDownstream<&VkuInstanceDispatchTable::DestroyDebugUtilsMessengerEXT>(instance, messenger, pAllocator); } // Passthrough required for VK_EXT_debug_utils VKAPI_ATTR void VKAPI_CALL vkSubmitDebugUtilsMessageEXT(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData) { - if (instance_dispatch_table(instance)->SubmitDebugUtilsMessageEXT) { - instance_dispatch_table(instance)->SubmitDebugUtilsMessageEXT(instance, messageSeverity, messageTypes, pCallbackData); - } + layersvt::DispatchDownstream<&VkuInstanceDispatchTable::SubmitDebugUtilsMessageEXT>(instance, messageSeverity, messageTypes, pCallbackData); } + } // extern "C" diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index 99fa92c24e..f2324442c0 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -37,6 +37,10 @@ function(LayerTest NAME) if (${NAME} STREQUAL "DebugMarker") target_sources(${TEST_NAME} PRIVATE ../debug_marker/debug_marker.cpp ../debug_marker/debug_marker_perfetto.cpp ../perfetto/perfetto.cc) target_include_directories(${TEST_NAME} PRIVATE .. ../debug_marker) + target_link_libraries(${TEST_NAME} layersvt_common) + if (NOT MSVC) + set_source_files_properties(../perfetto/perfetto.cc PROPERTIES COMPILE_OPTIONS "-Wno-deprecated-declarations") + endif() elseif (${NAME} STREQUAL "DeviceMemoryReport") target_sources(${TEST_NAME} PRIVATE ../device_memory_report/device_memory_report.cpp ../device_memory_report/device_memory_report_perfetto.cpp ../perfetto/perfetto.cc) target_include_directories(${TEST_NAME} PRIVATE .. ../device_memory_report) diff --git a/layersvt/test/layer_test_helper.h b/layersvt/test/layer_test_helper.h index 8baa275e19..20a395809b 100644 --- a/layersvt/test/layer_test_helper.h +++ b/layersvt/test/layer_test_helper.h @@ -20,6 +20,7 @@ #include +#include #include #include #include @@ -102,5 +103,4 @@ inline void ResetLayer(bool destroy = false) { detail::GetActiveLayerDeleter() = []() { test_instance.reset(); }; } } - } // namespace layer_test diff --git a/layersvt/test/test_debugmarker.cpp b/layersvt/test/test_debugmarker.cpp index e1f2325d2f..07ef4f636b 100644 --- a/layersvt/test/test_debugmarker.cpp +++ b/layersvt/test/test_debugmarker.cpp @@ -19,6 +19,42 @@ #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 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); + } + 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) { + return LayerBase::EnumerateDeviceExtensionProperties(physical_device, layer_name, property_count, properties); + } + static DeviceInstanceTracker& GetDeviceTracker(LayerBase& layer) { + return layer.GetDeviceTracker(); + } +}; +} // namespace layersvt + static const char* kLayerName = "VK_LAYER_GOOGLE_DebugMarker"; class DebugMarkerTests : public VkTestFramework { @@ -27,12 +63,17 @@ class DebugMarkerTests : public VkTestFramework { static void SetUpTestSuite() {} static void TearDownTestSuite(){}; + + protected: + void SetUp() override { + VkTestFramework::SetUp(); + layer_test::ResetLayer(); + } }; TEST_F(DebugMarkerTests, CombinedTest) { TEST_DESCRIPTION("Combined test for DebugMarker layer"); - DebugMarker::Get().Clear(); layer_test::VulkanInstanceBuilder inst_builder; inst_builder.AddExtension("VK_EXT_debug_utils"); VkResult err = inst_builder.Init(kLayerName); @@ -58,7 +99,116 @@ TEST_F(DebugMarkerTests, CombinedTest) { EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed")); EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstance")); - // 3. Clear - DebugMarker::Get().Clear(); + // 3. Reset + layer_test::ResetLayer(); EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_INSTANCE, (uint64_t)instance, "MyInstanceRenamed")); } + +TEST_F(DebugMarkerTests, ManifestTest) { + TEST_DESCRIPTION("Verify DebugMarker LayerManifest properties and extension enumeration via LayerBase"); + + // Test EnumerateInstanceLayerProperties + uint32_t property_count = 0; + VkResult result = layersvt::LayerBaseTestPeer::EnumerateInstanceLayerProperties(&property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + VkLayerProperties layer_properties{}; + result = layersvt::LayerBaseTestPeer::EnumerateInstanceLayerProperties(&property_count, &layer_properties); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(layer_properties.layerName, kLayerName); + EXPECT_STREQ(layer_properties.description, "layer: DebugMarker"); + + // Test EnumerateDeviceLayerProperties + property_count = 0; + result = layersvt::LayerBaseTestPeer::EnumerateDeviceLayerProperties(VK_NULL_HANDLE, &property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + // Test EnumerateInstanceExtensionProperties + property_count = 0; + result = layersvt::LayerBaseTestPeer::EnumerateInstanceExtensionProperties(kLayerName, &property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + std::vector extensions(property_count); + result = layersvt::LayerBaseTestPeer::EnumerateInstanceExtensionProperties(kLayerName, &property_count, extensions.data()); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(extensions[0].extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + // Query non-matching layer name returns VK_ERROR_LAYER_NOT_PRESENT + property_count = 0; + result = layersvt::LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_NONEXISTENT", &property_count, nullptr); + EXPECT_EQ(result, VK_ERROR_LAYER_NOT_PRESENT); + + // Test EnumerateDeviceExtensionProperties + property_count = 0; + result = layersvt::LayerBaseTestPeer::EnumerateDeviceExtensionProperties(VK_NULL_HANDLE, kLayerName, &property_count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(property_count, 1u); + + extensions.resize(property_count); + result = layersvt::LayerBaseTestPeer::EnumerateDeviceExtensionProperties(VK_NULL_HANDLE, kLayerName, &property_count, extensions.data()); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(extensions[0].extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME); +} + +TEST_F(DebugMarkerTests, LayerBaseLifecycleAndTrackerTest) { + TEST_DESCRIPTION("Verify DebugMarker LayerBase inheritance and DeviceInstanceTracker integration"); + + // Verify tracker operates correctly via GetDeviceTracker on DebugMarker::Get() + VkPhysicalDevice mock_physical_device = reinterpret_cast(0x1234); + VkInstance mock_instance = reinterpret_cast(0x5678); + + layersvt::LayerBaseTestPeer::GetDeviceTracker(DebugMarker::Get()).SetVkInstance(mock_physical_device, mock_instance); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetDeviceTracker(DebugMarker::Get()).GetVkInstance(mock_physical_device), mock_instance); + + layer_test::ResetLayer(); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetDeviceTracker(DebugMarker::Get()).GetVkInstance(mock_physical_device), VK_NULL_HANDLE); +} + +TEST_F(DebugMarkerTests, TemplateMethodDispatchTest) { + TEST_DESCRIPTION("Verify DebugMarker layer-specific hooks and LayerBase template method dispatching"); + + // Layer-specific instance commands intercepted + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkDestroyDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkSubmitDebugUtilsMessageEXT"), nullptr); + + // Common lifecycle instance commands resolved via LayerBase fallback + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateInstance"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkDestroyInstance"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumeratePhysicalDevices"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDevice"), nullptr); + + // Layer-specific device commands intercepted + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkCmdBeginDebugUtilsLabelEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkSetDebugUtilsObjectNameEXT"), nullptr); + + // Common lifecycle device commands resolved via LayerBase fallback + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkCreateDevice"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkDestroyDevice"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetKnownDeviceCommand("vkGetDeviceProcAddr"), nullptr); + + // Global commands resolvable with VK_NULL_HANDLE via GetInstanceProcAddr + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"), nullptr); + + // Non-global commands return nullptr when passed VK_NULL_HANDLE + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkCmdBeginDebugUtilsLabelEXT"), nullptr); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkNonExistentCmd"), nullptr); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkNonExistentCmd"), nullptr); + + // Non-global commands resolvable with valid instance/device handles + VkInstance mock_instance = reinterpret_cast(0x1234); + VkDevice mock_device = reinterpret_cast(0x5678); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCreateDebugUtilsMessengerEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCmdDebugMarkerBeginEXT"), nullptr); + EXPECT_NE(layersvt::LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkCmdBeginDebugUtilsLabelEXT"), nullptr); +} From 2a5715083f243161100b35423f26a9289c3c9c94 Mon Sep 17 00:00:00 2001 From: okuznetsov Date: Wed, 9 Sep 2026 19:04:54 +0100 Subject: [PATCH 12/12] layersvt: Clean up debug objects on device destruction in DebugMarker Override PreDestroyDevice in DebugMarker to remove tracked object names associated with a destroyed VkDevice, preventing unbounded memory growth when devices are repeatedly created and destroyed. Add PreDestroyDeviceCleanupTest in test_debugmarker.cpp to verify that tracked objects for destroyed devices are cleaned up while preserving objects on remaining active devices. Bug: Test: new tests - DebugMarkerTests#PreDestroyDeviceCleanupTest Change-Id: I9a3624e7bf3fd05d6447adb46df887dd6a6a6964 --- .agents/skills/test-android-layer/SKILL.md | 211 ++++++++++++++++++ layersvt/CMakeLists.txt | 4 + layersvt/common/README.md | 2 +- layersvt/common/dispatch_downstream.h | 13 -- layersvt/common/dispatch_table_manager.cpp | 61 +++-- layersvt/common/dispatch_table_manager.h | 28 +-- layersvt/common/layer_base.cpp | 130 ++++------- layersvt/common/layer_base.h | 33 +-- layersvt/debug_marker/debug_marker.cpp | 18 +- layersvt/debug_marker/debug_marker.h | 8 +- layersvt/test/common/layer_base_test_peer.h | 34 ++- .../test/common/test_dispatch_downstream.cpp | 12 +- .../common/test_dispatch_table_manager.cpp | 15 +- layersvt/test/common/test_layer_base.cpp | 12 +- layersvt/test/common/test_layer_manifest.cpp | 85 ------- layersvt/test/test_debugmarker.cpp | 79 +++---- 16 files changed, 402 insertions(+), 343 deletions(-) create mode 100644 .agents/skills/test-android-layer/SKILL.md diff --git a/.agents/skills/test-android-layer/SKILL.md b/.agents/skills/test-android-layer/SKILL.md new file mode 100644 index 0000000000..5f3f29f04a --- /dev/null +++ b/.agents/skills/test-android-layer/SKILL.md @@ -0,0 +1,211 @@ +--- +name: test-android-layer +description: >- + Builds, deploys, and verifies Vulkan layers (DebugMarker, DeviceMemoryReport, FpsOverlay, + LimitExtensions, Screenshot, etc.) on a connected Android device. Deploys layers via app native + libraries with SELinux context (bypassing Android 14/15 restrictions), enables global GPU debug + layers, launches the target workload (e.g. Boss Room Unity sample or Sherlock), captures a + Perfetto trace (GPU render stages + VulkanDebugMarker), and validates debug names and crash-free + execution via TraceProcessor SQL. Use when asked to test, validate, or profile a Vulkan layer on + Android, verify debug names/markers, or capture Perfetto traces with Vulkan layers. +metadata: + icon: 📱 +--- + +# Vulkan Layer Android Testing & Verification Skill + +Use this skill to build, deploy, and verify any Vulkan layer in `VulkanTools` (`VK_LAYER_GOOGLE_DebugMarker`, `VK_LAYER_GOOGLE_DeviceMemoryReport`, `VK_LAYER_GOOGLE_FpsOverlay`, `VK_LAYER_GOOGLE_LimitExtensions`, `VK_LAYER_GOOGLE_Screenshot`) on a connected Android device against real workloads (e.g. Boss Room Unity sample or Sherlock layer app). + +--- + +## 1. Prerequisites & Environment Setup + +1. **Connected Device**: + Verify an ADB device is connected and responsive: + ```bash + adb devices + ``` + +2. **Android SDK & NDK**: + Ensure `ANDROID_HOME` and `ANDROID_NDK_HOME` (NDK 29+) are exported, and CMake (3.22.1+) / Ninja are on `PATH`: + ```bash + export ANDROID_HOME=/usr/local/google/home/okuznetsov/Android/Sdk + export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865 + export PATH=$ANDROID_HOME/cmake/3.22.1/bin:$PATH + ``` + +--- + +## 2. Build the Vulkan Layer for Android + +From the repository root (`/usr/local/google/home/okuznetsov/prj/VulkanTools`): + +```bash +python3 scripts/android.py --config Release --app-abi arm64-v8a +``` + +The compiled shared libraries will be placed in: +`build-android/install/arm64-v8a/lib/libVkLayer_.so` + +Available layers: +- `libVkLayer_DebugMarker.so` (`VK_LAYER_GOOGLE_DebugMarker`) +- `libVkLayer_DeviceMemoryReport.so` (`VK_LAYER_GOOGLE_DeviceMemoryReport`) +- `libVkLayer_FpsOverlay.so` (`VK_LAYER_GOOGLE_FpsOverlay`) +- `libVkLayer_LimitExtensions.so` (`VK_LAYER_GOOGLE_LimitExtensions`) +- `libVkLayer_Screenshot.so` (`VK_LAYER_GOOGLE_Screenshot`) + +--- + +## 3. Deploy Layer to Android (Bypassing Android 14/15 SELinux Restrictions) + +> [!IMPORTANT] +> On Android 14 and 15, pushing layers to `/data/local/debug/vulkan/` often fails or is blocked by SELinux for release or third-party applications. The reliable deployment technique is pushing directly into the target application's native library directory and restoring the `apk_data_file` SELinux label. + +### Step 3.1: Locate the Target Application's Native Library Directory + +```bash +PACKAGE_NAME="com.Unity.com.unity.multiplayer.samples.coop" # or target app package +APP_DIR=$(adb shell pm path "$PACKAGE_NAME" | head -n 1 | sed 's/package://;s/\/base.apk//') +LIB_DIR="$APP_DIR/lib/arm64" +``` + +Common target packages: +- **Boss Room Unity Sample**: `com.Unity.com.unity.multiplayer.samples.coop` +- **Sherlock Layers App**: `com.google.androidperformanceanalyzer` + +### Step 3.2: Push and Fix SELinux Context + +```bash +LAYER_SO="libVkLayer_DebugMarker.so" +LOCAL_SO="build-android/install/arm64-v8a/lib/$LAYER_SO" + +# Push directly to target app's lib directory +adb push "$LOCAL_SO" "$LIB_DIR/$LAYER_SO" + +# Fix permissions and SELinux label so the app sandbox can load it +adb shell chcon u:object_r:apk_data_file:s0 "$LIB_DIR/$LAYER_SO" +adb shell chmod 755 "$LIB_DIR/$LAYER_SO" +``` + +--- + +## 4. Enable the Layer in Android Graphics Environment + +Set the global layer properties and Android `Settings` hooks: + +```bash +LAYER_NAME="VK_LAYER_GOOGLE_DebugMarker" + +adb shell setprop debug.vulkan.layers "$LAYER_NAME" +adb shell settings put global enable_gpu_debug_layers 1 +adb shell settings put global gpu_debug_app "$PACKAGE_NAME" +adb shell settings put global gpu_debug_layers "$LAYER_NAME" +``` + +--- + +## 5. Launch the Workload and Verify Layer Loading + +Clear logcat, stop any existing instance, and launch the application: + +```bash +adb shell am force-stop "$PACKAGE_NAME" +adb shell logcat -c + +# Launch main activity +adb shell monkey -p "$PACKAGE_NAME" -c android.intent.category.LAUNCHER 1 +sleep 3 +``` + +Verify that the Vulkan loader successfully attached the layer: + +```bash +adb logcat -d | grep -iE "vulkan.*Loaded layer|vulkan.*added global layer" +``` + +Expected logcat snippet: +```log +vulkan : searching for layers in '/data/app/.../lib/arm64' +vulkan : added global layer 'VK_LAYER_GOOGLE_DebugMarker' from library '/data/app/.../lib/arm64/libVkLayer_DebugMarker.so' +vulkan : Loaded layer VK_LAYER_GOOGLE_DebugMarker +``` + +--- + +## 6. Capture Perfetto Trace (Render Stages + Vulkan Debug Markers) + +Create a 3-second Perfetto capture configuration targeting GPU render stages and Vulkan debug marker events: + +```bash +cat << 'EOF' > /tmp/perfetto_layer_config.txt +buffers: { + size_kb: 65536 + fill_policy: RING_BUFFER +} +data_sources: { + config { + name: "gpu.renderstages" + } +} +data_sources: { + config { + name: "track_event" + track_event_config { + enabled_categories: "VulkanDebugMarker" + } + } +} +duration_ms: 3000 +EOF + +adb push /tmp/perfetto_layer_config.txt /data/misc/perfetto-configs/perfetto.txt +adb shell "perfetto --out /data/misc/perfetto-traces/trace.perfetto --txt -c /data/misc/perfetto-configs/perfetto.txt" +adb pull /data/misc/perfetto-traces/trace.perfetto /tmp/trace_layer.perfetto +``` + +--- + +## 7. Automated TraceProcessor Verification + +Validate the captured trace programmatically using `perfetto.trace_processor` to verify that debug names and render stages are present and the workload did not crash: + +```python +#!/usr/bin/env python3 +import sys +from perfetto.trace_processor import TraceProcessor, TraceProcessorConfig + +trace_file = "/tmp/trace_layer.perfetto" +tp = TraceProcessor(trace=trace_file, config=TraceProcessorConfig(bin_path=None)) + +# 1. Verify GPU Render Stages +render_stages = list(tp.query("SELECT count(*) as count, name FROM slice WHERE track_id IN (SELECT id FROM gpu_track) GROUP BY name;")) +print("=== GPU Render Stages ===") +for row in render_stages: + print(f" {row.name}: {row.count} slices") + +# 2. Verify Vulkan API / Debug Marker Events +debug_events = list(tp.query("SELECT count(*) as count FROM slice WHERE name LIKE '%Vk%' OR name LIKE '%Vulkan%' OR category = 'VulkanDebugMarker';")) +print(f"\n=== Vulkan Debug Events ===\n Total matching slices: {debug_events[0].count if debug_events else 0}") + +# 3. Query Object Names if present in args +object_names = list(tp.query("SELECT display_value as name, count(*) as count FROM args WHERE key = 'debug_name' OR key LIKE '%object_name%' GROUP BY display_value LIMIT 15;")) +if object_names: + print("\n=== Sample Captured Object Names ===") + for row in object_names: + print(f" {row.name} ({row.count} occurrences)") + +tp.close() +``` + +--- + +## 8. Teardown & Device Cleanup + +Reset device layer settings after the test: + +```bash +adb shell setprop debug.vulkan.layers "" +adb shell settings delete global enable_gpu_debug_layers +adb shell settings delete global gpu_debug_app +adb shell settings delete global gpu_debug_layers +``` diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 06d2f09a89..905d58597a 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -261,6 +261,10 @@ foreach(layer ${TOOL_LAYERS}) target_link_Libraries(${layer} PRIVATE Vulkan::Headers Vulkan::UtilityHeaders Vulkan::LayerSettings) + if (CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|DragonFly|GNU") + target_link_options(${layer} PRIVATE -Wl,-Bsymbolic-functions) + endif() + if (ANDROID) target_link_Libraries(${layer} PRIVATE log android atomic) endif() diff --git a/layersvt/common/README.md b/layersvt/common/README.md index 3031a99311..42bef8f31b 100644 --- a/layersvt/common/README.md +++ b/layersvt/common/README.md @@ -53,7 +53,7 @@ The `layersvt_common` library provides a modern, thread-safe C++ foundation for * **`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. + Header-only template metaprogramming helpers (`DispatchDownstream`, `DispatchDownstreamOr`) 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. diff --git a/layersvt/common/dispatch_downstream.h b/layersvt/common/dispatch_downstream.h index 10ade441d2..5ccac86531 100644 --- a/layersvt/common/dispatch_downstream.h +++ b/layersvt/common/dispatch_downstream.h @@ -90,17 +90,4 @@ inline auto DispatchDownstreamOr(Fallback&& fallback, Handle handle, Args&&... a } } -/** - * Forwards an optional Vulkan command returning VkResult downstream, falling back to VK_SUCCESS. - * Returns the downstream VkResult on success, or VK_SUCCESS if the downstream command is unavailable. - */ -template -inline VkResult DispatchDownstreamOrSuccess(Handle handle, Args&&... args) { - using TableType = typename MemberTraits::ClassType; - using ReturnType = decltype((std::declval()->*MemberPointer)(handle, std::forward(args)...)); - static_assert(std::is_same_v, - "DispatchDownstreamOrSuccess can only be used with Vulkan commands returning VkResult"); - return DispatchDownstreamOr(VK_SUCCESS, handle, std::forward(args)...); -} - } // namespace layersvt diff --git a/layersvt/common/dispatch_table_manager.cpp b/layersvt/common/dispatch_table_manager.cpp index 87519264c6..9e1c91cbb4 100644 --- a/layersvt/common/dispatch_table_manager.cpp +++ b/layersvt/common/dispatch_table_manager.cpp @@ -25,14 +25,13 @@ VkuInstanceDispatchTable* DispatchTableManager::InitInstanceTable(VkInstance ins PFN_vkGetInstanceProcAddr get_instance_proc_addr) { assert(instance != VK_NULL_HANDLE); assert(get_instance_proc_addr != nullptr); - auto table = std::make_unique(); - vkuInitInstanceDispatchTable(instance, table.get(), get_instance_proc_addr); Key key = GetDispatchKey(instance); std::lock_guard lock(instance_mutex_); - auto [iterator, inserted] = instance_tables_.try_emplace(key, std::move(table)); - instance_keys_[key] = instance; - return iterator->second.get(); + auto [iterator, inserted] = instances_.try_emplace(key); + iterator->second.instance = instance; + vkuInitInstanceDispatchTable(instance, &iterator->second.table, get_instance_proc_addr); + return &iterator->second.table; } VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(VkInstance instance) const { @@ -41,9 +40,9 @@ VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(VkInsta } 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(); + auto table_iterator = instances_.find(key); + if (table_iterator != instances_.end()) { + return const_cast(&table_iterator->second.table); } return nullptr; } @@ -56,9 +55,9 @@ VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(VkPhysi 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(); + auto table_iterator = instances_.find(instance_key); + if (table_iterator != instances_.end()) { + return const_cast(&table_iterator->second.table); } } return nullptr; @@ -67,21 +66,13 @@ VkuInstanceDispatchTable* DispatchTableManager::GetInstanceDispatchTable(VkPhysi 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; + auto iterator = instances_.find(key); + if (iterator != instances_.end()) { + VkInstance instance = iterator->second.instance; std::erase_if(physical_device_to_instance_map_, [instance](const auto& entry) { return entry.second == instance; }); - instance_keys_.erase(key_iterator); + instances_.erase(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, @@ -112,13 +103,12 @@ VkInstance DispatchTableManager::GetVkInstance(VkPhysicalDevice physical_device) 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(); + auto [iterator, inserted] = device_entries_.try_emplace(key); + vkuInitDeviceDispatchTable(device, &iterator->second.table, get_device_proc_addr); + return &iterator->second.table; } VkuDeviceDispatchTable* DispatchTableManager::GetDeviceDispatchTable(const void* object) const { @@ -127,9 +117,9 @@ VkuDeviceDispatchTable* DispatchTableManager::GetDeviceDispatchTable(const void* } 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(); + auto table_iterator = device_entries_.find(key); + if (table_iterator != device_entries_.end()) { + return const_cast(&table_iterator->second.table); } return nullptr; } @@ -137,8 +127,7 @@ VkuDeviceDispatchTable* DispatchTableManager::GetDeviceDispatchTable(const void* void DispatchTableManager::DestroyDeviceTable(Key key) { assert(key != Key{}); std::lock_guard lock(device_mutex_); - device_tables_.erase(key); - loader_callbacks_.erase(key); + device_entries_.erase(key); } void DispatchTableManager::SetDeviceLoaderDataCallback(VkDevice device, PFN_vkSetDeviceLoaderData callback) { @@ -146,16 +135,16 @@ void DispatchTableManager::SetDeviceLoaderDataCallback(VkDevice device, PFN_vkSe assert(callback != nullptr); Key key = GetDispatchKey(device); std::lock_guard lock(device_mutex_); - loader_callbacks_[key] = callback; + device_entries_[key].loader_callback = 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; + auto callback_iterator = device_entries_.find(key); + if (callback_iterator != device_entries_.end()) { + return callback_iterator->second.loader_callback; } return nullptr; } diff --git a/layersvt/common/dispatch_table_manager.h b/layersvt/common/dispatch_table_manager.h index db7f312f68..0002a987f3 100644 --- a/layersvt/common/dispatch_table_manager.h +++ b/layersvt/common/dispatch_table_manager.h @@ -68,13 +68,6 @@ class DispatchTableManager final { */ [[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 @@ -84,11 +77,6 @@ class DispatchTableManager final { // 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. */ @@ -135,19 +123,27 @@ class DispatchTableManager final { [[nodiscard]] PFN_vkSetDeviceLoaderData GetDeviceLoaderDataCallback(VkDevice device) const; private: + struct InstanceEntry { + VkInstance instance = VK_NULL_HANDLE; + VkuInstanceDispatchTable table{}; + }; + + struct DeviceEntry { + VkuDeviceDispatchTable table{}; + PFN_vkSetDeviceLoaderData loader_callback = nullptr; + }; + DispatchTableManager(const DispatchTableManager&) = delete; DispatchTableManager& operator=(const DispatchTableManager&) = delete; DispatchTableManager(DispatchTableManager&&) = delete; DispatchTableManager& operator=(DispatchTableManager&&) = delete; mutable std::mutex instance_mutex_; - std::unordered_map> instance_tables_; - std::unordered_map instance_keys_; + std::unordered_map instances_; std::unordered_map physical_device_to_instance_map_; mutable std::mutex device_mutex_; - std::unordered_map> device_tables_; - std::unordered_map loader_callbacks_; + std::unordered_map device_entries_; }; } // namespace layersvt diff --git a/layersvt/common/layer_base.cpp b/layersvt/common/layer_base.cpp index bd14c2793f..bc631649c7 100644 --- a/layersvt/common/layer_base.cpp +++ b/layersvt/common/layer_base.cpp @@ -22,20 +22,6 @@ #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 { @@ -87,24 +73,9 @@ VkResult CopyEnumerationProperties(const std::vector& items, uint32_t* proper return (copy_count < total) ? VK_INCOMPLETE : VK_SUCCESS; } -#if defined(_WIN32) -void InitPlatformErrorHandling() { -#if !defined(NDEBUG) - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); -#endif - _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); -} -#endif } // namespace LayerBase::LayerBase() { -#if defined(_WIN32) - InitPlatformErrorHandling(); -#endif layer_ = this; } @@ -287,9 +258,7 @@ VkResult LayerBase::EnumerateInstanceExtensionProperties(const char* layer_name, LayerBase* layer = Get(); const LayerManifest* manifest = layer->GetLayerManifest(); - if (!manifest) { - return VK_ERROR_INITIALIZATION_FAILED; - } + assert(manifest != nullptr); 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) { @@ -298,8 +267,6 @@ VkResult LayerBase::EnumerateInstanceExtensionProperties(const char* layer_name, } std::vector extensions = manifest->instance_extensions; - layer->ProcessInstanceExtensions(layer_name, extensions); - return CopyEnumerationProperties(extensions, property_count, properties); } @@ -309,9 +276,7 @@ VkResult LayerBase::EnumerateInstanceLayerProperties(uint32_t* property_count, V LayerBase* layer = Get(); const LayerManifest* manifest = layer->GetLayerManifest(); - if (!manifest) { - return VK_ERROR_INITIALIZATION_FAILED; - } + assert(manifest != nullptr); if (properties == nullptr) { *property_count = 1; @@ -335,25 +300,13 @@ VkResult LayerBase::EnumerateDeviceLayerProperties(VkPhysicalDevice physical_dev 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 : ""; + assert(manifest != nullptr); + const char* my_layer_name = (manifest->layer_name != nullptr) ? 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) { @@ -362,6 +315,14 @@ VkResult LayerBase::EnumerateDeviceExtensionPropertiesWithDownstream( return CopyEnumerationProperties(extensions, property_count, properties); } + PFN_vkEnumerateDeviceExtensionProperties downstream_function = nullptr; + if (physical_device != VK_NULL_HANDLE) { + auto* table = GetInstanceDispatchTable(physical_device); + if (table != nullptr) { + downstream_function = table->EnumerateDeviceExtensionProperties; + } + } + // If another layer is being queried, forward downstream or return VK_ERROR_LAYER_NOT_PRESENT if (layer_name != nullptr) { if (downstream_function) { @@ -390,19 +351,17 @@ VkResult LayerBase::EnumerateDeviceExtensionPropertiesWithDownstream( } } - 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); + 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); @@ -412,27 +371,23 @@ VkResult LayerBase::EnumerateDeviceExtensionPropertiesWithDownstream( VkResult LayerBase::GetPhysicalDeviceToolProperties(VkPhysicalDevice physical_device, uint32_t* tool_count, VkPhysicalDeviceToolPropertiesEXT* tool_properties) { - PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream = nullptr; + assert(tool_count != nullptr); + AssertLayerInitialized(); + + LayerBase* layer = Get(); + const LayerManifest* manifest = layer->GetLayerManifest(); + assert(manifest != nullptr); + + PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream_function = nullptr; if (physical_device != VK_NULL_HANDLE) { auto* table = GetInstanceDispatchTable(physical_device); if (table != nullptr) { - downstream = table->GetPhysicalDeviceToolPropertiesEXT; - if (!downstream) { - downstream = table->GetPhysicalDeviceToolProperties; + downstream_function = table->GetPhysicalDeviceToolPropertiesEXT; + if (!downstream_function) { + downstream_function = 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) { @@ -456,26 +411,15 @@ VkResult LayerBase::GetPhysicalDeviceToolPropertiesWithDownstream( } } - if (manifest && manifest->tool_properties.has_value()) { + if (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*) {} @@ -484,6 +428,11 @@ void LayerBase::PreCreateDevice(VkPhysicalDevice, VkDeviceCreateInfo*, const VkA void LayerBase::PostCreateDevice(VkDevice, VkPhysicalDevice, const VkDeviceCreateInfo*, const VkAllocationCallbacks*) {} void LayerBase::PreDestroyDevice(VkDevice, const VkAllocationCallbacks*) {} +const LayerManifest* LayerBase::GetLayerManifest() const { + static const LayerManifest kDefaultManifest{}; + return &kDefaultManifest; +} + PFN_vkVoidFunction LayerBase::GetLayerInstanceCommand(const char*) { return nullptr; } PFN_vkVoidFunction LayerBase::GetLayerDeviceCommand(const char*) { return nullptr; } @@ -530,7 +479,8 @@ PFN_vkVoidFunction LayerBase::GetKnownInstanceCommand(const char* command_name) } if (std::strcmp(command_name, "vkGetPhysicalDeviceToolPropertiesEXT") == 0 || std::strcmp(command_name, "vkGetPhysicalDeviceToolProperties") == 0) { - if (layer->HasToolProperties()) { + const LayerManifest* manifest = layer->GetLayerManifest(); + if (manifest != nullptr && manifest->tool_properties.has_value()) { return reinterpret_cast(GetPhysicalDeviceToolProperties); } } diff --git a/layersvt/common/layer_base.h b/layersvt/common/layer_base.h index abf6b1b353..311545c151 100644 --- a/layersvt/common/layer_base.h +++ b/layersvt/common/layer_base.h @@ -83,17 +83,10 @@ class LayerBase { /** * 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; } + [[nodiscard]] virtual const LayerManifest* GetLayerManifest() const; - // Extension and tooling hooks - - /** - * Customizes or filters instance extensions during vkEnumerateInstanceExtensionProperties. - */ - virtual void ProcessInstanceExtensions(const char* layer_name, - std::vector& extensions) const; + // Extension hooks /** * Customizes or filters device extensions during vkEnumerateDeviceExtensionProperties. @@ -101,19 +94,6 @@ class LayerBase { virtual void ProcessDeviceExtensions(VkPhysicalDevice physical_device, const char* layer_name, std::vector& extensions) const; - /** - * Customizes or filters tool properties during vkGetPhysicalDeviceToolProperties. - */ - virtual void ProcessToolProperties(VkPhysicalDevice physical_device, - std::vector& tools) const; - - /** - * Indicates whether this layer intercepts physical device tool properties. - * Default implementation returns true if the layer manifest defines tool_properties. - * Returns true if tool properties queries should be intercepted, or false to dispatch downstream. - */ - [[nodiscard]] virtual bool HasToolProperties() const; - // Layer-specific command intercepts /** @@ -175,9 +155,6 @@ class LayerBase { virtual void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); private: - [[nodiscard]] DispatchTableManager& GetDispatchTableManager() noexcept { return dispatch_table_manager_; } - [[nodiscard]] const DispatchTableManager& GetDispatchTableManager() const noexcept { return dispatch_table_manager_; } - [[nodiscard]] static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkInstance instance); [[nodiscard]] static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkPhysicalDevice physical_device); [[nodiscard]] static VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object); @@ -245,14 +222,8 @@ class LayerBase { 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/debug_marker/debug_marker.cpp b/layersvt/debug_marker/debug_marker.cpp index e57f7d024b..8539b04926 100644 --- a/layersvt/debug_marker/debug_marker.cpp +++ b/layersvt/debug_marker/debug_marker.cpp @@ -17,7 +17,6 @@ #include "debug_marker_perfetto.h" #include "debug_marker_handwritten_functions_vk_ext_debug_marker.h" #include "debug_marker_handwritten_functions_vk_ext_debug_utils.h" -#include "common/device_instance_tracker.h" #include "perfetto/perfetto.h" #include @@ -28,7 +27,7 @@ DebugMarker g_layer; DebugMarker::DebugMarker() = default; const layersvt::LayerManifest* DebugMarker::GetLayerManifest() const { - static const layersvt::LayerManifest manifest(layersvt::LayerManifest::Config{ + static const layersvt::LayerManifest manifest{ .layer_name = "VK_LAYER_GOOGLE_DebugMarker", .description = "layer: DebugMarker", .spec_version = VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), @@ -42,7 +41,7 @@ const layersvt::LayerManifest* DebugMarker::GetLayerManifest() const { {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, }, .tool_properties = std::nullopt, - }); + }; return &manifest; } @@ -53,6 +52,19 @@ void DebugMarker::PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkA std::call_once(perfetto_initialization_flag, []() { InitializeDebugMarkerPerfetto(); }); } +void DebugMarker::PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { + (void)pAllocator; + std::lock_guard lock(mutex_); + uint64_t dev_handle = (uint64_t)device; + for (auto it = debug_object_names_.begin(); it != debug_object_names_.end();) { + if (it->second.vk_device == dev_handle) { + it = debug_object_names_.erase(it); + } else { + ++it; + } + } +} + void DebugMarker::SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name) { std::lock_guard lock(mutex_); diff --git a/layersvt/debug_marker/debug_marker.h b/layersvt/debug_marker/debug_marker.h index aeaacd4ef7..d2ce7bcc67 100644 --- a/layersvt/debug_marker/debug_marker.h +++ b/layersvt/debug_marker/debug_marker.h @@ -47,9 +47,6 @@ * we write all currently known object names to the trace. We retain the names in memory * because a user might start another Perfetto session later, requiring us to emit * all object names again. - * A potential issue exists if an application constantly creates and destroys - * objects without bound, as we currently do not remove names for destroyed objects. - * Support for removing names on object destruction can be added later if needed. * * This class is a singleton, inherits from LayerBase, and provides thread-safe access to its state. */ @@ -89,6 +86,11 @@ class DebugMarker : public layersvt::LayerBase { */ void PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) override; + /** + * Lifecycle hook called before vkDestroyDevice to remove tracked names for destroyed objects. + */ + void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) override; + const layersvt::LayerManifest* GetLayerManifest() const override; PFN_vkVoidFunction GetLayerInstanceCommand(const char* name) override; PFN_vkVoidFunction GetLayerDeviceCommand(const char* name) override; diff --git a/layersvt/test/common/layer_base_test_peer.h b/layersvt/test/common/layer_base_test_peer.h index ad4c8dc5b3..ce41ab0840 100644 --- a/layersvt/test/common/layer_base_test_peer.h +++ b/layersvt/test/common/layer_base_test_peer.h @@ -64,8 +64,19 @@ class LayerBaseTestPeer { VkExtensionProperties* properties, PFN_vkEnumerateDeviceExtensionProperties downstream_function = nullptr) { if (downstream_function != nullptr) { - return LayerBase::EnumerateDeviceExtensionPropertiesWithDownstream( - physical_device, layer_name, property_count, properties, downstream_function); + static void* mock_instance_vtable = reinterpret_cast(static_cast(0xF00D)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + static void* mock_phys_dev_vtable = reinterpret_cast(static_cast(0xBAAD)); + if (physical_device == VK_NULL_HANDLE) { + physical_device = reinterpret_cast(&mock_phys_dev_vtable); + } + LayerBase* layer = LayerBase::Get(); + if (layer != nullptr) { + VkuInstanceDispatchTable* table = layer->dispatch_table_manager_.InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + table->EnumerateDeviceExtensionProperties = downstream_function; + layer->dispatch_table_manager_.RegisterPhysicalDevices(&physical_device, 1, mock_instance); + } } return LayerBase::EnumerateDeviceExtensionProperties( physical_device, layer_name, property_count, properties); @@ -75,15 +86,26 @@ class LayerBaseTestPeer { VkPhysicalDeviceToolPropertiesEXT* tool_properties, PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream_function = nullptr) { if (downstream_function != nullptr) { - return LayerBase::GetPhysicalDeviceToolPropertiesWithDownstream( - physical_device, tool_count, tool_properties, downstream_function); + static void* mock_instance_vtable = reinterpret_cast(static_cast(0xF00D)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + static void* mock_phys_dev_vtable = reinterpret_cast(static_cast(0xBAAD)); + if (physical_device == VK_NULL_HANDLE) { + physical_device = reinterpret_cast(&mock_phys_dev_vtable); + } + LayerBase* layer = LayerBase::Get(); + if (layer != nullptr) { + VkuInstanceDispatchTable* table = layer->dispatch_table_manager_.InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + table->GetPhysicalDeviceToolPropertiesEXT = downstream_function; + layer->dispatch_table_manager_.RegisterPhysicalDevices(&physical_device, 1, mock_instance); + } } return LayerBase::GetPhysicalDeviceToolProperties( physical_device, tool_count, tool_properties); } static const LayerManifest* GetLayerManifest(const LayerBase& layer) { return layer.GetLayerManifest(); } - static DispatchTableManager& GetDispatchTableManager(LayerBase& layer) { return layer.GetDispatchTableManager(); } - static const DispatchTableManager& GetDispatchTableManager(const LayerBase& layer) { return layer.GetDispatchTableManager(); } + static DispatchTableManager& GetDispatchTableManager(LayerBase& layer) { return layer.dispatch_table_manager_; } + static const DispatchTableManager& GetDispatchTableManager(const LayerBase& layer) { return layer.dispatch_table_manager_; } static VkInstance GetVkInstance(VkPhysicalDevice physical_device) { return LayerBase::GetVkInstance(physical_device); } diff --git a/layersvt/test/common/test_dispatch_downstream.cpp b/layersvt/test/common/test_dispatch_downstream.cpp index 5b3a62a4c6..0d2661bef2 100644 --- a/layersvt/test/common/test_dispatch_downstream.cpp +++ b/layersvt/test/common/test_dispatch_downstream.cpp @@ -31,13 +31,13 @@ TEST(DispatchDownstreamTest, DispatchDownstream) { auto mock_device = reinterpret_cast(&mock_device_vtable); // 1. Unregistered handles (no dispatch table present) - // DispatchDownstreamOrSuccess returns VK_SUCCESS fallback for VkResult commands + // DispatchDownstreamOr with VK_SUCCESS fallback for VkResult commands uint32_t count = 0; VkResult instance_result = - DispatchDownstreamOrSuccess<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(mock_instance, &count, nullptr); + DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(VK_SUCCESS, mock_instance, &count, nullptr); EXPECT_EQ(instance_result, VK_SUCCESS); - VkResult device_result = DispatchDownstreamOrSuccess<&VkuDeviceDispatchTable::DeviceWaitIdle>(mock_device); + VkResult device_result = DispatchDownstreamOr<&VkuDeviceDispatchTable::DeviceWaitIdle>(VK_SUCCESS, mock_device); EXPECT_EQ(device_result, VK_SUCCESS); // void return type safely no-ops with DispatchDownstreamOr @@ -53,9 +53,9 @@ TEST(DispatchDownstreamTest, DispatchDownstream) { 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)), + EXPECT_EQ((DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(VK_SUCCESS, mock_instance, &count, nullptr)), VK_SUCCESS); - EXPECT_EQ((DispatchDownstreamOrSuccess<&VkuDeviceDispatchTable::DeviceWaitIdle>(mock_device)), VK_SUCCESS); + EXPECT_EQ((DispatchDownstreamOr<&VkuDeviceDispatchTable::DeviceWaitIdle>(VK_SUCCESS, mock_device)), VK_SUCCESS); DispatchDownstreamOr<&VkuInstanceDispatchTable::DestroyInstance>([] {}, mock_instance, nullptr); DispatchDownstreamOr<&VkuDeviceDispatchTable::DestroyDevice>([] {}, mock_device, nullptr); @@ -101,7 +101,7 @@ TEST(DispatchDownstreamTest, DispatchDownstream) { [] {}, mock_physical_device, &properties); // Mapped physical device forwards downstream through instance table - dispatch_table_manager.SetVkInstance(mock_physical_device, mock_instance); + dispatch_table_manager.RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); static bool physical_device_function_called = false; physical_device_function_called = false; diff --git a/layersvt/test/common/test_dispatch_table_manager.cpp b/layersvt/test/common/test_dispatch_table_manager.cpp index 9781c4889c..2efea12bca 100644 --- a/layersvt/test/common/test_dispatch_table_manager.cpp +++ b/layersvt/test/common/test_dispatch_table_manager.cpp @@ -167,8 +167,8 @@ TEST(DispatchTableManagerTest, BasicPhysicalDeviceTracking) { 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); + VkPhysicalDevice physical_devices[] = {mock_physical_device1, mock_physical_device2}; + dispatch_table_manager.RegisterPhysicalDevices(physical_devices, 2, mock_instance); EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device1), mock_instance); EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device2), mock_instance); @@ -204,7 +204,7 @@ TEST(DispatchTableManagerTest, PhysicalDeviceResolvesInstanceDispatchTable) { mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); EXPECT_NE(instance_table, nullptr); - dispatch_table_manager.SetVkInstance(mock_physical_device, mock_instance); + dispatch_table_manager.RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_physical_device), instance_table); } @@ -221,8 +221,8 @@ TEST(DispatchTableManagerTest, AtomicTeardownOfPhysicalDevicesOnInstanceDestroy) 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); + VkPhysicalDevice physical_devices[] = {mock_physical_device1, mock_physical_device2}; + dispatch_table_manager.RegisterPhysicalDevices(physical_devices, 2, mock_instance); EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device1), mock_instance); EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device2), mock_instance); @@ -243,7 +243,6 @@ TEST(DispatchTableManagerTest, NullHandleSafety) { 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); } @@ -283,8 +282,8 @@ TEST(DispatchTableManagerTest, ConcurrentPhysicalDevicesAndLifecycle) { 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); + VkPhysicalDevice thread_devices[] = {my_data.physical_device1, my_data.physical_device2}; + dispatch_table_manager.RegisterPhysicalDevices(thread_devices, 2, 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); diff --git a/layersvt/test/common/test_layer_base.cpp b/layersvt/test/common/test_layer_base.cpp index ba0e154415..a843c80c24 100644 --- a/layersvt/test/common/test_layer_base.cpp +++ b/layersvt/test/common/test_layer_base.cpp @@ -238,7 +238,7 @@ TEST(LayerBaseTest, PreCreateDeviceMutation) { auto mock_physical_device = reinterpret_cast(static_cast(0x5555)); MutatingLayer layer; - LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + LayerBaseTestPeer::GetDispatchTableManager(layer).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); VkDevice device = VK_NULL_HANDLE; EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), VK_SUCCESS); @@ -270,7 +270,7 @@ TEST(LayerBaseTest, TeardownOrdering) { } return nullptr; }); - LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + LayerBaseTestPeer::GetDispatchTableManager(layer).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); LayerBaseTestPeer::DestroyInstance(mock_instance, nullptr); @@ -318,7 +318,7 @@ TEST(LayerBaseTest, CreateDeviceWithMockChain) { device_create_info.pNext = &callback_info; LifecycleTestLayer layer; - LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + LayerBaseTestPeer::GetDispatchTableManager(layer).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); VkDevice device = VK_NULL_HANDLE; VkAllocationCallbacks mock_allocator{}; @@ -343,7 +343,7 @@ TEST(LayerBaseTest, CreateDeviceNullHandling) { 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); + LayerBaseTestPeer::GetDispatchTableManager(layer).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); // Null create info EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, nullptr, nullptr, &device), @@ -548,7 +548,7 @@ TEST(LayerBaseTest, CreateDeviceNullFpCreateDevice) { auto mock_physical_device = reinterpret_cast(static_cast(0x5555)); LifecycleTestLayer layer; - LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + LayerBaseTestPeer::GetDispatchTableManager(layer).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); static PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; @@ -740,7 +740,7 @@ TEST(LayerBaseTest, PhysicalDeviceResolvesInstanceTable) { auto mock_physical_device = reinterpret_cast(&mock_physical_device_vtable); LayerBase layer; - LayerBaseTestPeer::GetDispatchTableManager(layer).SetVkInstance(mock_physical_device, mock_instance); + LayerBaseTestPeer::GetDispatchTableManager(layer).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); diff --git a/layersvt/test/common/test_layer_manifest.cpp b/layersvt/test/common/test_layer_manifest.cpp index ea1bdb7cba..0843d26285 100644 --- a/layersvt/test/common/test_layer_manifest.cpp +++ b/layersvt/test/common/test_layer_manifest.cpp @@ -391,91 +391,6 @@ TEST(LayerBaseHooksTest, ProcessDeviceExtensionsFiltering) { 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", diff --git a/layersvt/test/test_debugmarker.cpp b/layersvt/test/test_debugmarker.cpp index 07ef4f636b..d98f71ee73 100644 --- a/layersvt/test/test_debugmarker.cpp +++ b/layersvt/test/test_debugmarker.cpp @@ -18,42 +18,7 @@ #include #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 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); - } - 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) { - return LayerBase::EnumerateDeviceExtensionProperties(physical_device, layer_name, property_count, properties); - } - static DeviceInstanceTracker& GetDeviceTracker(LayerBase& layer) { - return layer.GetDeviceTracker(); - } -}; -} // namespace layersvt +#include "test/common/layer_base_test_peer.h" static const char* kLayerName = "VK_LAYER_GOOGLE_DebugMarker"; @@ -160,11 +125,47 @@ TEST_F(DebugMarkerTests, LayerBaseLifecycleAndTrackerTest) { VkPhysicalDevice mock_physical_device = reinterpret_cast(0x1234); VkInstance mock_instance = reinterpret_cast(0x5678); - layersvt::LayerBaseTestPeer::GetDeviceTracker(DebugMarker::Get()).SetVkInstance(mock_physical_device, mock_instance); - EXPECT_EQ(layersvt::LayerBaseTestPeer::GetDeviceTracker(DebugMarker::Get()).GetVkInstance(mock_physical_device), mock_instance); + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetVkInstance(mock_physical_device), mock_instance); layer_test::ResetLayer(); - EXPECT_EQ(layersvt::LayerBaseTestPeer::GetDeviceTracker(DebugMarker::Get()).GetVkInstance(mock_physical_device), VK_NULL_HANDLE); + EXPECT_EQ(layersvt::LayerBaseTestPeer::GetVkInstance(mock_physical_device), VK_NULL_HANDLE); +} + +TEST_F(DebugMarkerTests, PreDestroyDeviceCleanupTest) { + TEST_DESCRIPTION("Verify that DestroyDevice cleans up tracked objects associated with that device"); + + layer_test::ResetLayer(); + + void* mock_dev1_vtable = reinterpret_cast(0x1000); + VkDevice dev1 = reinterpret_cast(&mock_dev1_vtable); + void* mock_dev2_vtable = reinterpret_cast(0x2000); + VkDevice dev2 = reinterpret_cast(&mock_dev2_vtable); + + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()).InitDeviceTable( + dev1, [](VkDevice, const char*) -> PFN_vkVoidFunction { + return reinterpret_cast(+[](VkDevice, const VkAllocationCallbacks*) {}); + }); + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()).InitDeviceTable( + dev2, [](VkDevice, const char*) -> PFN_vkVoidFunction { + return reinterpret_cast(+[](VkDevice, const VkAllocationCallbacks*) {}); + }); + + DebugMarker::Get().SetDebugObjectName((uint64_t)dev1, VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1"); + DebugMarker::Get().SetDebugObjectName((uint64_t)dev2, VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2"); + + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1")); + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); + + // Destroy dev1 - should remove Buffer1 but keep Buffer2 + layersvt::LayerBaseTestPeer::DestroyDevice(dev1, nullptr); + + EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1")); + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); + + // Destroy dev2 - should remove Buffer2 + layersvt::LayerBaseTestPeer::DestroyDevice(dev2, nullptr); + EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); } TEST_F(DebugMarkerTests, TemplateMethodDispatchTest) {