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 92538d5e3a..905d58597a 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -45,6 +45,8 @@ else() add_compile_options(-Wpointer-arith) endif() +add_subdirectory(common) + if(BUILD_APIDUMP) find_package(Python3 REQUIRED) @@ -116,7 +118,7 @@ if(BUILD_SCREENSHOT) vk_layer_table.cpp vk_layer_table.h screenshot/screenshot_layer.md - layer_keep_alive.cpp + common/layer_keep_alive.cpp screenshot/json/VkLayer_screenshot.json.in ) endif() @@ -136,7 +138,7 @@ if(BUILD_CPUTIMING) ../scripts/generators/cputiming_generator.py vk_layer_table.cpp vk_layer_table.h - layer_keep_alive.cpp + common/layer_keep_alive.cpp cpu_timing/VkLayer_CPUTiming.json.in ) @@ -167,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 @@ -176,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 - layer_keep_alive.cpp debug_marker/VkLayer_DebugMarker.json.in ) @@ -188,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) @@ -214,7 +214,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 ) @@ -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/README.md b/layersvt/README.md index 7e870e2a68..44a9a040f5 100644 --- a/layersvt/README.md +++ b/layersvt/README.md @@ -22,6 +22,7 @@ Layers are activated at vkCreateInstance time. Layers can also be activated via Note that some layers are code-generated and will therefore exist in the directory (build_dir)/layers -include/vkLayer.h - header file for layer code. +- [common/README.md](common/README.md) - Common layer foundation library (`layersvt_common`) and guide for creating new layers. ### Print API Calls and Parameter Values (build dir)/layers/api_dump.cpp (name=VK_LAYER_LUNARG_api_dump) - print out API calls along with parameter values diff --git a/layersvt/common/CMakeLists.txt b/layersvt/common/CMakeLists.txt new file mode 100644 index 0000000000..64f74a84ed --- /dev/null +++ b/layersvt/common/CMakeLists.txt @@ -0,0 +1,55 @@ +# Copyright (C) 2026 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_library(layersvt_common OBJECT + dispatch_table_manager.h + dispatch_table_manager.cpp + layer_manifest.h + layer_manifest.cpp + layer_base.h + layer_base.cpp + dispatch_downstream.h +) + +set_target_properties(layersvt_common PROPERTIES + FOLDER "layers/common" + POSITION_INDEPENDENT_CODE ON +) + +target_include_directories(layersvt_common PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +target_link_libraries(layersvt_common PUBLIC + Vulkan::Headers + Vulkan::UtilityHeaders +) +if (ANDROID) + target_sources(layersvt_common PRIVATE + layer_keep_alive.cpp + ) + target_link_libraries(layersvt_common PUBLIC ${CMAKE_DL_LIBS}) +endif() + +add_library(layersvt_entrypoints OBJECT + layer_entrypoints.cpp +) + +set_target_properties(layersvt_entrypoints PROPERTIES + FOLDER "layers/common" +) + +target_link_libraries(layersvt_entrypoints PRIVATE + layersvt_common +) diff --git a/layersvt/common/README.md b/layersvt/common/README.md new file mode 100644 index 0000000000..42bef8f31b --- /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`) that deduce table types at compile time and forward commands downstream. +* **`layersvt_entrypoints`** ([`layer_entrypoints.cpp`](layer_entrypoints.cpp)): + CMake `OBJECT` library that exports standard C symbols (`vkGetInstanceProcAddr`, `vkGetDeviceProcAddr`, `vkNegotiateLoaderLayerInterfaceVersion`, and the four Android loader enumeration entry points) without macro duplication. + +--- + +## 2. Step-by-Step: Adding a New Layer + +Follow this 5-step guide to add a new layer (e.g. `MyCustomLayer`). + +### Step 1: Declare the Layer Class (`my_custom_layer.h`) + +Inherit from `layersvt::LayerBase`. Shadow `LayerBase::Get()` to return your derived layer instance, and override only the lifecycle hooks and command intercepts your layer needs: + +```cpp +#pragma once + +#include +#include + +namespace layersvt { + +class MyCustomLayer : public LayerBase { + public: + static MyCustomLayer& Get(); + + protected: + // Return declarative manifest + const LayerManifest* GetLayerManifest() const override; + + // Intercept custom or extension commands + PFN_vkVoidFunction GetLayerInstanceCommand(const char* command_name) override; + PFN_vkVoidFunction GetLayerDeviceCommand(const char* command_name) override; + + // Lifecycle hooks (override as needed) + void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator) override; + void PostCreateInstance(VkInstance instance, const VkInstanceCreateInfo* create_info, + const VkAllocationCallbacks* allocator) override; + void PreDestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) override; + + void PreCreateDevice(VkPhysicalDevice physical_device, VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator) override; + void PostCreateDevice(VkDevice device, VkPhysicalDevice physical_device, + const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator) override; + void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) override; +}; + +} // namespace layersvt +``` + +### Step 2: Implement the Layer (`my_custom_layer.cpp`) + +Implement the layer methods, configure the static manifest, and implement custom intercepted commands: + +```cpp +#include "my_custom_layer.h" +#include + +#include + +namespace layersvt { + +namespace { + +// 1. Static layer instance (instantiated in the shared library entrypoint, not in files linked to unit tests) +MyCustomLayer g_layer; + +// 2. Declarative layer manifest +const LayerManifest kManifest({ + .layer_name = "VK_LAYER_GOOGLE_MyCustomLayer", + .description = "Google Vulkan MyCustomLayer", + .spec_version = VK_API_VERSION_1_3, + .implementation_version = 1, + .instance_extensions = {}, + .device_extensions = { + VkExtensionProperties{"VK_EXT_custom_extension", 1}, + VkExtensionProperties{VK_EXT_TOOLING_INFO_EXTENSION_NAME, VK_EXT_TOOLING_INFO_SPEC_VERSION}, + }, + .tool_properties = VkPhysicalDeviceToolPropertiesEXT{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, + .pNext = nullptr, + .name = "MyCustomLayer", + .version = "1.0", + .purposes = VK_TOOL_PURPOSE_PROFILING_BIT_EXT, + .description = "Google Vulkan MyCustomLayer", + .layer = "VK_LAYER_GOOGLE_MyCustomLayer", + }, +}); + +// Custom intercepted Vulkan command +VKAPI_ATTR void VKAPI_CALL Hook_vkCmdDraw(VkCommandBuffer command_buffer, uint32_t vertex_count, + uint32_t instance_count, uint32_t first_vertex, + uint32_t first_instance) { + // Custom layer logic before dispatch ... + + // Forward downstream to the next layer/driver + layersvt::DispatchDownstream<&VkuDeviceDispatchTable::CmdDraw>( + command_buffer, vertex_count, instance_count, first_vertex, first_instance); + + // Custom layer logic after dispatch ... +} + +} // namespace + +MyCustomLayer& MyCustomLayer::Get() { + assert(LayerBase::Get() != nullptr); + return *static_cast(LayerBase::Get()); +} + +const LayerManifest* MyCustomLayer::GetLayerManifest() const { + return &kManifest; +} + +PFN_vkVoidFunction MyCustomLayer::GetLayerDeviceCommand(const char* command_name) { + assert(command_name != nullptr); + if (std::strcmp(command_name, "vkCmdDraw") == 0) { + return reinterpret_cast(Hook_vkCmdDraw); + } + return nullptr; +} + +PFN_vkVoidFunction MyCustomLayer::GetLayerInstanceCommand(const char* /*command_name*/) { + return nullptr; +} + +void MyCustomLayer::PostCreateDevice(VkDevice device, VkPhysicalDevice physical_device, + const VkDeviceCreateInfo* /*create_info*/, + const VkAllocationCallbacks* /*allocator*/) { + // Setup per-device state ... +} + +void MyCustomLayer::PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* /*allocator*/) { + // Teardown per-device state before downstream destruction ... +} + +} // namespace layersvt +``` + +### Step 3: Configure CMake Target (`layersvt/CMakeLists.txt`) + +Add the shared library module target, linking `layersvt_common` and including `$`: + +```cmake +add_library(VkLayer_MyCustomLayer MODULE + my_custom_layer/my_custom_layer.cpp + my_custom_layer/my_custom_layer.h + $ +) + +target_include_directories(VkLayer_MyCustomLayer PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/common +) + +target_link_libraries(VkLayer_MyCustomLayer PRIVATE + layersvt_common + Vulkan::Headers + Vulkan::LayerSettings +) + +# Configure output filename and definition file on Windows +if (WIN32) + target_sources(VkLayer_MyCustomLayer PRIVATE my_custom_layer/VkLayer_MyCustomLayer.def) +endif() + +list(APPEND TOOL_LAYERS "VkLayer_MyCustomLayer") +``` + +### Step 4: JSON Manifest and Windows DEF + +1. **`my_custom_layer/VkLayer_MyCustomLayer.json.in`**: + Standard Vulkan layer manifest template configured by CMake: + ```json + { + "file_format_version": "1.2.0", + "layer": { + "name": "VK_LAYER_GOOGLE_MyCustomLayer", + "type": "GLOBAL", + "library_path": "@JSON_LIBRARY_PATH@", + "api_version": "1.3.0", + "implementation_version": "1", + "description": "Google Vulkan MyCustomLayer" + } + } + ``` +2. **`my_custom_layer/VkLayer_MyCustomLayer.def`** (Windows): + Export entry point symbols: + ```def + LIBRARY VkLayer_MyCustomLayer + EXPORTS + vkGetInstanceProcAddr + vkGetDeviceProcAddr + vkNegotiateLoaderLayerInterfaceVersion + vkEnumerateInstanceExtensionProperties + vkEnumerateInstanceLayerProperties + vkEnumerateDeviceExtensionProperties + vkEnumerateDeviceLayerProperties + ``` + +### Step 5: Add Unit Tests (`layersvt/test/test_mycustomlayer.cpp`) + +Write unit tests using GoogleTest and the test framework. To keep tests hermetic and restore clean state across fixtures, reset the global layer instance: + +```cpp +#include +#include "common/layer_base_test_peer.h" +#include "layer_test_helper.h" +#include "my_custom_layer/my_custom_layer.h" + +namespace layersvt { + +class MyCustomLayerTest : public ::testing::Test { + protected: + void SetUp() override { + // Re-create layer to restore pristine state + // (destructor cleans up tracker and dispatch table entries) + layer_test::ResetLayer(); + } +}; + +TEST_F(MyCustomLayerTest, ManifestValidation) { + const LayerManifest* manifest = LayerBaseTestPeer::GetLayerManifest(MyCustomLayer::Get()); + ASSERT_NE(manifest, nullptr); + EXPECT_STREQ(manifest->layer_name, "VK_LAYER_GOOGLE_MyCustomLayer"); +} + +} // namespace layersvt +``` + +Register the test in `layersvt/test/CMakeLists.txt`: +```cmake +LayerTest(MyCustomLayer) +target_sources(test_MyCustomLayer_layer PRIVATE ../my_custom_layer/my_custom_layer.cpp) +target_link_libraries(test_MyCustomLayer_layer layersvt_common) +``` +> **Note**: +> - Do **not** link `layersvt_entrypoints` into test executables that link `Vulkan::Loader`; tests link directly to `layersvt_common` and invoke `LayerBase` APIs via `LayerBaseTestPeer` to avoid symbol collisions with `Vulkan::Loader`. + +--- + +## 3. Best Practices & Conventions + +1. **Do Not Reimplement Common Dispatch Boilerplate**: + Never manually parse `VkLayerInstanceCreateInfo` / `VkLayerDeviceCreateInfo` link chains or allocate raw dispatch tables. `LayerBase` automatically unwraps loader chains, initializes `DispatchTableManager`, and tracks physical devices. +2. **Use `DispatchDownstream` for Downstream Forwarding**: + Use `DispatchDownstream<&VkuDeviceDispatchTable::CmdDraw>(...)` to invoke the next layer or driver. It automatically deduces instance vs. device dispatch tables from the member pointer at compile time and asserts that tables and function pointers are non-null. +3. **Null Handle Destruction is Safe**: + `LayerBase::DestroyInstance` and `LayerBase::DestroyDevice` immediately return on `VK_NULL_HANDLE` per Vulkan specification (Section 2.7), bypassing downstream dispatch and virtual hooks. Virtual `PreDestroy*` hooks are guaranteed to receive only valid non-null handles. +4. **Tool Properties Downstream Initialization**: + When querying downstream tooling properties in custom commands, always initialize `tool.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT` and `tool.pNext = nullptr` on each array element before passing buffers to downstream functions to satisfy Vulkan VUIDs. +5. **Thread Safety**: + `DispatchTableManager` is fully synchronized via mutexes. Layer-specific global states must similarly protect their own internal maps. Avoid recursive locks across downstream dispatch invocations. diff --git a/layersvt/common/dispatch_downstream.h b/layersvt/common/dispatch_downstream.h new file mode 100644 index 0000000000..5ccac86531 --- /dev/null +++ b/layersvt/common/dispatch_downstream.h @@ -0,0 +1,93 @@ +/* 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)); + } +} + +} // namespace layersvt diff --git a/layersvt/common/dispatch_table_manager.cpp b/layersvt/common/dispatch_table_manager.cpp new file mode 100644 index 0000000000..9e1c91cbb4 --- /dev/null +++ b/layersvt/common/dispatch_table_manager.cpp @@ -0,0 +1,152 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dispatch_table_manager.h" +#include +#include +#include +#include + +namespace layersvt { + +VkuInstanceDispatchTable* DispatchTableManager::InitInstanceTable(VkInstance instance, + PFN_vkGetInstanceProcAddr get_instance_proc_addr) { + assert(instance != VK_NULL_HANDLE); + assert(get_instance_proc_addr != nullptr); + + Key key = GetDispatchKey(instance); + std::lock_guard lock(instance_mutex_); + 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 { + if (instance == VK_NULL_HANDLE) { + return nullptr; + } + Key key = GetDispatchKey(instance); + std::lock_guard lock(instance_mutex_); + auto table_iterator = instances_.find(key); + if (table_iterator != instances_.end()) { + return const_cast(&table_iterator->second.table); + } + 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 = instances_.find(instance_key); + if (table_iterator != instances_.end()) { + return const_cast(&table_iterator->second.table); + } + } + return nullptr; +} + +void DispatchTableManager::DestroyInstanceTable(Key key) { + assert(key != Key{}); + std::lock_guard lock(instance_mutex_); + 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; }); + instances_.erase(iterator); + } +} + +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); + + Key key = GetDispatchKey(device); + std::lock_guard lock(device_mutex_); + 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 { + if (object == nullptr) { + return nullptr; + } + Key key = GetDispatchKey(object); + std::lock_guard lock(device_mutex_); + auto table_iterator = device_entries_.find(key); + if (table_iterator != device_entries_.end()) { + return const_cast(&table_iterator->second.table); + } + return nullptr; +} + +void DispatchTableManager::DestroyDeviceTable(Key key) { + assert(key != Key{}); + std::lock_guard lock(device_mutex_); + device_entries_.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_); + 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 = device_entries_.find(key); + if (callback_iterator != device_entries_.end()) { + return callback_iterator->second.loader_callback; + } + 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..0002a987f3 --- /dev/null +++ b/layersvt/common/dispatch_table_manager.h @@ -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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace layersvt { + +/** + * Thread-safe manager for Vulkan instance and device dispatch tables and physical device tracking. + * + * Manages dispatch tables keyed by handle dispatch key, maintains associations between + * physical devices and parent instances, and tracks loader data callbacks + * (VK_LOADER_DATA_CALLBACK) to initialize dispatchable objects created internally. + */ +class DispatchTableManager final { + public: + DispatchTableManager() = default; + ~DispatchTableManager() = default; + + enum class Key : uintptr_t {}; + + /** + * Returns the dispatch key (first pointer-sized word) for a dispatchable Vulkan handle. + */ + [[nodiscard]] static Key GetDispatchKey(const void* object) noexcept { + assert(object != nullptr); + return static_cast(reinterpret_cast(*reinterpret_cast(object))); + } + + // Instance dispatch tables + + /** + * Initializes and stores an instance dispatch table using downstream vkGetInstanceProcAddr. + * Returns a non-null pointer to the stored dispatch table. + */ + VkuInstanceDispatchTable* InitInstanceTable(VkInstance instance, PFN_vkGetInstanceProcAddr get_instance_proc_addr); + + /** + * Looks up the instance dispatch table for a given instance handle. + * Returns a pointer to the stored table on success, or nullptr if not registered. + */ + [[nodiscard]] VkuInstanceDispatchTable* GetInstanceDispatchTable(VkInstance instance) const; + + /** + * Looks up the instance dispatch table for a given physical device handle. + * Resolves the parent instance and returns a pointer to its dispatch table on success, or nullptr if unregistered. + */ + [[nodiscard]] VkuInstanceDispatchTable* GetInstanceDispatchTable(VkPhysicalDevice physical_device) const; + + /** + * 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 multiple physical device handles with their parent VkInstance in a single atomic lock. + */ + void RegisterPhysicalDevices(const VkPhysicalDevice* physical_devices, uint32_t count, VkInstance instance); + + /** + * Retrieves the VkInstance associated with a physical device. + * Returns the parent VkInstance on success, or VK_NULL_HANDLE if not registered. + */ + [[nodiscard]] VkInstance GetVkInstance(VkPhysicalDevice physical_device) const; + + // Device dispatch tables + + /** + * Initializes and stores a device dispatch table using downstream vkGetDeviceProcAddr. + * Returns a non-null pointer to the stored dispatch table. + */ + VkuDeviceDispatchTable* InitDeviceTable(VkDevice device, PFN_vkGetDeviceProcAddr get_device_proc_addr); + + /** + * Looks up the device dispatch table for a dispatchable object. + * Returns a pointer to the stored table on success, or nullptr if object is null or unregistered. + */ + [[nodiscard]] VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object) const; + + /** + * Destroys the device dispatch table and loader callback for the given dispatch key. + * Callers should capture the Key beforehand via GetDispatchKey(...) before + * downstream vkDestroyDevice invalidates the handle. + */ + void DestroyDeviceTable(Key key); + + // Loader data callbacks (from VK_LOADER_DATA_CALLBACK) + + /** + * Registers the vkSetDeviceLoaderData callback for a logical device. + */ + void SetDeviceLoaderDataCallback(VkDevice device, PFN_vkSetDeviceLoaderData callback); + + /** + * Retrieves the vkSetDeviceLoaderData callback for a logical device. + * Returns the registered callback on success, or nullptr if unset. + */ + [[nodiscard]] PFN_vkSetDeviceLoaderData GetDeviceLoaderDataCallback(VkDevice device) const; + + private: + 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 instances_; + std::unordered_map physical_device_to_instance_map_; + + mutable std::mutex device_mutex_; + std::unordered_map device_entries_; +}; + +} // namespace layersvt diff --git a/layersvt/common/layer_base.cpp b/layersvt/common/layer_base.cpp new file mode 100644 index 0000000000..bc631649c7 --- /dev/null +++ b/layersvt/common/layer_base.cpp @@ -0,0 +1,553 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "layer_base.h" +#include "dispatch_downstream.h" +#include "dispatch_table_manager.h" +#include "layer_manifest.h" + +#include +#include +#include + +namespace layersvt { + +namespace { + +inline void AssertLayerInitialized() { + assert(LayerBase::Get() != nullptr && "LayerBase instance must be initialized"); +} + +bool IsGlobalCommand(const char* command_name) { + return std::strcmp(command_name, "vkGetInstanceProcAddr") == 0 || + std::strcmp(command_name, "vkCreateInstance") == 0 || + std::strcmp(command_name, "vkEnumerateInstanceExtensionProperties") == 0 || + std::strcmp(command_name, "vkEnumerateInstanceLayerProperties") == 0 || + std::strcmp(command_name, "vkEnumerateInstanceVersion") == 0; +} + +VkLayerInstanceCreateInfo* GetChainInfo(const VkInstanceCreateInfo& create_info, VkLayerFunction function) { + auto* chain_info = static_cast(create_info.pNext); + while (chain_info && (chain_info->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO || chain_info->function != function)) { + chain_info = static_cast(chain_info->pNext); + } + return const_cast(chain_info); +} + +VkLayerDeviceCreateInfo* GetChainInfo(const VkDeviceCreateInfo& create_info, VkLayerFunction function) { + auto* chain_info = static_cast(create_info.pNext); + while (chain_info && (chain_info->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO || chain_info->function != function)) { + chain_info = static_cast(chain_info->pNext); + } + return const_cast(chain_info); +} + +template +VkResult CopyEnumerationProperties(const std::vector& items, uint32_t* property_count, T* properties) { + assert(property_count != nullptr); + + const uint32_t total = static_cast(items.size()); + if (properties == nullptr) { + *property_count = total; + return VK_SUCCESS; + } + + const uint32_t copy_count = std::min(*property_count, total); + if (copy_count > 0) { + std::copy_n(items.begin(), copy_count, properties); + } + *property_count = copy_count; + + return (copy_count < total) ? VK_INCOMPLETE : VK_SUCCESS; +} + +} // 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_vkSetDeviceLoaderData LayerBase::GetDeviceLoaderDataCallback(VkDevice device) { + AssertLayerInitialized(); + return layer_->dispatch_table_manager_.GetDeviceLoaderDataCallback(device); +} + +VkResult LayerBase::CreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, + VkInstance* instance) { + if (!create_info || !instance) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + AssertLayerInitialized(); + + VkInstanceCreateInfo modified_create_info = *create_info; + VkLayerInstanceCreateInfo* chain_info = GetChainInfo(modified_create_info, VK_LAYER_LINK_INFO); + if (!chain_info || !chain_info->u.pLayerInfo || !chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + PFN_vkGetInstanceProcAddr get_instance_proc_addr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + auto create_instance = reinterpret_cast(get_instance_proc_addr(VK_NULL_HANDLE, "vkCreateInstance")); + if (create_instance == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + LayerBase* layer = Get(); + layer->PreCreateInstance(&modified_create_info, allocator); + + chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; + VkResult result = create_instance(&modified_create_info, allocator, instance); + if (result == VK_SUCCESS) { + layer->dispatch_table_manager_.InitInstanceTable(*instance, get_instance_proc_addr); + layer->PostCreateInstance(*instance, &modified_create_info, allocator); + } + return result; +} + +void LayerBase::DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) { + if (instance == VK_NULL_HANDLE) { + return; + } + + AssertLayerInitialized(); + LayerBase* layer = Get(); + layer->PreDestroyInstance(instance, allocator); + + auto key = DispatchTableManager::GetDispatchKey(instance); + DispatchDownstream<&VkuInstanceDispatchTable::DestroyInstance>(instance, allocator); + layer->dispatch_table_manager_.DestroyInstanceTable(key); +} + +VkResult LayerBase::EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices) { + VkResult result = DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>( + VK_ERROR_INITIALIZATION_FAILED, instance, physical_device_count, physical_devices); + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && physical_device_count != nullptr && + physical_devices != nullptr) { + LayerBase* layer = Get(); + layer->dispatch_table_manager_.RegisterPhysicalDevices(physical_devices, *physical_device_count, instance); + } + return result; +} + +VkResult LayerBase::EnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties) { + VkResult result = DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDeviceGroups>( + [&] { + return DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDeviceGroupsKHR>( + VK_ERROR_INITIALIZATION_FAILED, instance, physical_device_group_count, + physical_device_group_properties); + }, + instance, physical_device_group_count, physical_device_group_properties); + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && physical_device_group_count != nullptr && + physical_device_group_properties != nullptr) { + LayerBase* layer = Get(); + for (uint32_t i = 0; i < *physical_device_group_count; ++i) { + assert(physical_device_group_properties[i].physicalDeviceCount <= VK_MAX_DEVICE_GROUP_SIZE); + const uint32_t device_count = physical_device_group_properties[i].physicalDeviceCount; + layer->dispatch_table_manager_.RegisterPhysicalDevices(physical_device_group_properties[i].physicalDevices, + device_count, instance); + } + } + return result; +} + +VkResult LayerBase::CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device) { + if (physical_device == VK_NULL_HANDLE || !create_info || !device) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkInstance instance = GetVkInstance(physical_device); + if (instance == VK_NULL_HANDLE) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkDeviceCreateInfo modified_create_info = *create_info; + VkLayerDeviceCreateInfo* chain_info = GetChainInfo(modified_create_info, VK_LAYER_LINK_INFO); + if (!chain_info || !chain_info->u.pLayerInfo || !chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr || + !chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + PFN_vkGetInstanceProcAddr get_instance_proc_addr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr get_device_proc_addr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; + + auto create_device = reinterpret_cast(get_instance_proc_addr(instance, "vkCreateDevice")); + if (create_device == nullptr) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + // Check for loader callback to initialize dispatchable handles created internally by the layer + PFN_vkSetDeviceLoaderData loader_callback = nullptr; + VkLayerDeviceCreateInfo* callback_info = GetChainInfo(modified_create_info, VK_LOADER_DATA_CALLBACK); + if (callback_info && callback_info->u.pfnSetDeviceLoaderData) { + loader_callback = callback_info->u.pfnSetDeviceLoaderData; + } + + LayerBase* layer = Get(); + layer->PreCreateDevice(physical_device, &modified_create_info, allocator); + + chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; + + VkResult result = create_device(physical_device, &modified_create_info, allocator, device); + if (result == VK_SUCCESS) { + layer->dispatch_table_manager_.InitDeviceTable(*device, get_device_proc_addr); + if (loader_callback) { + layer->dispatch_table_manager_.SetDeviceLoaderDataCallback(*device, loader_callback); + } + layer->PostCreateDevice(*device, physical_device, &modified_create_info, allocator); + } + return result; +} + +void LayerBase::DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) { + if (device == VK_NULL_HANDLE) { + return; + } + + AssertLayerInitialized(); + LayerBase* layer = Get(); + layer->PreDestroyDevice(device, allocator); + + auto key = DispatchTableManager::GetDispatchKey(device); + DispatchDownstream<&VkuDeviceDispatchTable::DestroyDevice>(device, allocator); + layer->dispatch_table_manager_.DestroyDeviceTable(key); +} + +VkResult LayerBase::EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) { + assert(property_count != nullptr); + AssertLayerInitialized(); + + LayerBase* layer = Get(); + const LayerManifest* manifest = layer->GetLayerManifest(); + 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) { + *property_count = 0; + return VK_ERROR_LAYER_NOT_PRESENT; + } + + std::vector extensions = manifest->instance_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(); + assert(manifest != nullptr); + + 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) { + assert(property_count != nullptr); + AssertLayerInitialized(); + + LayerBase* layer = Get(); + const LayerManifest* manifest = layer->GetLayerManifest(); + 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) { + std::vector extensions = manifest->device_extensions; + layer->ProcessDeviceExtensions(physical_device, layer_name, extensions); + 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) { + 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)); + } + } + + 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) { + 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_function = table->GetPhysicalDeviceToolPropertiesEXT; + if (!downstream_function) { + downstream_function = table->GetPhysicalDeviceToolProperties; + } + } + } + + 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->tool_properties.has_value()) { + tools.push_back(*manifest->tool_properties); + } + + return CopyEnumerationProperties(tools, tool_count, tool_properties); +} + +void LayerBase::ProcessDeviceExtensions(VkPhysicalDevice, const char*, std::vector&) const {} + +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*) {} + +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; } + +PFN_vkVoidFunction LayerBase::GetKnownInstanceCommand(const char* command_name) { + assert(command_name != nullptr); + AssertLayerInitialized(); + LayerBase* layer = Get(); + PFN_vkVoidFunction custom_command = layer->GetLayerInstanceCommand(command_name); + if (custom_command != nullptr) { + return custom_command; + } + + if (std::strcmp(command_name, "vkGetInstanceProcAddr") == 0) { + return reinterpret_cast(GetInstanceProcAddr); + } + if (std::strcmp(command_name, "vkCreateInstance") == 0) { + return reinterpret_cast(CreateInstance); + } + if (std::strcmp(command_name, "vkDestroyInstance") == 0) { + return reinterpret_cast(DestroyInstance); + } + if (std::strcmp(command_name, "vkEnumeratePhysicalDevices") == 0) { + return reinterpret_cast(EnumeratePhysicalDevices); + } + if (std::strcmp(command_name, "vkEnumeratePhysicalDeviceGroups") == 0 || + std::strcmp(command_name, "vkEnumeratePhysicalDeviceGroupsKHR") == 0) { + return reinterpret_cast(EnumeratePhysicalDeviceGroups); + } + if (std::strcmp(command_name, "vkCreateDevice") == 0) { + return reinterpret_cast(CreateDevice); + } + if (std::strcmp(command_name, "vkEnumerateInstanceExtensionProperties") == 0) { + return reinterpret_cast(EnumerateInstanceExtensionProperties); + } + if (std::strcmp(command_name, "vkEnumerateInstanceLayerProperties") == 0) { + return reinterpret_cast(EnumerateInstanceLayerProperties); + } + if (std::strcmp(command_name, "vkEnumerateDeviceLayerProperties") == 0) { + return reinterpret_cast(EnumerateDeviceLayerProperties); + } + if (std::strcmp(command_name, "vkEnumerateDeviceExtensionProperties") == 0) { + return reinterpret_cast(EnumerateDeviceExtensionProperties); + } + if (std::strcmp(command_name, "vkGetPhysicalDeviceToolPropertiesEXT") == 0 || + std::strcmp(command_name, "vkGetPhysicalDeviceToolProperties") == 0) { + const LayerManifest* manifest = layer->GetLayerManifest(); + if (manifest != nullptr && manifest->tool_properties.has_value()) { + return reinterpret_cast(GetPhysicalDeviceToolProperties); + } + } + return nullptr; +} + +PFN_vkVoidFunction LayerBase::GetKnownDeviceCommand(const char* command_name) { + assert(command_name != nullptr); + AssertLayerInitialized(); + LayerBase* layer = Get(); + PFN_vkVoidFunction custom_command = layer->GetLayerDeviceCommand(command_name); + if (custom_command != nullptr) { + return custom_command; + } + + if (std::strcmp(command_name, "vkGetDeviceProcAddr") == 0) { + return reinterpret_cast(GetDeviceProcAddr); + } + if (std::strcmp(command_name, "vkDestroyDevice") == 0) { + return reinterpret_cast(DestroyDevice); + } + return nullptr; +} + +PFN_vkVoidFunction VKAPI_CALL LayerBase::GetInstanceProcAddr(VkInstance instance, const char* command_name) { + if (!command_name) { + return nullptr; + } + + AssertLayerInitialized(); + + if (instance == VK_NULL_HANDLE) { + if (!IsGlobalCommand(command_name)) { + return nullptr; + } + return GetKnownInstanceCommand(command_name); + } + + PFN_vkVoidFunction command = GetKnownInstanceCommand(command_name); + if (command != nullptr) { + return command; + } + + command = GetKnownDeviceCommand(command_name); + if (command != nullptr) { + return command; + } + + return DispatchDownstreamOr<&VkuInstanceDispatchTable::GetInstanceProcAddr>(nullptr, instance, command_name); +} + +PFN_vkVoidFunction VKAPI_CALL LayerBase::GetDeviceProcAddr(VkDevice device, const char* command_name) { + if (!command_name) { + return nullptr; + } + + if (device == VK_NULL_HANDLE) { + return nullptr; + } + + AssertLayerInitialized(); + PFN_vkVoidFunction command = GetKnownDeviceCommand(command_name); + if (command != nullptr) { + return command; + } + + return DispatchDownstreamOr<&VkuDeviceDispatchTable::GetDeviceProcAddr>(nullptr, device, command_name); +} + +} // namespace layersvt diff --git a/layersvt/common/layer_base.h b/layersvt/common/layer_base.h new file mode 100644 index 0000000000..311545c151 --- /dev/null +++ b/layersvt/common/layer_base.h @@ -0,0 +1,232 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "dispatch_table_manager.h" +#include +#include +#include +#include +#include + +namespace layersvt { + +struct LayerManifest; +class LayerBaseTestPeer; + +/** + * Base class providing common infrastructure for Vulkan layer implementations. + * + * Implements the Template Method pattern for Vulkan API routing, centralizing loader + * negotiation, dispatch table tracking, handle mapping, and property enumeration: + * + * - Singleton Lifecycle: A single LayerBase instance is created at library load time + * (typically as a file-scope static object in the layer's translation unit). + * - Thread Safety: Internal registries (DispatchTableManager) are thread-safe. + * Overridden hooks called concurrently by Vulkan applications must maintain their + * own thread safety for layer-specific state. + * - Command Routing: Custom commands return function pointers via GetLayerInstanceCommand + * and GetLayerDeviceCommand; unhandled commands route to downstream dispatch tables. + * - Property Enumeration: Serves layer extensions and tool properties automatically + * from GetLayerManifest(), merging layer properties with downstream capabilities. + * - Lifecycle Hooks: PreCreate* / PostCreate* / PreDestroy* hooks bracket instance and + * device creation and destruction. PreDestroy* hooks are guaranteed non-null handles + * (null handle calls return immediately per Vulkan Spec 2.7). No PostDestroy* hooks + * exist because downstream destruction frees and invalidates handles before returning. + * + * For authoring guides, CMake build setup, and downstream dispatch examples, + * see layersvt/common/README.md. + */ +class LayerBase { + public: + LayerBase(); + virtual ~LayerBase(); + + LayerBase(const LayerBase&) = delete; + LayerBase& operator=(const LayerBase&) = delete; + LayerBase(LayerBase&&) = delete; + LayerBase& operator=(LayerBase&&) = delete; + + // Layer singleton management + /** + * Retrieves the currently active LayerBase singleton instance. + * Returns a pointer to the active LayerBase instance, or nullptr if no layer is initialized. + */ + [[nodiscard]] static LayerBase* Get() noexcept { return layer_; } + + /** + * Retrieves the parent VkInstance associated with a physical device. + * Returns the parent VkInstance on success, or VK_NULL_HANDLE if unregistered or invalid. + */ + [[nodiscard]] static VkInstance GetVkInstance(VkPhysicalDevice physical_device); + + /** + * Retrieves the loader data callback for initializing dispatchable handles created by layers. + * Returns the registered PFN_vkSetDeviceLoaderData callback on success, or nullptr if unset. + */ + [[nodiscard]] static PFN_vkSetDeviceLoaderData GetDeviceLoaderDataCallback(VkDevice device); + + protected: + /** + * Override to provide the layer's metadata, supported extensions, and tool properties. + * Enables automatic handling of layer and extension property enumeration queries. + */ + [[nodiscard]] virtual const LayerManifest* GetLayerManifest() const; + + // Extension hooks + + /** + * Customizes or filters device extensions during vkEnumerateDeviceExtensionProperties. + */ + virtual void ProcessDeviceExtensions(VkPhysicalDevice physical_device, const char* layer_name, + std::vector& extensions) const; + + // Layer-specific command intercepts + + /** + * Override to intercept instance-level Vulkan commands. + * + * Returns a function pointer to the hook implementation, or nullptr to fall back + * to core Vulkan intercepts (e.g. vkCreateInstance, vkDestroyInstance, + * vkEnumeratePhysicalDevices) or downstream dispatch. + */ + virtual PFN_vkVoidFunction GetLayerInstanceCommand(const char* command_name); + + /** + * Override to intercept device-level Vulkan commands. + * + * Returns a function pointer to the hook implementation, or nullptr to fall back + * to core Vulkan intercepts (e.g. vkDestroyDevice) or downstream dispatch. + */ + virtual PFN_vkVoidFunction GetLayerDeviceCommand(const char* command_name); + + // Instance and device lifecycle hooks (template method pattern) + + /** + * Hook called immediately before vkCreateInstance dispatches downstream. + * Allows inspecting or modifying create_info (e.g. injecting extensions or pNext structs). + */ + virtual void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately after vkCreateInstance succeeds downstream. + * Use to initialize instance state, settings, or tracing. The instance dispatch table is ready. + */ + virtual void PostCreateInstance(VkInstance instance, const VkInstanceCreateInfo* create_info, + const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately before vkDestroyInstance dispatches downstream. + * Guaranteed to receive a valid, non-null VkInstance handle. + */ + virtual void PreDestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately before vkCreateDevice dispatches downstream. + * Allows inspecting or modifying create_info (e.g. injecting device extensions or pNext structs). + */ + virtual void PreCreateDevice(VkPhysicalDevice physical_device, VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately after vkCreateDevice succeeds downstream. + * Use to initialize per-device state or allocate layer resources. The device dispatch table is ready. + */ + virtual void PostCreateDevice(VkDevice device, VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator); + + /** + * Hook called immediately before vkDestroyDevice dispatches downstream. + * Guaranteed to receive a valid, non-null VkDevice handle. + */ + virtual void PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); + + private: + [[nodiscard]] static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkInstance instance); + [[nodiscard]] static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkPhysicalDevice physical_device); + [[nodiscard]] static VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object); + // Internal subsystems + // Managed automatically by LayerBase; derived layers access downstream + // dispatch via DispatchDownstream instead of querying these tables directly. + DispatchTableManager dispatch_table_manager_; + + static inline LayerBase* layer_ = nullptr; + + // Exported Vulkan layer entry points (implemented in layer_entrypoints.cpp) +#if defined(_WIN32) + friend VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL ::vkGetInstanceProcAddr(VkInstance instance, const char* command_name); + friend VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL ::vkGetDeviceProcAddr(VkDevice device, const char* command_name); + friend VKAPI_ATTR VkResult VKAPI_CALL ::vkEnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL ::vkEnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL ::vkEnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL ::vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, + VkExtensionProperties* properties); +#else + friend VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (::vkGetInstanceProcAddr)(VkInstance instance, const char* command_name); + friend VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (::vkGetDeviceProcAddr)(VkDevice device, const char* command_name); + friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateInstanceLayerProperties)(uint32_t* property_count, VkLayerProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateInstanceExtensionProperties)(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateDeviceLayerProperties)(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties); + friend VKAPI_ATTR VkResult VKAPI_CALL (::vkEnumerateDeviceExtensionProperties)(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, + VkExtensionProperties* properties); +#endif + + friend class LayerBaseTestPeer; + + template + friend auto DispatchDownstream(Handle handle, Args&&... args); + + template + friend auto DispatchDownstreamOr(Fallback&& fallback, Handle handle, Args&&... args); + + // Vulkan intercept commands (static C-compatible functions) + + static PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance, const char* command_name); + static PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice device, const char* command_name); + + static VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, + VkInstance* instance); + static void VKAPI_CALL DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator); + + static VkResult VKAPI_CALL EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices); + static VkResult VKAPI_CALL EnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties); + static VkResult VKAPI_CALL CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device); + static void VKAPI_CALL DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); + + static VkResult VKAPI_CALL EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties); + static VkResult VKAPI_CALL EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties); + static VkResult VKAPI_CALL EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties); + static VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device, const char* layer_name, + uint32_t* property_count, VkExtensionProperties* properties); + static VkResult VKAPI_CALL GetPhysicalDeviceToolProperties(VkPhysicalDevice physical_device, uint32_t* tool_count, + VkPhysicalDeviceToolPropertiesEXT* tool_properties); + + static PFN_vkVoidFunction GetKnownInstanceCommand(const char* command_name); + static PFN_vkVoidFunction GetKnownDeviceCommand(const char* command_name); +}; + +} // namespace layersvt diff --git a/layersvt/common/layer_entrypoints.cpp b/layersvt/common/layer_entrypoints.cpp new file mode 100644 index 0000000000..a5dab26ab5 --- /dev/null +++ b/layersvt/common/layer_entrypoints.cpp @@ -0,0 +1,89 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "layer_base.h" + +#include +#include +#include + +#ifndef VK_LAYER_EXPORT +#if defined(_WIN32) +// On Windows, layer DLL entry points are exported via .def module definition files. +// Omitting __declspec(dllexport) prevents MSVC C2375 linkage conflicts with Vulkan SDK headers. +#define VK_LAYER_EXPORT +#else +#define VK_LAYER_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +extern "C" { + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkNegotiateLoaderLayerInterfaceVersion( + VkNegotiateLayerInterface* version_interface) { + assert(version_interface != nullptr); + assert(version_interface->sType == LAYER_NEGOTIATE_INTERFACE_STRUCT); + + if (version_interface->loaderLayerInterfaceVersion >= 2) { + version_interface->loaderLayerInterfaceVersion = 2; + version_interface->pfnGetInstanceProcAddr = vkGetInstanceProcAddr; + version_interface->pfnGetDeviceProcAddr = vkGetDeviceProcAddr; + version_interface->pfnGetPhysicalDeviceProcAddr = nullptr; + return VK_SUCCESS; + } + + if (version_interface->loaderLayerInterfaceVersion == 1) { + version_interface->loaderLayerInterfaceVersion = 1; + return VK_SUCCESS; + } + + return VK_ERROR_INITIALIZATION_FAILED; +} + +VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr( + VkInstance instance, const char* command_name) { + return layersvt::LayerBase::GetInstanceProcAddr(instance, command_name); +} + +VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr( + VkDevice device, const char* command_name) { + return layersvt::LayerBase::GetDeviceProcAddr(device, command_name); +} + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties( + uint32_t* property_count, VkLayerProperties* properties) { + return layersvt::LayerBase::EnumerateInstanceLayerProperties(property_count, properties); +} + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties( + const char* layer_name, uint32_t* property_count, VkExtensionProperties* properties) { + return layersvt::LayerBase::EnumerateInstanceExtensionProperties( + layer_name, property_count, properties); +} + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( + VkPhysicalDevice physical_device, uint32_t* property_count, VkLayerProperties* properties) { + return layersvt::LayerBase::EnumerateDeviceLayerProperties( + physical_device, property_count, properties); +} + +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties( + VkPhysicalDevice physical_device, const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) { + return layersvt::LayerBase::EnumerateDeviceExtensionProperties( + physical_device, layer_name, property_count, properties); +} + +} // extern "C" diff --git a/layersvt/layer_keep_alive.cpp b/layersvt/common/layer_keep_alive.cpp similarity index 54% rename from layersvt/layer_keep_alive.cpp rename to layersvt/common/layer_keep_alive.cpp index c375f0b637..616adee880 100644 --- a/layersvt/layer_keep_alive.cpp +++ b/layersvt/common/layer_keep_alive.cpp @@ -17,28 +17,19 @@ #if defined(__ANDROID__) #include +#include namespace { -// Anonymous namespace function is NOT exported, keeping it local to each shared object. -// We use a constructor attribute to trigger it when the library is loaded/opened. -void layer_keep_alive_func(); - -class KeepAlive { - public: - KeepAlive() { - Dl_info info; - // Attempt to find the filename of the library containing this code. - if (dladdr((void*)&layer_keep_alive_func, &info)) { - // Re-open with RTLD_NODELETE to force the library to stay resident. - dlopen(info.dli_fname, RTLD_NODELETE); - } +// Function with constructor attribute executes during library load. +// Re-open with RTLD_NODELETE to ensure the layer shared library stays resident in process memory +// across Vulkan loader queries. +__attribute__((constructor)) void LayerKeepAlive() { + Dl_info info{}; + if (dladdr(reinterpret_cast(&LayerKeepAlive), &info) != 0 && info.dli_fname != nullptr && + info.dli_fname[0] != '\0' && std::strstr(info.dli_fname, ".so") != nullptr) { + (void)dlopen(info.dli_fname, RTLD_NOW | RTLD_NODELETE); } -}; - -__attribute__((constructor)) void layer_keep_alive_func() { - static KeepAlive k; - (void)k; } } // namespace diff --git a/layersvt/common/layer_manifest.cpp b/layersvt/common/layer_manifest.cpp new file mode 100644 index 0000000000..a80542f279 --- /dev/null +++ b/layersvt/common/layer_manifest.cpp @@ -0,0 +1,34 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "layer_manifest.h" +#include +#include +namespace layersvt { + +VkLayerProperties LayerManifest::GetLayerProperties() const noexcept { + assert(layer_name != nullptr); + assert(description != nullptr); + VkLayerProperties properties{}; + std::strncpy(properties.layerName, layer_name, sizeof(properties.layerName) - 1); + properties.layerName[sizeof(properties.layerName) - 1] = '\0'; + std::strncpy(properties.description, description, sizeof(properties.description) - 1); + properties.description[sizeof(properties.description) - 1] = '\0'; + properties.specVersion = spec_version; + properties.implementationVersion = implementation_version; + return properties; +} + +} // namespace layersvt diff --git a/layersvt/common/layer_manifest.h b/layersvt/common/layer_manifest.h new file mode 100644 index 0000000000..88fea33aa2 --- /dev/null +++ b/layersvt/common/layer_manifest.h @@ -0,0 +1,45 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace layersvt { + +/** + * Declarative metadata describing a Vulkan layer's identity, versions, + * exposed extensions, and optional tooling properties. + */ +struct LayerManifest { + const char* layer_name = ""; + const char* description = ""; + uint32_t spec_version = VK_API_VERSION_1_3; + uint32_t implementation_version = 1; + std::vector instance_extensions; + std::vector device_extensions; + std::optional tool_properties; + + /** + * Converts manifest metadata into a standard VkLayerProperties structure. + * Returns the populated VkLayerProperties instance. + */ + [[nodiscard]] VkLayerProperties GetLayerProperties() const noexcept; +}; + +} // namespace layersvt diff --git a/layersvt/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..8539b04926 100644 --- a/layersvt/debug_marker/debug_marker.cpp +++ b/layersvt/debug_marker/debug_marker.cpp @@ -15,23 +15,54 @@ #include "debug_marker.h" #include "debug_marker_perfetto.h" +#include "debug_marker_handwritten_functions_vk_ext_debug_marker.h" +#include "debug_marker_handwritten_functions_vk_ext_debug_utils.h" #include "perfetto/perfetto.h" +#include -DebugMarker& DebugMarker::Get() { - static DebugMarker instance; - return instance; +namespace { +DebugMarker g_layer; +} // namespace + +DebugMarker::DebugMarker() = default; + +const layersvt::LayerManifest* DebugMarker::GetLayerManifest() const { + static const layersvt::LayerManifest manifest{ + .layer_name = "VK_LAYER_GOOGLE_DebugMarker", + .description = "layer: DebugMarker", + .spec_version = VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), + .implementation_version = VK_MAKE_VERSION(0, 1, 0), + .instance_extensions = + { + {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, + }, + .device_extensions = + { + {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, + }, + .tool_properties = std::nullopt, + }; + return &manifest; } -void DebugMarker::SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance) { - std::lock_guard lock(mutex_); - vk_instance_map_[phys_dev] = instance; +void DebugMarker::PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) { + (void)pCreateInfo; + (void)pAllocator; + static std::once_flag perfetto_initialization_flag; + std::call_once(perfetto_initialization_flag, []() { InitializeDebugMarkerPerfetto(); }); } -VkInstance DebugMarker::GetVkInstance(VkPhysicalDevice phys_dev) { +void DebugMarker::PreDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { + (void)pAllocator; 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; + 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) { @@ -72,11 +103,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 +110,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..d2ce7bcc67 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. @@ -45,19 +48,17 @@ * 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 +73,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 +80,20 @@ 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); + void PreCreateInstance(VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) override; /** - * @brief Retrieves the Vulkan instance associated with a given physical device. - * @param phys_dev The Vulkan physical device. - * @return The associated Vulkan instance. + * Lifecycle hook called before vkDestroyDevice to remove tracked names for destroyed objects. */ - VkInstance GetVkInstance(VkPhysicalDevice phys_dev); + 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; private: struct DebugObjectName { @@ -113,10 +108,6 @@ class DebugMarker { }; std::mutex mutex_; - /** - * @brief Maps a physical device handle to its corresponding Vulkan instance handle. - */ - std::unordered_map vk_instance_map_; /** * @brief Maps a pair of (object_type, object_handle) to its debug name information. * We use a pair as the key because handles are not guaranteed to be unique across different object types. diff --git a/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp b/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp deleted file mode 100644 index a320032500..0000000000 --- a/layersvt/debug_marker/debug_marker_handwritten_dispatch.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* Copyright (C) 2026 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "debug_marker_handwritten_functions.h" -#include "debug_marker_handwritten_functions_vk_ext_debug_marker.h" -#include "debug_marker_handwritten_functions_vk_ext_debug_utils.h" -#include "vk_layer_table.h" -#include - -extern "C" { - -static PFN_vkVoidFunction debug_marker_known_instance_functions(const char* pName) { - if (strcmp(pName, "vkGetInstanceProcAddr") == 0) return reinterpret_cast(vkGetInstanceProcAddr); - if (strcmp(pName, "vkCreateInstance") == 0) return reinterpret_cast(vkCreateInstance); - if (strcmp(pName, "vkDestroyInstance") == 0) return reinterpret_cast(vkDestroyInstance); - if (strcmp(pName, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast(vkEnumeratePhysicalDevices); - if (strcmp(pName, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast(vkEnumeratePhysicalDeviceGroups); - if (strcmp(pName, "vkEnumerateInstanceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateInstanceExtensionProperties); - if (strcmp(pName, "vkEnumerateInstanceLayerProperties") == 0) return reinterpret_cast(vkEnumerateInstanceLayerProperties); - if (strcmp(pName, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkCreateDebugUtilsMessengerEXT); - if (strcmp(pName, "vkDestroyDebugUtilsMessengerEXT") == 0) return reinterpret_cast(vkDestroyDebugUtilsMessengerEXT); - if (strcmp(pName, "vkSubmitDebugUtilsMessageEXT") == 0) return reinterpret_cast(vkSubmitDebugUtilsMessageEXT); - return nullptr; -} - -static PFN_vkVoidFunction debug_marker_known_device_functions(const char* pName) { - if (strcmp(pName, "vkGetDeviceProcAddr") == 0) return reinterpret_cast(vkGetDeviceProcAddr); - if (strcmp(pName, "vkCreateDevice") == 0) return reinterpret_cast(vkCreateDevice); - if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast(vkEnumerateDeviceLayerProperties); - if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); - - // VK_EXT_debug_marker - if (strcmp(pName, "vkCmdDebugMarkerBeginEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerBeginEXT); - if (strcmp(pName, "vkCmdDebugMarkerEndEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerEndEXT); - if (strcmp(pName, "vkCmdDebugMarkerInsertEXT") == 0) return reinterpret_cast(vkCmdDebugMarkerInsertEXT); - if (strcmp(pName, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectNameEXT); - if (strcmp(pName, "vkDebugMarkerSetObjectTagEXT") == 0) return reinterpret_cast(vkDebugMarkerSetObjectTagEXT); - - // VK_EXT_debug_utils - if (strcmp(pName, "vkCmdBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdBeginDebugUtilsLabelEXT); - if (strcmp(pName, "vkCmdEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdEndDebugUtilsLabelEXT); - if (strcmp(pName, "vkCmdInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkCmdInsertDebugUtilsLabelEXT); - if (strcmp(pName, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectNameEXT); - if (strcmp(pName, "vkSetDebugUtilsObjectTagEXT") == 0) return reinterpret_cast(vkSetDebugUtilsObjectTagEXT); - if (strcmp(pName, "vkQueueBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueBeginDebugUtilsLabelEXT); - if (strcmp(pName, "vkQueueEndDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueEndDebugUtilsLabelEXT); - if (strcmp(pName, "vkQueueInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast(vkQueueInsertDebugUtilsLabelEXT); - - return nullptr; -} - -EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* pName) { - PFN_vkVoidFunction func = debug_marker_known_instance_functions(pName); - if (func) { - return func; - } - - // If it's a device function, we can also return it here if we want to support GIPA for device functions. - func = debug_marker_known_device_functions(pName); - if (func) { - return func; - } - - if (instance == nullptr) { - return nullptr; - } - - auto table = instance_dispatch_table(instance); - if (table == NULL) { - return nullptr; - } - - if (table->GetInstanceProcAddr == NULL) { - return nullptr; - } - - return table->GetInstanceProcAddr(instance, pName); -} - -EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char* pName) { - PFN_vkVoidFunction func = debug_marker_known_device_functions(pName); - if (func) { - return func; - } - - if (device == nullptr) { - return nullptr; - } - - if (device_dispatch_table(device)->GetDeviceProcAddr == NULL) { - return nullptr; - } - - return device_dispatch_table(device)->GetDeviceProcAddr(device, pName); -} - -} // extern "C" diff --git a/layersvt/debug_marker/debug_marker_handwritten_functions.h b/layersvt/debug_marker/debug_marker_handwritten_functions.h deleted file mode 100644 index 18fe280c05..0000000000 --- a/layersvt/debug_marker/debug_marker_handwritten_functions.h +++ /dev/null @@ -1,250 +0,0 @@ -/* Copyright (C) 2026 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include "vk_layer_table.h" -#include "debug_marker.h" -#include "debug_marker_perfetto.h" - -// This file contains handwritten implementations for core Vulkan functions -// (instance/device creation and physical device enumeration) required for the layer's -// infrastructure and state management: -// -// - vkCreateInstance: Initializes Perfetto tracing, the instance dispatch table, and performs eager physical device enumeration. -// - vkEnumeratePhysicalDevices / vkEnumeratePhysicalDeviceGroups: Tracks the mapping -// between physical devices and instances to support dispatch table lookups. -// - vkCreateDevice: Initializes the device dispatch table for intercepted devices. -// -// Extension-specific functions (e.g., VK_EXT_debug_marker, VK_EXT_debug_utils) -// are located in separate dedicated header files. - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) - -#if defined(__GNUC__) && __GNUC__ >= 4 -#define EXPORT_FUNCTION __attribute__((visibility("default"))) -#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) -#define EXPORT_FUNCTION __attribute__((visibility("default"))) -#else -#define EXPORT_FUNCTION -#endif - -static std::once_flag g_perfetto_init_flag; - - -extern "C" { - -VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, - VkInstance* pInstance) { - std::call_once(g_perfetto_init_flag, []() { InitializeDebugMarkerPerfetto(); }); - - // Get the function pointer - VkLayerInstanceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); - assert(chain_info->u.pLayerInfo != 0); - PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; - assert(fpGetInstanceProcAddr != 0); - PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); - if (fpCreateInstance == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - // Call the function and create the dispatch table - chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; - VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance); - if (result == VK_SUCCESS) { - initInstanceTable(*pInstance, fpGetInstanceProcAddr); - - // Eagerly enumerate physical devices and map them to the instance. - // This ensures we have the mapping even if the app bypasses our enumeration hooks. - PFN_vkEnumeratePhysicalDevices fpEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)fpGetInstanceProcAddr(*pInstance, "vkEnumeratePhysicalDevices"); - if (fpEnumeratePhysicalDevices) { - uint32_t count = 0; - fpEnumeratePhysicalDevices(*pInstance, &count, nullptr); - if (count > 0) { - std::vector devices(count); - fpEnumeratePhysicalDevices(*pInstance, &count, devices.data()); - for (uint32_t i = 0; i < count; ++i) { - DebugMarker::Get().SetVkInstance(devices[i], *pInstance); - } - } - } - } - - return result; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) { - if (instance_dispatch_table(instance)->EnumeratePhysicalDevices == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices); - - if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pPhysicalDevices != nullptr) { - for (uint32_t i = 0; i < *pPhysicalDeviceCount; ++i) { - DebugMarker::Get().SetVkInstance(pPhysicalDevices[i], instance); - } - } - return result; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties) { - if (instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups(instance, pPhysicalDeviceGroupCount, pPhysicalDeviceGroupProperties); - - if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pPhysicalDeviceGroupProperties != nullptr) { - for (uint32_t i = 0; i < *pPhysicalDeviceGroupCount; ++i) { - for (uint32_t j = 0; j < pPhysicalDeviceGroupProperties[i].physicalDeviceCount; ++j) { - DebugMarker::Get().SetVkInstance(pPhysicalDeviceGroupProperties[i].physicalDevices[j], instance); - } - } - } - return result; -} - -VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator) { - dispatch_key key = get_dispatch_key(instance); - instance_dispatch_table(instance)->DestroyInstance(instance, pAllocator); - destroy_instance_dispatch_table(key); -} - -VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - // Get the function pointer - VkLayerDeviceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); - assert(chain_info->u.pLayerInfo != 0); - PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; - PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; - VkInstance vk_instance = DebugMarker::Get().GetVkInstance(physicalDevice); - PFN_vkCreateDevice fpCreateDevice = (PFN_vkCreateDevice)fpGetInstanceProcAddr(vk_instance, "vkCreateDevice"); - if (fpCreateDevice == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } - - // Call the function and create the dispatch table - chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; - VkResult result = fpCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); - if (result == VK_SUCCESS) { - initDeviceTable(*pDevice, fpGetDeviceProcAddr); - } - - return result; -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - static const VkExtensionProperties instanceExtensions[] = { - {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, - }; - - if (pLayerName != nullptr && strcmp(pLayerName, "VK_LAYER_GOOGLE_DebugMarker") == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); - } - - return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, - VkLayerProperties* pProperties) { - static const VkLayerProperties layerProperties[] = {{ - "VK_LAYER_GOOGLE_DebugMarker", - VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), // specVersion - VK_MAKE_VERSION(0, 1, 0), // implementationVersion - "layer: DebugMarker", - }}; - - return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, - uint32_t* pPropertyCount, - VkLayerProperties* pProperties) { - static const VkLayerProperties layerProperties[] = {{ - "VK_LAYER_GOOGLE_DebugMarker", - VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), - VK_MAKE_VERSION(0, 1, 0), - "layer: DebugMarker", - }}; - - return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); -} - -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, - const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - static const VkExtensionProperties deviceExtensions[] = { - {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, - }; - - if (pLayerName != nullptr && strcmp(pLayerName, "VK_LAYER_GOOGLE_DebugMarker") == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); - } - - VkInstance vk_instance = DebugMarker::Get().GetVkInstance(physicalDevice); - - // Manually append device extension. This should not be necessary, but the Android vulkan - // loader does not expose extensions from implicit layer (b/143293104). - if (pProperties == nullptr) { - VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - (*pPropertyCount) += ARRAY_SIZE(deviceExtensions); - } - return res; - } - - if (*pPropertyCount > 0) { - uint32_t requestedCount = *pPropertyCount; - VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - uint32_t originalCount = *pPropertyCount; - uint32_t additionalCount = 0; - - for (uint32_t i = 0; i < ARRAY_SIZE(deviceExtensions); ++i) { - bool found = false; - for (uint32_t j = 0; j < originalCount; ++j) { - if (strcmp(pProperties[j].extensionName, deviceExtensions[i].extensionName) == 0) { - found = true; - break; - } - } - if (!found) { - if (originalCount + additionalCount < requestedCount) { - pProperties[originalCount + additionalCount] = deviceExtensions[i]; - } - additionalCount++; - } - } - *pPropertyCount = originalCount + additionalCount; - if (*pPropertyCount > requestedCount) { - *pPropertyCount = requestedCount; - } - } - return res; - } - return VK_SUCCESS; -} - - -} // extern "C" diff --git a/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h b/layersvt/debug_marker/debug_marker_handwritten_functions_vk_ext_debug_marker.h index 84db032b13..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 cd635acb9e..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) @@ -86,3 +90,27 @@ if (TARGET VkLayer_DeviceMemoryReport) endif() endif() +add_executable(test_common_layer + common/test_dispatch_downstream.cpp + common/test_dispatch_table_manager.cpp + common/test_layer_base.cpp + common/test_layer_entrypoints.cpp + common/test_layer_manifest.cpp + layer_test_main.cpp + $ +) +target_link_libraries(test_common_layer PRIVATE + layersvt_common + GTest::gtest + Vulkan::Headers + Vulkan::UtilityHeaders +) +target_include_directories(test_common_layer PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) +add_test(NAME test_common_layer COMMAND test_common_layer) +set_target_properties(test_common_layer PROPERTIES FOLDER "layers/common/Test") + +if(WIN32 AND (QT_TARGET_TYPE STREQUAL STATIC_LIBRARY)) + set_property(TARGET test_common_layer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() diff --git a/layersvt/test/common/layer_base_test_peer.h b/layersvt/test/common/layer_base_test_peer.h new file mode 100644 index 0000000000..ce41ab0840 --- /dev/null +++ b/layersvt/test/common/layer_base_test_peer.h @@ -0,0 +1,138 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "common/layer_base.h" +#include +#include + +namespace layersvt { + +class LayerBaseTestPeer { + public: + static PFN_vkVoidFunction GetKnownInstanceCommand(const char* command_name) { + return LayerBase::GetKnownInstanceCommand(command_name); + } + static PFN_vkVoidFunction GetKnownDeviceCommand(const char* command_name) { + return LayerBase::GetKnownDeviceCommand(command_name); + } + + static VkResult EnumeratePhysicalDevices(VkInstance instance, uint32_t* physical_device_count, + VkPhysicalDevice* physical_devices) { + return LayerBase::EnumeratePhysicalDevices(instance, physical_device_count, physical_devices); + } + + static VkResult EnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_group_properties) { + return LayerBase::EnumeratePhysicalDeviceGroups(instance, physical_device_group_count, physical_device_group_properties); + } + static VkResult CreateDevice(VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks* allocator, VkDevice* device) { + return LayerBase::CreateDevice(physical_device, create_info, allocator, device); + } + + static void DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) { + LayerBase::DestroyDevice(device, allocator); + } + + static VkResult EnumerateInstanceExtensionProperties(const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties) { + return LayerBase::EnumerateInstanceExtensionProperties(layer_name, property_count, properties); + } + static VkResult EnumerateInstanceLayerProperties(uint32_t* property_count, VkLayerProperties* properties) { + return LayerBase::EnumerateInstanceLayerProperties(property_count, properties); + } + static VkResult EnumerateDeviceLayerProperties(VkPhysicalDevice physical_device, uint32_t* property_count, + VkLayerProperties* properties) { + return LayerBase::EnumerateDeviceLayerProperties(physical_device, property_count, properties); + } + static VkResult EnumerateDeviceExtensionProperties( + VkPhysicalDevice physical_device, const char* layer_name, uint32_t* property_count, + VkExtensionProperties* properties, + PFN_vkEnumerateDeviceExtensionProperties downstream_function = nullptr) { + if (downstream_function != nullptr) { + 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); + } + static VkResult GetPhysicalDeviceToolProperties( + VkPhysicalDevice physical_device, uint32_t* tool_count, + VkPhysicalDeviceToolPropertiesEXT* tool_properties, + PFN_vkGetPhysicalDeviceToolPropertiesEXT downstream_function = nullptr) { + if (downstream_function != nullptr) { + 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.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); } + + static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkInstance instance) { + return LayerBase::GetInstanceDispatchTable(instance); + } + static VkuInstanceDispatchTable* GetInstanceDispatchTable(VkPhysicalDevice physical_device) { + return LayerBase::GetInstanceDispatchTable(physical_device); + } + + static VkuDeviceDispatchTable* GetDeviceDispatchTable(const void* object) { return LayerBase::GetDeviceDispatchTable(object); } + + static PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* command_name) { + return LayerBase::GetInstanceProcAddr(instance, command_name); + } + static PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* command_name) { + return LayerBase::GetDeviceProcAddr(device, command_name); + } + + static VkResult CreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, + VkInstance* instance) { + return LayerBase::CreateInstance(create_info, allocator, instance); + } + + static void DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) { + LayerBase::DestroyInstance(instance, allocator); + } +}; + +} // namespace layersvt diff --git a/layersvt/test/common/test_dispatch_downstream.cpp b/layersvt/test/common/test_dispatch_downstream.cpp new file mode 100644 index 0000000000..0d2661bef2 --- /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) + // DispatchDownstreamOr with VK_SUCCESS fallback for VkResult commands + uint32_t count = 0; + VkResult instance_result = + DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(VK_SUCCESS, mock_instance, &count, nullptr); + EXPECT_EQ(instance_result, VK_SUCCESS); + + VkResult device_result = DispatchDownstreamOr<&VkuDeviceDispatchTable::DeviceWaitIdle>(VK_SUCCESS, 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((DispatchDownstreamOr<&VkuInstanceDispatchTable::EnumeratePhysicalDevices>(VK_SUCCESS, mock_instance, &count, nullptr)), + 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); + + 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.RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); + + static bool physical_device_function_called = false; + physical_device_function_called = false; + instance_table->GetPhysicalDeviceProperties = [](VkPhysicalDevice, VkPhysicalDeviceProperties* physical_device_properties) { + physical_device_function_called = true; + if (physical_device_properties) physical_device_properties->apiVersion = VK_API_VERSION_1_3; + }; + + DispatchDownstream<&VkuInstanceDispatchTable::GetPhysicalDeviceProperties>(mock_physical_device, &properties); + EXPECT_TRUE(physical_device_function_called); + EXPECT_EQ(properties.apiVersion, static_cast(VK_API_VERSION_1_3)); +} + diff --git a/layersvt/test/common/test_dispatch_table_manager.cpp b/layersvt/test/common/test_dispatch_table_manager.cpp new file mode 100644 index 0000000000..2efea12bca --- /dev/null +++ b/layersvt/test/common/test_dispatch_table_manager.cpp @@ -0,0 +1,308 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/dispatch_table_manager.h" +#include +#include +#include +#include +#include + +using namespace layersvt; + +TEST(DispatchTableManagerTest, GetDispatchKey) { + void* mock_vtable = reinterpret_cast(static_cast(0xDEADBEEF)); + void* mock_object = &mock_vtable; + + EXPECT_EQ(DispatchTableManager::GetDispatchKey(mock_object), + static_cast(reinterpret_cast(mock_vtable))); +} + +TEST(DispatchTableManagerTest, LoaderDataCallback) { + DispatchTableManager dispatch_table_manager; + + void* mock_device_vtable = reinterpret_cast(static_cast(0x12345678)); + VkDevice mock_device = reinterpret_cast(&mock_device_vtable); + + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(mock_device), nullptr); + + PFN_vkSetDeviceLoaderData dummy_callback = [](VkDevice, void*) -> VkResult { return VK_SUCCESS; }; + dispatch_table_manager.SetDeviceLoaderDataCallback(mock_device, dummy_callback); + + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(mock_device), dummy_callback); + + dispatch_table_manager.DestroyDeviceTable(DispatchTableManager::GetDispatchKey(mock_device)); + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(mock_device), nullptr); +} + +TEST(DispatchTableManagerTest, InstanceAndDeviceTableLifecycle) { + DispatchTableManager dispatch_table_manager; + + void* mock_instance_vtable = reinterpret_cast(static_cast(0x1111)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_instance), nullptr); + auto* instance_table = dispatch_table_manager.InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(instance_table, nullptr); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_instance), instance_table); + + dispatch_table_manager.DestroyInstanceTable(DispatchTableManager::GetDispatchKey(mock_instance)); + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(mock_instance), nullptr); + + void* mock_device_vtable = reinterpret_cast(static_cast(0x2222)); + auto mock_device = reinterpret_cast(&mock_device_vtable); + + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(mock_device), nullptr); + auto* device_table = + dispatch_table_manager.InitDeviceTable(mock_device, [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(device_table, nullptr); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(mock_device), device_table); + + dispatch_table_manager.DestroyDeviceTable(DispatchTableManager::GetDispatchKey(mock_device)); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(mock_device), nullptr); +} + +TEST(DispatchTableManagerTest, ReinitPreservesExistingTablePointerStability) { + DispatchTableManager dispatch_table_manager; + + void* mock_device_vtable = reinterpret_cast(static_cast(0x3333)); + auto mock_device = reinterpret_cast(&mock_device_vtable); + PFN_vkGetDeviceProcAddr get_device_proc_addr = [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }; + + auto* first_table = dispatch_table_manager.InitDeviceTable(mock_device, get_device_proc_addr); + auto* second_table = dispatch_table_manager.InitDeviceTable(mock_device, get_device_proc_addr); + EXPECT_EQ(second_table, first_table); +} + +TEST(DispatchTableManagerTest, ConcurrentAccess) { + DispatchTableManager dispatch_table_manager; + + constexpr int kNumberOfThreads = 8; + constexpr int kIterations = 500; + std::atomic start_flag{false}; + std::vector threads; + + // Pre-allocate dummy vtables and handles for each thread to ensure stable memory addresses + struct ThreadMockData { + void* instance_vtable; + VkInstance instance; + void* device_vtable; + VkDevice device; + }; + std::vector mock_data(kNumberOfThreads); + for (int thread_index = 0; thread_index < kNumberOfThreads; ++thread_index) { + mock_data[thread_index].instance_vtable = reinterpret_cast(static_cast(0x10000 + thread_index * 0x100)); + mock_data[thread_index].instance = reinterpret_cast(&mock_data[thread_index].instance_vtable); + mock_data[thread_index].device_vtable = reinterpret_cast(static_cast(0x20000 + thread_index * 0x100)); + mock_data[thread_index].device = reinterpret_cast(&mock_data[thread_index].device_vtable); + } + + PFN_vkSetDeviceLoaderData dummy_callback = [](VkDevice, void*) -> VkResult { return VK_SUCCESS; }; + + for (int thread_index = 0; thread_index < kNumberOfThreads; ++thread_index) { + threads.emplace_back([&, thread_index]() { + while (!start_flag.load()) { + std::this_thread::yield(); + } + + auto& my_data = mock_data[thread_index]; + + for (int i = 0; i < kIterations; ++i) { + // Initialize tables + auto* instance_table = dispatch_table_manager.InitInstanceTable( + my_data.instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(instance_table, nullptr); + + auto* device_table = dispatch_table_manager.InitDeviceTable( + my_data.device, [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }); + EXPECT_NE(device_table, nullptr); + + // Set loader data callback + dispatch_table_manager.SetDeviceLoaderDataCallback(my_data.device, dummy_callback); + + // Read back own tables and callback + EXPECT_EQ(dispatch_table_manager.GetInstanceDispatchTable(my_data.instance), instance_table); + EXPECT_EQ(dispatch_table_manager.GetDeviceDispatchTable(my_data.device), device_table); + EXPECT_EQ(dispatch_table_manager.GetDeviceLoaderDataCallback(my_data.device), dummy_callback); + + // Concurrent cross-thread read from a neighbor's handle + int neighbor_index = (thread_index + 1) % kNumberOfThreads; + (void)dispatch_table_manager.GetDeviceDispatchTable(mock_data[neighbor_index].device); + (void)dispatch_table_manager.GetInstanceDispatchTable(mock_data[neighbor_index].instance); + + // Destroy tables periodically + if ((i % 10) == 0) { + dispatch_table_manager.DestroyDeviceTable(DispatchTableManager::GetDispatchKey(my_data.device)); + dispatch_table_manager.DestroyInstanceTable(DispatchTableManager::GetDispatchKey(my_data.instance)); + } + } + }); + } + + start_flag.store(true); + for (auto& thread : threads) { + thread.join(); + } +} + +TEST(DispatchTableManagerTest, BasicPhysicalDeviceTracking) { + DispatchTableManager dispatch_table_manager; + + auto mock_instance = reinterpret_cast(static_cast(0x1000)); + auto mock_physical_device1 = reinterpret_cast(static_cast(0x2001)); + auto mock_physical_device2 = reinterpret_cast(static_cast(0x2002)); + + EXPECT_EQ(dispatch_table_manager.GetVkInstance(mock_physical_device1), VK_NULL_HANDLE); + + 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); +} + +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.RegisterPhysicalDevices(&mock_physical_device, 1, 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); + + 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); + 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.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); + + 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); + + int neighbor_index = (thread_index + 1) % kNumberOfThreads; + (void)dispatch_table_manager.GetVkInstance(mock_data[neighbor_index].physical_device1); + (void)dispatch_table_manager.GetInstanceDispatchTable(mock_data[neighbor_index].physical_device1); + + if ((i % 10) == 0) { + dispatch_table_manager.DestroyInstanceTable(DispatchTableManager::GetDispatchKey(my_data.instance)); + EXPECT_EQ(dispatch_table_manager.GetVkInstance(my_data.physical_device1), VK_NULL_HANDLE); + } + } + }); + } + + start_flag.store(true); + for (auto& thread : threads) { + thread.join(); + } +} + diff --git a/layersvt/test/common/test_layer_base.cpp b/layersvt/test/common/test_layer_base.cpp new file mode 100644 index 0000000000..a843c80c24 --- /dev/null +++ b/layersvt/test/common/test_layer_base.cpp @@ -0,0 +1,786 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/layer_base.h" +#include "layer_base_test_peer.h" +#include "layer_test_helper.h" +#include +#include +#include + +using namespace layersvt; + +class LifecycleTestLayer : public LayerBase { + public: + bool pre_create_instance_called = false; + bool post_create_instance_called = false; + bool pre_destroy_instance_called = false; + + bool pre_create_device_called = false; + bool post_create_device_called = false; + bool pre_destroy_device_called = false; + + const VkAllocationCallbacks* captured_post_create_instance_allocator = nullptr; + const VkAllocationCallbacks* captured_post_create_device_allocator = nullptr; + + void PreCreateInstance(VkInstanceCreateInfo*, const VkAllocationCallbacks*) override { pre_create_instance_called = true; } + void PostCreateInstance(VkInstance, const VkInstanceCreateInfo*, const VkAllocationCallbacks* allocator) override { + post_create_instance_called = true; + captured_post_create_instance_allocator = allocator; + } + void PreDestroyInstance(VkInstance, const VkAllocationCallbacks*) override { pre_destroy_instance_called = true; } + + void PreCreateDevice(VkPhysicalDevice, VkDeviceCreateInfo*, const VkAllocationCallbacks*) override { + pre_create_device_called = true; + } + void PostCreateDevice(VkDevice, VkPhysicalDevice, const VkDeviceCreateInfo*, const VkAllocationCallbacks* allocator) override { + post_create_device_called = true; + captured_post_create_device_allocator = allocator; + } + void PreDestroyDevice(VkDevice, const VkAllocationCallbacks*) override { pre_destroy_device_called = true; } +}; + +TEST(LayerBaseTest, HookInvocations) { + LifecycleTestLayer layer; + EXPECT_FALSE(layer.pre_create_instance_called); + EXPECT_FALSE(layer.pre_create_device_called); + EXPECT_FALSE(layer.post_create_device_called); + + // Verify hooks trigger as expected + layer.PreCreateInstance(nullptr, nullptr); + EXPECT_TRUE(layer.pre_create_instance_called); + + layer.PreCreateDevice(VK_NULL_HANDLE, nullptr, nullptr); + EXPECT_TRUE(layer.pre_create_device_called); + + layer.PostCreateDevice(VK_NULL_HANDLE, VK_NULL_HANDLE, nullptr, nullptr); + EXPECT_TRUE(layer.post_create_device_called); +} + +TEST(LayerBaseTest, CreateInstanceWithMockChain) { + static void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + static auto mock_instance_handle = reinterpret_cast(&mock_instance_vtable); + + PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkCreateInstance") == 0) { + return reinterpret_cast( + +[](const VkInstanceCreateInfo*, const VkAllocationCallbacks*, VkInstance* instance_handle) -> VkResult { + *instance_handle = mock_instance_handle; + return VK_SUCCESS; + }); + } + if (std::strcmp(function_name, "vkDestroyInstance") == 0) { + return reinterpret_cast(+[](VkInstance, const VkAllocationCallbacks*) {}); + } + return nullptr; + }; + + VkLayerInstanceLink layer_link{nullptr, mock_get_instance_proc_addr, nullptr}; + VkLayerInstanceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = &chain_info; + LifecycleTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + + VkAllocationCallbacks mock_allocator{}; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, &mock_allocator, &instance), VK_SUCCESS); + EXPECT_EQ(instance, mock_instance_handle); + EXPECT_TRUE(layer.pre_create_instance_called); + EXPECT_TRUE(layer.post_create_instance_called); + EXPECT_EQ(layer.captured_post_create_instance_allocator, &mock_allocator); + EXPECT_NE(LayerBaseTestPeer::GetDispatchTableManager(layer).GetInstanceDispatchTable(instance), nullptr); + + LayerBaseTestPeer::DestroyInstance(instance, nullptr); + EXPECT_TRUE(layer.pre_destroy_instance_called); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetInstanceDispatchTable(instance), nullptr); +} + +TEST(LayerBaseTest, CreateInstanceNullHandling) { + LifecycleTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + // Null create info + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(nullptr, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); + + // Missing chain info + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, PreCreateNotInvokedOnMissingChain) { + LifecycleTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); + EXPECT_FALSE(layer.pre_create_instance_called); +} + +class SubclassWithInspection : public LayerBase { + public: + bool inspected = false; + void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks*) override { + if (create_info && create_info->pApplicationInfo) { + inspected = true; + } + } +}; + +TEST(LayerBaseTest, CreateInstanceNullSafetyInHook) { + SubclassWithInspection layer; + VkInstance instance = VK_NULL_HANDLE; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(nullptr, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); + EXPECT_FALSE(layer.inspected); + + VkInstanceCreateInfo instance_create_info{}; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, nullptr, nullptr), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, PreCreateInstanceMutation) { + class MutatingLayer : public LayerBase { + public: + void PreCreateInstance(VkInstanceCreateInfo* create_info, const VkAllocationCallbacks*) override { + if (create_info) { + create_info->flags = 0xABCD; + } + } + }; + + static VkInstanceCreateFlags received_flags = 0; + static void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + static auto mock_instance_handle = reinterpret_cast(&mock_instance_vtable); + + PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkCreateInstance") == 0) { + return reinterpret_cast( + +[](const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks*, VkInstance* instance) -> VkResult { + received_flags = create_info->flags; + *instance = mock_instance_handle; + return VK_SUCCESS; + }); + } + if (std::strcmp(function_name, "vkDestroyInstance") == 0) { + return reinterpret_cast(+[](VkInstance, const VkAllocationCallbacks*) {}); + } + return nullptr; + }; + + VkLayerInstanceLink layer_link{nullptr, mock_get_instance_proc_addr, nullptr}; + VkLayerInstanceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = &chain_info; + + MutatingLayer layer; + VkInstance instance = VK_NULL_HANDLE; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, nullptr, &instance), VK_SUCCESS); + EXPECT_EQ(received_flags, 0xABCDu); + LayerBaseTestPeer::DestroyInstance(instance, nullptr); +} + +TEST(LayerBaseTest, PreCreateDeviceMutation) { + class MutatingLayer : public LayerBase { + public: + void PreCreateDevice(VkPhysicalDevice, VkDeviceCreateInfo* create_info, const VkAllocationCallbacks*) override { + if (create_info) { + create_info->flags = 0x5678; + } + } + }; + + static VkDeviceCreateFlags received_flags = 0; + static void* mock_device_vtable = reinterpret_cast(static_cast(0x55667788)); + static auto mock_device_handle = reinterpret_cast(&mock_device_vtable); + + PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkCreateDevice") == 0) { + return reinterpret_cast(+[](VkPhysicalDevice, const VkDeviceCreateInfo* create_info, + const VkAllocationCallbacks*, VkDevice* device_handle) -> VkResult { + received_flags = create_info->flags; + *device_handle = mock_device_handle; + return VK_SUCCESS; + }); + } + return nullptr; + }; + + PFN_vkGetDeviceProcAddr mock_get_device_proc_addr = [](VkDevice, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkDestroyDevice") == 0) { + return reinterpret_cast(+[](VkDevice, const VkAllocationCallbacks*) {}); + } + return nullptr; + }; + + VkLayerDeviceLink layer_link{nullptr, mock_get_instance_proc_addr, mock_get_device_proc_addr}; + VkLayerDeviceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkDeviceCreateInfo device_create_info{}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = &chain_info; + + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x5555)); + + MutatingLayer layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).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); + 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).RegisterPhysicalDevices(&mock_physical_device, 1, 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).RegisterPhysicalDevices(&mock_physical_device, 1, 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).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); + + // Null create info + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, nullptr, nullptr, &device), + VK_ERROR_INITIALIZATION_FAILED); + + // Missing chain info + VkDeviceCreateInfo device_create_info{}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), + VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, CreateDeviceInvalidInputSafety) { + LayerBase layer; + VkDevice device = VK_NULL_HANDLE; + VkDeviceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + auto mock_untracked_physical_device = reinterpret_cast(static_cast(0xBAADF00D)); + // Untracked physical device must fail cleanly without crashing + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_untracked_physical_device, &create_info, nullptr, &device), + VK_ERROR_INITIALIZATION_FAILED); + + // Null device pointer must fail cleanly + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_untracked_physical_device, &create_info, nullptr, nullptr), + VK_ERROR_INITIALIZATION_FAILED); + + // VK_NULL_HANDLE physical device must fail cleanly + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(VK_NULL_HANDLE, &create_info, nullptr, &device), + VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, EnumeratePhysicalDevicesMapping) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x7777)); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDevices") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* physical_device_count, VkPhysicalDevice* physical_devices) -> VkResult { + if (!physical_device_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_devices) { + *physical_device_count = 1; + return VK_SUCCESS; + } + physical_devices[0] = reinterpret_cast(static_cast(0x7777)); + *physical_device_count = 1; + return VK_SUCCESS; + }); + } + return nullptr; + }); + + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDevices(mock_instance, &count, nullptr), VK_SUCCESS); + ASSERT_EQ(count, 1u); + + std::vector physical_devices(count); + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDevices(mock_instance, &count, physical_devices.data()), VK_SUCCESS); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device), mock_instance); +} + +class HookTestLayer : public LayerBase { + public: + using LayerBase::LayerBase; + using LayerBase::PostCreateDevice; + using LayerBase::PostCreateInstance; + using LayerBase::PreCreateDevice; + using LayerBase::PreCreateInstance; + using LayerBase::PreDestroyDevice; + using LayerBase::PreDestroyInstance; +}; + +TEST(LayerBaseTest, DefaultHooksExecution) { + HookTestLayer base; + + // Execute default no-op hooks to verify base class behavior + VkInstanceCreateInfo instance_create_info{}; + base.PreCreateInstance(&instance_create_info, nullptr); + base.PostCreateInstance(VK_NULL_HANDLE, &instance_create_info, nullptr); + base.PreDestroyInstance(VK_NULL_HANDLE, nullptr); + + VkDeviceCreateInfo device_create_info{}; + base.PreCreateDevice(VK_NULL_HANDLE, &device_create_info, nullptr); + base.PostCreateDevice(VK_NULL_HANDLE, VK_NULL_HANDLE, &device_create_info, nullptr); + base.PreDestroyDevice(VK_NULL_HANDLE, nullptr); +} + +TEST(LayerBaseTest, EnumeratePhysicalDeviceGroupsMapping) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device1 = reinterpret_cast(static_cast(0x8881)); + auto mock_physical_device2 = reinterpret_cast(static_cast(0x8882)); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDeviceGroups") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_groups) -> VkResult { + if (!physical_device_group_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_device_groups) { + *physical_device_group_count = 1; + return VK_SUCCESS; + } + physical_device_groups[0].physicalDeviceCount = 2; + physical_device_groups[0].physicalDevices[0] = + reinterpret_cast(static_cast(0x8881)); + physical_device_groups[0].physicalDevices[1] = + reinterpret_cast(static_cast(0x8882)); + *physical_device_group_count = 1; + return VK_SUCCESS; + }); + } + return nullptr; + }); + + void* missing_instance_vtable = reinterpret_cast(static_cast(0xBAADF00D)); + auto missing_instance = reinterpret_cast(&missing_instance_vtable); + // Missing table or function + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(missing_instance, nullptr, nullptr), + VK_ERROR_INITIALIZATION_FAILED); + + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(mock_instance, &count, nullptr), VK_SUCCESS); + ASSERT_EQ(count, 1u); + + std::vector groups(count); + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(mock_instance, &count, groups.data()), VK_SUCCESS); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device1), mock_instance); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device2), mock_instance); +} + +TEST(LayerBaseTest, EnumeratePhysicalDeviceGroupsKHRFallbackMapping) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x8883)); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkEnumeratePhysicalDeviceGroupsKHR") == 0) { + return reinterpret_cast( + +[](VkInstance, uint32_t* physical_device_group_count, + VkPhysicalDeviceGroupProperties* physical_device_groups) -> VkResult { + if (!physical_device_group_count) return VK_ERROR_INITIALIZATION_FAILED; + if (!physical_device_groups) { + *physical_device_group_count = 1; + return VK_SUCCESS; + } + physical_device_groups[0].physicalDeviceCount = 1; + physical_device_groups[0].physicalDevices[0] = + reinterpret_cast(static_cast(0x8883)); + *physical_device_group_count = 1; + return VK_SUCCESS; + }); + } + return nullptr; + }); + + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(mock_instance, &count, nullptr), VK_SUCCESS); + ASSERT_EQ(count, 1u); + + std::vector groups(count); + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDeviceGroups(mock_instance, &count, groups.data()), VK_SUCCESS); + EXPECT_EQ(LayerBaseTestPeer::GetDispatchTableManager(layer).GetVkInstance(mock_physical_device), mock_instance); +} + +TEST(LayerBaseTest, EnumeratePhysicalDevicesNullTable) { + LayerBase layer; + void* missing_instance_vtable = reinterpret_cast(static_cast(0xBAADF00D)); + auto missing_instance = reinterpret_cast(&missing_instance_vtable); + uint32_t count = 0; + EXPECT_EQ(LayerBaseTestPeer::EnumeratePhysicalDevices(missing_instance, &count, nullptr), + VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, CreateInstanceNullFpCreateInstance) { + static PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char*) -> PFN_vkVoidFunction { + return nullptr; + }; + VkLayerInstanceLink layer_link{nullptr, mock_get_instance_proc_addr, nullptr}; + VkLayerInstanceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkInstanceCreateInfo instance_create_info{}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = &chain_info; + + LifecycleTestLayer layer; + VkInstance instance = VK_NULL_HANDLE; + EXPECT_EQ(LayerBaseTestPeer::CreateInstance(&instance_create_info, nullptr, &instance), VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, CreateDeviceNullFpCreateDevice) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + auto mock_physical_device = reinterpret_cast(static_cast(0x5555)); + + LifecycleTestLayer layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); + + static PFN_vkGetInstanceProcAddr mock_get_instance_proc_addr = [](VkInstance, const char*) -> PFN_vkVoidFunction { + return nullptr; + }; + static PFN_vkGetDeviceProcAddr mock_get_device_proc_addr = [](VkDevice, const char*) -> PFN_vkVoidFunction { return nullptr; }; + VkLayerDeviceLink layer_link{nullptr, mock_get_instance_proc_addr, mock_get_device_proc_addr}; + VkLayerDeviceCreateInfo chain_info{VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO, nullptr, VK_LAYER_LINK_INFO, {&layer_link}}; + + VkDeviceCreateInfo device_create_info{}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = &chain_info; + + VkDevice device = VK_NULL_HANDLE; + EXPECT_EQ(LayerBaseTestPeer::CreateDevice(mock_physical_device, &device_create_info, nullptr, &device), + VK_ERROR_INITIALIZATION_FAILED); +} + +TEST(LayerBaseTest, LayerTracking) { + EXPECT_EQ(LayerBase::Get(), nullptr); + { + LayerBase layer; + EXPECT_EQ(LayerBase::Get(), &layer); + } + EXPECT_EQ(LayerBase::Get(), nullptr); + + { + LayerBase layer1; + EXPECT_EQ(LayerBase::Get(), &layer1); + } + EXPECT_EQ(LayerBase::Get(), nullptr); +} + +TEST(LayerBaseTest, GetKnownCommandsCommon) { + LayerBase layer; + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateInstance"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkDestroyInstance"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumeratePhysicalDevices"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumeratePhysicalDeviceGroups"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkNonExistentInstanceFunction"), nullptr); + + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetDeviceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkDestroyDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkNonExistentDeviceFunction"), nullptr); +} + +class TestDerivedLayer : public LayerBase { + public: + static inline auto mock_custom_instance_function = + reinterpret_cast(static_cast(0x12345678)); + static inline auto mock_custom_device_function = + reinterpret_cast(static_cast(0x87654321)); + + protected: + PFN_vkVoidFunction GetLayerInstanceCommand(const char* command_name) override { + if (std::strcmp(command_name, "vkCustomInstanceCmd") == 0) { + return mock_custom_instance_function; + } + return nullptr; + } + + PFN_vkVoidFunction GetLayerDeviceCommand(const char* command_name) override { + if (std::strcmp(command_name, "vkCustomDeviceCmd") == 0) { + return mock_custom_device_function; + } + return nullptr; + } +}; + +TEST(LayerBaseTest, LayerSpecificOverrideHooks) { + TestDerivedLayer layer; + + // Custom commands handled by virtual hooks + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkCustomInstanceCmd"), TestDerivedLayer::mock_custom_instance_function); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkCustomDeviceCmd"), TestDerivedLayer::mock_custom_device_function); + + // Common commands still handled by base template method fallback + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateInstance"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkCreateDevice"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetDeviceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownDeviceCommand("vkDestroyDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkCreateDevice"), nullptr); + + // Unhandled commands return nullptr + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkUnknownCmd"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkUnknownCmd"), nullptr); +} + +TEST(LayerBaseTest, ProcAddrDispatchChain) { + TestDerivedLayer layer; + auto& dispatch_table_manager = LayerBaseTestPeer::GetDispatchTableManager(layer); + + // 1. Global commands can be queried with VK_NULL_HANDLE + EXPECT_NE(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkGetInstanceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"), nullptr); + + // Non-global commands must return nullptr when instance is VK_NULL_HANDLE + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkDestroyInstance"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCustomInstanceCmd"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCustomDeviceCmd"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(VK_NULL_HANDLE, "vkNextLayerCmd"), nullptr); + + // GetDeviceProcAddr with VK_NULL_HANDLE must always return nullptr + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkGetDeviceProcAddr"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkDestroyDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkCustomDeviceCmd"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(VK_NULL_HANDLE, "vkNextLayerCmd"), nullptr); + + // 2. Querying with valid instance handle + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + static auto mock_next_instance_command = reinterpret_cast(static_cast(0xABCDEF01)); + + dispatch_table_manager.InitInstanceTable(mock_instance, [](VkInstance, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkNextLayerInstCmd") == 0) { + return mock_next_instance_command; + } + return nullptr; + }); + + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCustomInstanceCmd"), + TestDerivedLayer::mock_custom_instance_function); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCustomDeviceCmd"), + TestDerivedLayer::mock_custom_device_function); + EXPECT_NE(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkCreateDevice"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkDestroyInstance"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkNextLayerInstCmd"), mock_next_instance_command); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkUnimplementedCmd"), nullptr); + + // 3. Querying with valid device handle + void* mock_device_vtable = reinterpret_cast(static_cast(0x55667788)); + auto mock_device = reinterpret_cast(&mock_device_vtable); + static auto mock_next_device_command = reinterpret_cast(static_cast(0xABCDEF02)); + + dispatch_table_manager.InitDeviceTable(mock_device, [](VkDevice, const char* function_name) -> PFN_vkVoidFunction { + if (std::strcmp(function_name, "vkNextLayerDevCmd") == 0) { + return mock_next_device_command; + } + return nullptr; + }); + + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkCustomDeviceCmd"), + TestDerivedLayer::mock_custom_device_function); + EXPECT_NE(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkGetDeviceProcAddr"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkDestroyDevice"), nullptr); + // Instance commands must return nullptr via GetDeviceProcAddr even with valid device + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkCreateDevice"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkDestroyInstance"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkNextLayerDevCmd"), mock_next_device_command); + EXPECT_EQ(LayerBaseTestPeer::GetDeviceProcAddr(mock_device, "vkUnimplementedCmd"), nullptr); +} + +TEST(LayerBaseTest, DestroyNullHandles) { + class DestroyTrackingLayer : public LayerBase { + public: + int pre_destroy_instance_calls = 0; + int pre_destroy_device_calls = 0; + + protected: + void PreDestroyInstance(VkInstance, const VkAllocationCallbacks*) override { + ADD_FAILURE() << "PreDestroyInstance should not be called for VK_NULL_HANDLE"; + ++pre_destroy_instance_calls; + } + void PreDestroyDevice(VkDevice, const VkAllocationCallbacks*) override { + ADD_FAILURE() << "PreDestroyDevice should not be called for VK_NULL_HANDLE"; + ++pre_destroy_device_calls; + } + }; + + DestroyTrackingLayer layer; + // Vulkan specification mandates destroying VK_NULL_HANDLE is a valid no-op. + // Virtual PreDestroy hooks are bypassed when destroying VK_NULL_HANDLE. + LayerBaseTestPeer::DestroyInstance(VK_NULL_HANDLE, nullptr); + EXPECT_EQ(layer.pre_destroy_instance_calls, 0); + + LayerBaseTestPeer::DestroyDevice(VK_NULL_HANDLE, nullptr); + EXPECT_EQ(layer.pre_destroy_device_calls, 0); +} + +TEST(LayerBaseTest, PhysicalDeviceResolvesInstanceTable) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + void* mock_physical_device_vtable = reinterpret_cast(static_cast(0x55667788)); + auto mock_physical_device = reinterpret_cast(&mock_physical_device_vtable); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).RegisterPhysicalDevices(&mock_physical_device, 1, mock_instance); + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + + EXPECT_NE(LayerBaseTestPeer::GetInstanceDispatchTable(mock_physical_device), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetInstanceDispatchTable(mock_physical_device), + LayerBaseTestPeer::GetInstanceDispatchTable(mock_instance)); +} + +TEST(LayerBaseTest, EnumeratePhysicalDeviceGroupsKHRResolution) { + void* mock_instance_vtable = reinterpret_cast(static_cast(0x11223344)); + auto mock_instance = reinterpret_cast(&mock_instance_vtable); + + LayerBase layer; + LayerBaseTestPeer::GetDispatchTableManager(layer).InitInstanceTable( + mock_instance, [](VkInstance, const char*) -> PFN_vkVoidFunction { return nullptr; }); + + PFN_vkVoidFunction function_khr = + LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkEnumeratePhysicalDeviceGroupsKHR"); + PFN_vkVoidFunction function_core = + LayerBaseTestPeer::GetInstanceProcAddr(mock_instance, "vkEnumeratePhysicalDeviceGroups"); + EXPECT_NE(function_khr, nullptr); + EXPECT_EQ(function_khr, function_core); +} + +TEST(LayerBaseTest, ResetLayerMultipleInvocations) { + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(/*destroy=*/true); + EXPECT_EQ(LayerBase::Get(), nullptr); +} + +TEST(LayerBaseTest, ResetLayerCrossType) { + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(); + EXPECT_NE(LayerBase::Get(), nullptr); + layer_test::ResetLayer(/*destroy=*/true); + EXPECT_EQ(LayerBase::Get(), nullptr); +} diff --git a/layersvt/test/common/test_layer_entrypoints.cpp b/layersvt/test/common/test_layer_entrypoints.cpp new file mode 100644 index 0000000000..da36c7d69e --- /dev/null +++ b/layersvt/test/common/test_layer_entrypoints.cpp @@ -0,0 +1,80 @@ +/* Copyright (C) 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/layer_base.h" +#include "common/layer_manifest.h" +#include +#include +#include + +namespace layersvt { +namespace { + +class EntrypointsTestLayer : public LayerBase { + public: + explicit EntrypointsTestLayer(const LayerManifest* manifest) : manifest_(manifest) {} + ~EntrypointsTestLayer() override = default; + + [[nodiscard]] const LayerManifest* GetLayerManifest() const override { return manifest_; } + + private: + const LayerManifest* manifest_; +}; + +TEST(LayerEntrypointsTest, ForwardingCalls) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Entrypoints", + }; + EntrypointsTestLayer test_layer(&manifest); + uint32_t property_count = 0; + + EXPECT_EQ(::vkEnumerateInstanceLayerProperties(&property_count, nullptr), VK_SUCCESS); + EXPECT_EQ(::vkEnumerateInstanceExtensionProperties("VK_LAYER_TEST_Entrypoints", &property_count, nullptr), VK_SUCCESS); + EXPECT_EQ(::vkEnumerateInstanceExtensionProperties(nullptr, &property_count, nullptr), VK_ERROR_LAYER_NOT_PRESENT); + EXPECT_EQ(::vkEnumerateDeviceLayerProperties(VK_NULL_HANDLE, &property_count, nullptr), VK_SUCCESS); + EXPECT_EQ(::vkEnumerateDeviceExtensionProperties(VK_NULL_HANDLE, nullptr, &property_count, nullptr), VK_SUCCESS); + + EXPECT_NE(::vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkGetInstanceProcAddr"), nullptr); + EXPECT_EQ(::vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkUnknownFunction"), nullptr); + EXPECT_EQ(::vkGetDeviceProcAddr(VK_NULL_HANDLE, "vkUnknownFunction"), nullptr); + + VkNegotiateLayerInterface version_interface{ + .sType = LAYER_NEGOTIATE_INTERFACE_STRUCT, + .pNext = nullptr, + .loaderLayerInterfaceVersion = 2, + }; + EXPECT_EQ(::vkNegotiateLoaderLayerInterfaceVersion(&version_interface), VK_SUCCESS); + EXPECT_EQ(version_interface.loaderLayerInterfaceVersion, 2u); + EXPECT_NE(version_interface.pfnGetInstanceProcAddr, nullptr); + EXPECT_NE(version_interface.pfnGetDeviceProcAddr, nullptr); + + VkNegotiateLayerInterface version_one_interface{ + .sType = LAYER_NEGOTIATE_INTERFACE_STRUCT, + .pNext = nullptr, + .loaderLayerInterfaceVersion = 1, + }; + EXPECT_EQ(::vkNegotiateLoaderLayerInterfaceVersion(&version_one_interface), VK_SUCCESS); + EXPECT_EQ(version_one_interface.loaderLayerInterfaceVersion, 1u); + + VkNegotiateLayerInterface unsupported_version_interface{ + .sType = LAYER_NEGOTIATE_INTERFACE_STRUCT, + .pNext = nullptr, + .loaderLayerInterfaceVersion = 0, + }; + EXPECT_EQ(::vkNegotiateLoaderLayerInterfaceVersion(&unsupported_version_interface), VK_ERROR_INITIALIZATION_FAILED); +} + +} // namespace +} // namespace layersvt diff --git a/layersvt/test/common/test_layer_manifest.cpp b/layersvt/test/common/test_layer_manifest.cpp new file mode 100644 index 0000000000..0843d26285 --- /dev/null +++ b/layersvt/test/common/test_layer_manifest.cpp @@ -0,0 +1,477 @@ +/* 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(LayerBaseTest, GetKnownCommandsCommonWithManifest) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Common", + }; + ManifestTestLayer layer(&manifest); + + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateInstanceExtensionProperties"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateInstanceLayerProperties"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateDeviceLayerProperties"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkEnumerateDeviceExtensionProperties"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkEnumerateDeviceLayerProperties"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkEnumerateDeviceExtensionProperties"), nullptr); + + // Without tool_properties in manifest, tooling functions return nullptr + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetPhysicalDeviceToolPropertiesEXT"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetPhysicalDeviceToolProperties"), nullptr); +} + +TEST(LayerBaseTest, GetKnownCommandsWithToolProperties) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Common", + .tool_properties = VkPhysicalDeviceToolPropertiesEXT{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, + .pNext = nullptr, + .name = "VK_LAYER_TEST_Common", + .version = "1", + .purposes = VK_TOOL_PURPOSE_TRACING_BIT_EXT, + .description = "Test layer", + .layer = "VK_LAYER_TEST_Common", + }, + }; + ManifestTestLayer layer(&manifest); + + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetPhysicalDeviceToolPropertiesEXT"), nullptr); + EXPECT_NE(LayerBaseTestPeer::GetKnownInstanceCommand("vkGetPhysicalDeviceToolProperties"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetPhysicalDeviceToolPropertiesEXT"), nullptr); + EXPECT_EQ(LayerBaseTestPeer::GetKnownDeviceCommand("vkGetPhysicalDeviceToolProperties"), nullptr); +} + +TEST(LayerBaseTest, GetPhysicalDeviceToolPropertiesDispatch) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Common", + .tool_properties = VkPhysicalDeviceToolPropertiesEXT{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, + .pNext = nullptr, + .name = "VK_LAYER_TEST_Common", + .version = "1", + .purposes = VK_TOOL_PURPOSE_TRACING_BIT_EXT, + .description = "Test layer", + .layer = "VK_LAYER_TEST_Common", + }, + }; + ManifestTestLayer layer(&manifest); + + // Test with null physical device (no downstream lookup) + uint32_t count = 0; + VkResult result = LayerBaseTestPeer::GetPhysicalDeviceToolProperties(VK_NULL_HANDLE, &count, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + ASSERT_EQ(count, 1u); + + std::vector tools(count); + result = LayerBaseTestPeer::GetPhysicalDeviceToolProperties(VK_NULL_HANDLE, &count, tools.data()); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_STREQ(tools[0].name, "VK_LAYER_TEST_Common"); +} + +TEST(LayerBaseEnumerationTest, EmptyExtensionsWithBuffer) { + LayerManifest manifest{ + .layer_name = "VK_LAYER_TEST_Empty", + }; + ManifestTestLayer layer(&manifest); + + uint32_t count = 5; + VkExtensionProperties properties[5]{}; + VkResult result = LayerBaseTestPeer::EnumerateInstanceExtensionProperties("VK_LAYER_TEST_Empty", &count, properties); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 0u); + + count = 5; + result = LayerBaseTestPeer::EnumerateDeviceExtensionProperties(nullptr, nullptr, &count, properties, nullptr); + EXPECT_EQ(result, VK_SUCCESS); + EXPECT_EQ(count, 0u); +} + diff --git a/layersvt/test/layer_test_helper.h b/layersvt/test/layer_test_helper.h index f1421dbb06..20a395809b 100644 --- a/layersvt/test/layer_test_helper.h +++ b/layersvt/test/layer_test_helper.h @@ -20,12 +20,14 @@ #include +#include #include #include #include #include #include #include +#include namespace layer_test { @@ -75,4 +77,30 @@ 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 diff --git a/layersvt/test/test_debugmarker.cpp b/layersvt/test/test_debugmarker.cpp index e1f2325d2f..d98f71ee73 100644 --- a/layersvt/test/test_debugmarker.cpp +++ b/layersvt/test/test_debugmarker.cpp @@ -18,6 +18,7 @@ #include #include #include +#include "test/common/layer_base_test_peer.h" static const char* kLayerName = "VK_LAYER_GOOGLE_DebugMarker"; @@ -27,12 +28,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 +64,152 @@ 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::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::GetVkInstance(mock_physical_device), VK_NULL_HANDLE); +} + +TEST_F(DebugMarkerTests, PreDestroyDeviceCleanupTest) { + TEST_DESCRIPTION("Verify that DestroyDevice cleans up tracked objects associated with that device"); + + layer_test::ResetLayer(); + + void* mock_dev1_vtable = reinterpret_cast(0x1000); + VkDevice dev1 = reinterpret_cast(&mock_dev1_vtable); + void* mock_dev2_vtable = reinterpret_cast(0x2000); + VkDevice dev2 = reinterpret_cast(&mock_dev2_vtable); + + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()).InitDeviceTable( + dev1, [](VkDevice, const char*) -> PFN_vkVoidFunction { + return reinterpret_cast(+[](VkDevice, const VkAllocationCallbacks*) {}); + }); + layersvt::LayerBaseTestPeer::GetDispatchTableManager(DebugMarker::Get()).InitDeviceTable( + dev2, [](VkDevice, const char*) -> PFN_vkVoidFunction { + return reinterpret_cast(+[](VkDevice, const VkAllocationCallbacks*) {}); + }); + + DebugMarker::Get().SetDebugObjectName((uint64_t)dev1, VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1"); + DebugMarker::Get().SetDebugObjectName((uint64_t)dev2, VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2"); + + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1")); + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); + + // Destroy dev1 - should remove Buffer1 but keep Buffer2 + layersvt::LayerBaseTestPeer::DestroyDevice(dev1, nullptr); + + EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x1111, "Buffer1")); + EXPECT_TRUE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); + + // Destroy dev2 - should remove Buffer2 + layersvt::LayerBaseTestPeer::DestroyDevice(dev2, nullptr); + EXPECT_FALSE(DebugMarker::Get().HasDebugObjectName(VK_OBJECT_TYPE_BUFFER, 0x2222, "Buffer2")); +} + +TEST_F(DebugMarkerTests, TemplateMethodDispatchTest) { + TEST_DESCRIPTION("Verify DebugMarker layer-specific hooks and LayerBase template method dispatching"); + + // 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); +}