From 342da33fdec78d269657194c9082835d647d2e68 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 23 Jun 2025 13:51:57 -0500 Subject: [PATCH 01/65] Cleanup command extension checking of device functions Remove some TODO's about needing the detailed dependency info of a command. Commands from extensions are available if any of the extensions they come from are enabled. The extensions themselves have complex requirements for enabling, but because the loader tries to do basic validation, it should only check that the primary extensions are available. This is so that the loader doesn't replicate all of the complicated dependency checking logic that already present in the Validation Layer. --- loader/generated/vk_loader_extensions.c | 8 ++--- .../generators/loader_extension_generator.py | 33 +++++-------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/loader/generated/vk_loader_extensions.c b/loader/generated/vk_loader_extensions.c index 1afd2efa9..dc80940a8 100644 --- a/loader/generated/vk_loader_extensions.c +++ b/loader/generated/vk_loader_extensions.c @@ -1627,7 +1627,7 @@ void init_extension_device_proc_terminator_dispatch(struct loader_device *dev) { // ---- VK_KHR_swapchain extension commands if (dev->driver_extensions.khr_swapchain_enabled) dispatch->CreateSwapchainKHR = (PFN_vkCreateSwapchainKHR)gpda(dev->icd_device, "vkCreateSwapchainKHR"); - if (dev->driver_extensions.khr_swapchain_enabled) + if (dev->driver_extensions.khr_swapchain_enabled || dev->driver_extensions.khr_device_group_enabled) dispatch->GetDeviceGroupSurfacePresentModesKHR = (PFN_vkGetDeviceGroupSurfacePresentModesKHR)gpda(dev->icd_device, "vkGetDeviceGroupSurfacePresentModesKHR"); // ---- VK_KHR_display_swapchain extension commands @@ -1660,7 +1660,7 @@ void init_extension_device_proc_terminator_dispatch(struct loader_device *dev) { #if defined(VK_USE_PLATFORM_WIN32_KHR) // ---- VK_EXT_full_screen_exclusive extension commands - if (dev->driver_extensions.ext_full_screen_exclusive_enabled && (dev->driver_extensions.khr_device_group_enabled || dev->driver_extensions.version_1_1_enabled)) + if (dev->driver_extensions.ext_full_screen_exclusive_enabled) dispatch->GetDeviceGroupSurfacePresentModes2EXT = (PFN_vkGetDeviceGroupSurfacePresentModes2EXT)gpda(dev->icd_device, "vkGetDeviceGroupSurfacePresentModes2EXT"); #endif // VK_USE_PLATFORM_WIN32_KHR } @@ -13648,7 +13648,7 @@ PFN_vkVoidFunction get_extension_device_proc_terminator(struct loader_device *de } if (!strcmp(name, "GetDeviceGroupSurfacePresentModesKHR")) { *found_name = true; - return dev->driver_extensions.khr_swapchain_enabled ? + return dev->driver_extensions.khr_swapchain_enabled || dev->driver_extensions.khr_device_group_enabled ? (PFN_vkVoidFunction)terminator_GetDeviceGroupSurfacePresentModesKHR : NULL; } // ---- VK_KHR_display_swapchain extension commands @@ -13713,7 +13713,7 @@ PFN_vkVoidFunction get_extension_device_proc_terminator(struct loader_device *de // ---- VK_EXT_full_screen_exclusive extension commands if (!strcmp(name, "GetDeviceGroupSurfacePresentModes2EXT")) { *found_name = true; - return (dev->driver_extensions.ext_full_screen_exclusive_enabled && (dev->driver_extensions.khr_device_group_enabled || dev->driver_extensions.version_1_1_enabled)) ? + return dev->driver_extensions.ext_full_screen_exclusive_enabled ? (PFN_vkVoidFunction)terminator_GetDeviceGroupSurfacePresentModes2EXT : NULL; } #endif // VK_USE_PLATFORM_WIN32_KHR diff --git a/scripts/generators/loader_extension_generator.py b/scripts/generators/loader_extension_generator.py index 3b7e77442..7d3f2543b 100644 --- a/scripts/generators/loader_extension_generator.py +++ b/scripts/generators/loader_extension_generator.py @@ -249,16 +249,6 @@ def print_vk_layer_dispatch_table(self, out): self.OutputLayerInstanceDispatchTable(out) self.OutputLayerDeviceDispatchTable(out) - # Convert an XML dependency expression to a C expression, taking a callback to replace extension names - # See https://registry.khronos.org/vulkan/specs/1.4/registry.html#depends-expressions - @staticmethod - def ConvertDependencyExpression(expr, replace_func): - # '(' and ')' can pass through unchanged - expr = re.sub(',', ' || ', expr) - expr = re.sub(r'\+', ' && ', expr) - expr = re.sub(r'\w+', lambda match: replace_func(match.group()), expr) - return expr - def DescribeBlock(self, command, current_block, out, custom_commands_string = ' commands', indent = ' '): effective_version_name = APISpecific.getEffectiveVersionName(self.targetApiName, command.version) if command.extensions != current_block and effective_version_name != current_block: @@ -664,14 +654,11 @@ def InitDeviceFunctionTerminatorDispatchTable(self, out): out.append(f'#if defined({command.protect})\n') current_block = self.DescribeBlock(command, current_block, out) - if command.name == 'vkGetDeviceGroupSurfacePresentModes2EXT': # command.extensions[0].depends in [x for x in self.vk.commands.values() if x.device]: - # Hardcode the dependency expression as vulkan_object.py doesn't expose this information - dep_expr = self.ConvertDependencyExpression('VK_KHR_device_group,VK_VERSION_1_1', lambda ext_name: f'dev->driver_extensions.{ext_name[3:].lower()}_enabled') - out.append(f' if (dev->driver_extensions.{command.extensions[0][3:].lower()}_enabled && ({dep_expr}))\n') - out.append(f' dispatch->{command.name[2:]} = (PFN_{(command.name)})gpda(dev->icd_device, "{(command.name)}");\n') - else: - out.append(f' if (dev->driver_extensions.{command.extensions[0][3:].lower()}_enabled)\n') - out.append(f' dispatch->{command.name[2:]} = (PFN_{(command.name)})gpda(dev->icd_device, "{(command.name)}");\n') + enable_extension_expressions = [] + for ext in command.extensions: + enable_extension_expressions.append(f'dev->driver_extensions.{ext[3:].lower()}_enabled') + out.append(f' if ({" || ".join(enable_extension_expressions)})\n') + out.append(f' dispatch->{command.name[2:]} = (PFN_{(command.name)})gpda(dev->icd_device, "{(command.name)}");\n') if command.protect is not None: out.append(f'#endif // {command.protect}\n') @@ -1304,12 +1291,10 @@ def DeviceExtensionGetTerminator(self, out): out.append(f' if (!strcmp(name, "{command.name[2:]}")) {{\n') out.append(' *found_name = true;\n') - if command.name == 'vkGetDeviceGroupSurfacePresentModes2EXT': # command.extensions[0].depends in [x for x in self.vk.commands.values() if x.device]: - # Hardcode the dependency expression as vulkan_object.py doesn't expose this information - dep_expr = self.ConvertDependencyExpression('VK_KHR_device_group,VK_VERSION_1_1', lambda ext_name: f'dev->driver_extensions.{ext_name[3:].lower()}_enabled') - out.append(f' return (dev->driver_extensions.{command.extensions[0][3:].lower()}_enabled && ({dep_expr})) ?\n') - else: - out.append(f' return dev->driver_extensions.{command.extensions[0][3:].lower()}_enabled ?\n') + enable_extension_expressions = [] + for ext in command.extensions: + enable_extension_expressions.append(f'dev->driver_extensions.{ext[3:].lower()}_enabled') + out.append(f' return {" || ".join(enable_extension_expressions)} ?\n') out.append(f' (PFN_vkVoidFunction)terminator_{(command.name[2:])} : NULL;\n') out.append(' }\n') From eb5baa53c657b89f515429a8e9b2db246f83d341 Mon Sep 17 00:00:00 2001 From: Mike Schuchardt Date: Fri, 27 Jun 2025 08:58:33 -0700 Subject: [PATCH 02/65] build: Update to header 1.4.320 --- CMakeLists.txt | 2 +- loader/loader.rc | 4 ++-- scripts/known_good.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea8098d3e..944a8f673 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.319 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.320 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/loader.rc b/loader/loader.rc index 38d62c8c6..705e8dc60 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 319, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.319.Dev Build" +#define VER_FILE_VERSION 1, 4, 320, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.320.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index 30df7194e..a69f843e5 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.319" + "commit": "v1.4.320" }, { "name": "googletest", From 8beef6cb63ffadb02300bf6321b4d3af85ea7417 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 20:31:52 +0000 Subject: [PATCH 03/65] build(deps): bump github/codeql-action from 3.29.0 to 3.29.2 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.0 to 3.29.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ce28f5bb42b7a9f2c824e633a3f6ee835bab6858...181d5eefc20863364f96762470ba6f862bdef56b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index adb1ffe52..da4fb2c94 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 - uses: actions/setup-python@v5 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: category: "/language:${{matrix.language}}" From da8d2caad9341ca8c5a7c3deba217d7da50a7c24 Mon Sep 17 00:00:00 2001 From: ziga-lunarg Date: Fri, 4 Jul 2025 12:50:49 +0300 Subject: [PATCH 04/65] build: Update to header 1.4.321 --- CMakeLists.txt | 2 +- loader/generated/vk_layer_dispatch_table.h | 3 + loader/generated/vk_loader_extensions.c | 34 ++++++- loader/generated/vk_loader_extensions.h | 1 + loader/loader.rc | 4 +- loader/wsi.c | 96 +++++++++---------- scripts/known_good.json | 2 +- .../generated/vk_dispatch_table_helper.h | 1 + 8 files changed, 90 insertions(+), 53 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 944a8f673..a30752492 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.320 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.321 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/generated/vk_layer_dispatch_table.h b/loader/generated/vk_layer_dispatch_table.h index ce2a6ddeb..b7c806ee4 100644 --- a/loader/generated/vk_layer_dispatch_table.h +++ b/loader/generated/vk_layer_dispatch_table.h @@ -729,6 +729,9 @@ typedef struct VkLayerDispatchTable_ { PFN_vkGetPipelineBinaryDataKHR GetPipelineBinaryDataKHR; PFN_vkReleaseCapturedPipelineDataKHR ReleaseCapturedPipelineDataKHR; + // ---- VK_KHR_swapchain_maintenance1 extension commands + PFN_vkReleaseSwapchainImagesKHR ReleaseSwapchainImagesKHR; + // ---- VK_KHR_line_rasterization extension commands PFN_vkCmdSetLineStippleKHR CmdSetLineStippleKHR; diff --git a/loader/generated/vk_loader_extensions.c b/loader/generated/vk_loader_extensions.c index dc80940a8..4e4b9ad76 100644 --- a/loader/generated/vk_loader_extensions.c +++ b/loader/generated/vk_loader_extensions.c @@ -767,6 +767,9 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_extension_dispatch_table(struct lo table->GetPipelineBinaryDataKHR = (PFN_vkGetPipelineBinaryDataKHR)gdpa(dev, "vkGetPipelineBinaryDataKHR"); table->ReleaseCapturedPipelineDataKHR = (PFN_vkReleaseCapturedPipelineDataKHR)gdpa(dev, "vkReleaseCapturedPipelineDataKHR"); + // ---- VK_KHR_swapchain_maintenance1 extension commands + table->ReleaseSwapchainImagesKHR = (PFN_vkReleaseSwapchainImagesKHR)gdpa(dev, "vkReleaseSwapchainImagesKHR"); + // ---- VK_KHR_line_rasterization extension commands table->CmdSetLineStippleKHR = (PFN_vkCmdSetLineStippleKHR)gdpa(dev, "vkCmdSetLineStippleKHR"); @@ -2726,6 +2729,9 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (!strcmp(name, "GetPipelineBinaryDataKHR")) return (void *)table->GetPipelineBinaryDataKHR; if (!strcmp(name, "ReleaseCapturedPipelineDataKHR")) return (void *)table->ReleaseCapturedPipelineDataKHR; + // ---- VK_KHR_swapchain_maintenance1 extension commands + if (!strcmp(name, "ReleaseSwapchainImagesKHR")) return (void *)table->ReleaseSwapchainImagesKHR; + // ---- VK_KHR_line_rasterization extension commands if (!strcmp(name, "CmdSetLineStippleKHR")) return (void *)table->CmdSetLineStippleKHR; @@ -5291,6 +5297,22 @@ VKAPI_ATTR VkResult VKAPI_CALL ReleaseCapturedPipelineDataKHR( } +// ---- VK_KHR_swapchain_maintenance1 extension trampoline/terminators + +VKAPI_ATTR VkResult VKAPI_CALL ReleaseSwapchainImagesKHR( + VkDevice device, + const VkReleaseSwapchainImagesInfoKHR* pReleaseInfo) { + const VkLayerDispatchTable *disp = loader_get_dispatch(device); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkReleaseSwapchainImagesKHR: Invalid device " + "[VUID-vkReleaseSwapchainImagesKHR-device-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + return disp->ReleaseSwapchainImagesKHR(device, pReleaseInfo); +} + + // ---- VK_KHR_cooperative_matrix extension trampoline/terminators VKAPI_ATTR VkResult VKAPI_CALL GetPhysicalDeviceCooperativeMatrixPropertiesKHR( @@ -7736,7 +7758,7 @@ VKAPI_ATTR void VKAPI_CALL GetImageSubresourceLayout2EXT( VKAPI_ATTR VkResult VKAPI_CALL ReleaseSwapchainImagesEXT( VkDevice device, - const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { + const VkReleaseSwapchainImagesInfoKHR* pReleaseInfo) { const VkLayerDispatchTable *disp = loader_get_dispatch(device); if (NULL == disp) { loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, @@ -11742,6 +11764,12 @@ bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *na return true; } + // ---- VK_KHR_swapchain_maintenance1 extension commands + if (!strcmp("vkReleaseSwapchainImagesKHR", name)) { + *addr = (void *)ReleaseSwapchainImagesKHR; + return true; + } + // ---- VK_KHR_cooperative_matrix extension commands if (!strcmp("vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR", name)) { *addr = (void *)GetPhysicalDeviceCooperativeMatrixPropertiesKHR; @@ -13534,6 +13562,9 @@ void fill_out_enabled_instance_extensions(uint32_t extension_count, const char * // ---- VK_KHR_portability_enumeration extension commands else if (0 == strcmp(extension_list[i], VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) { enables->khr_portability_enumeration = 1; } + // ---- VK_KHR_surface_maintenance1 extension commands + else if (0 == strcmp(extension_list[i], VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME)) { enables->khr_surface_maintenance1 = 1; } + // ---- VK_EXT_debug_report extension commands else if (0 == strcmp(extension_list[i], VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) { enables->ext_debug_report = 1; } @@ -14023,6 +14054,7 @@ const char *const LOADER_INSTANCE_EXTENSIONS[] = { VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME, VK_KHR_SURFACE_PROTECTED_CAPABILITIES_EXTENSION_NAME, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, + VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_EXTENSION_NAME, #if defined(VK_USE_PLATFORM_GGP) VK_GGP_STREAM_DESCRIPTOR_SURFACE_EXTENSION_NAME, diff --git a/loader/generated/vk_loader_extensions.h b/loader/generated/vk_loader_extensions.h index 9bb6d3b41..b8c55aa49 100644 --- a/loader/generated/vk_loader_extensions.h +++ b/loader/generated/vk_loader_extensions.h @@ -526,6 +526,7 @@ struct loader_instance_extension_enable_list { uint8_t khr_get_display_properties2; uint8_t khr_surface_protected_capabilities; uint8_t khr_portability_enumeration; + uint8_t khr_surface_maintenance1; uint8_t ext_debug_report; #if defined(VK_USE_PLATFORM_GGP) uint8_t ggp_stream_descriptor_surface; diff --git a/loader/loader.rc b/loader/loader.rc index 705e8dc60..242a54f79 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 320, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.320.Dev Build" +#define VER_FILE_VERSION 1, 4, 321, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.321.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/loader/wsi.c b/loader/wsi.c index 6308c5f5b..af821361b 100644 --- a/loader/wsi.c +++ b/loader/wsi.c @@ -2585,58 +2585,54 @@ vkGetPhysicalDeviceSurfaceCapabilities2KHR(VkPhysicalDevice physicalDevice, cons return disp->GetPhysicalDeviceSurfaceCapabilities2KHR(unwrapped_phys_dev, pSurfaceInfo, pSurfaceCapabilities); } -void emulate_VK_EXT_surface_maintenance1(struct loader_icd_term *icd_term, const VkPhysicalDeviceSurfaceInfo2KHR *pSurfaceInfo, +void emulate_VK_KHR_surface_maintenance1(const VkPhysicalDeviceSurfaceInfo2KHR *pSurfaceInfo, VkSurfaceCapabilities2KHR *pSurfaceCapabilities) { - // Because VK_EXT_surface_maintenance1 is an instance extension, applications will use it to query info on drivers which do + // Because VK_KHR_surface_maintenance1 is an instance extension, applications will use it to query info on drivers which do // not support the extension. Thus we need to emulate the driver filling out the structs in that case. - if (!icd_term->enabled_instance_extensions.ext_surface_maintenance1) { - VkPresentModeKHR present_mode = VK_PRESENT_MODE_MAX_ENUM_KHR; - const void *void_pNext = pSurfaceInfo->pNext; - while (void_pNext) { - VkBaseOutStructure out_structure = {0}; - memcpy(&out_structure, void_pNext, sizeof(VkBaseOutStructure)); - if (out_structure.sType == VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT) { - VkSurfacePresentModeEXT *surface_present_mode = (VkSurfacePresentModeEXT *)void_pNext; - present_mode = surface_present_mode->presentMode; - } - void_pNext = out_structure.pNext; - } - // If no VkSurfacePresentModeEXT was present, return - if (present_mode == VK_PRESENT_MODE_MAX_ENUM_KHR) { - return; + VkPresentModeKHR present_mode = VK_PRESENT_MODE_MAX_ENUM_KHR; + const void *void_pNext = pSurfaceInfo->pNext; + while (void_pNext) { + VkBaseOutStructure out_structure = {0}; + memcpy(&out_structure, void_pNext, sizeof(VkBaseOutStructure)); + if (out_structure.sType == VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_KHR) { + VkSurfacePresentModeKHR *surface_present_mode = (VkSurfacePresentModeKHR *)void_pNext; + present_mode = surface_present_mode->presentMode; } - - void_pNext = pSurfaceCapabilities->pNext; - while (void_pNext) { - VkBaseOutStructure out_structure = {0}; - memcpy(&out_structure, void_pNext, sizeof(VkBaseOutStructure)); - if (out_structure.sType == VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT) { - VkSurfacePresentModeCompatibilityEXT *surface_present_mode_compatibility = - (VkSurfacePresentModeCompatibilityEXT *)void_pNext; - if (surface_present_mode_compatibility->pPresentModes) { - if (surface_present_mode_compatibility->presentModeCount != 0) { - surface_present_mode_compatibility->pPresentModes[0] = present_mode; - surface_present_mode_compatibility->presentModeCount = 1; - } - } else { + void_pNext = out_structure.pNext; + } + // If no VkSurfacePresentModeKHR was present, return + if (present_mode == VK_PRESENT_MODE_MAX_ENUM_KHR) { + return; + } + + void_pNext = pSurfaceCapabilities->pNext; + while (void_pNext) { + VkBaseOutStructure out_structure = {0}; + memcpy(&out_structure, void_pNext, sizeof(VkBaseOutStructure)); + if (out_structure.sType == VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_KHR) { + VkSurfacePresentModeCompatibilityKHR *surface_present_mode_compatibility = + (VkSurfacePresentModeCompatibilityKHR *)void_pNext; + if (surface_present_mode_compatibility->pPresentModes) { + if (surface_present_mode_compatibility->presentModeCount != 0) { + surface_present_mode_compatibility->pPresentModes[0] = present_mode; surface_present_mode_compatibility->presentModeCount = 1; } - - } else if (out_structure.sType == VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT) { - // Because there is no way to fill out the information faithfully, set scaled max/min image extent to the - // surface capabilities max/min extent and the rest to zero. - VkSurfacePresentScalingCapabilitiesEXT *surface_present_scaling_capabilities = - (VkSurfacePresentScalingCapabilitiesEXT *)void_pNext; - surface_present_scaling_capabilities->supportedPresentScaling = 0; - surface_present_scaling_capabilities->supportedPresentGravityX = 0; - surface_present_scaling_capabilities->supportedPresentGravityY = 0; - surface_present_scaling_capabilities->maxScaledImageExtent = - pSurfaceCapabilities->surfaceCapabilities.maxImageExtent; - surface_present_scaling_capabilities->minScaledImageExtent = - pSurfaceCapabilities->surfaceCapabilities.minImageExtent; + } else { + surface_present_mode_compatibility->presentModeCount = 1; } - void_pNext = out_structure.pNext; + + } else if (out_structure.sType == VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_KHR) { + // Because there is no way to fill out the information faithfully, set scaled max/min image extent to the + // surface capabilities max/min extent and the rest to zero. + VkSurfacePresentScalingCapabilitiesKHR *surface_present_scaling_capabilities = + (VkSurfacePresentScalingCapabilitiesKHR *)void_pNext; + surface_present_scaling_capabilities->supportedPresentScaling = 0; + surface_present_scaling_capabilities->supportedPresentGravityX = 0; + surface_present_scaling_capabilities->supportedPresentGravityY = 0; + surface_present_scaling_capabilities->maxScaledImageExtent = pSurfaceCapabilities->surfaceCapabilities.maxImageExtent; + surface_present_scaling_capabilities->minScaledImageExtent = pSurfaceCapabilities->surfaceCapabilities.minImageExtent; } + void_pNext = out_structure.pNext; } } @@ -2684,8 +2680,9 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_GetPhysicalDeviceSurfaceCapabilities2K // Because VK_EXT_surface_maintenance1 is an instance extension, applications will use it to query info on drivers which do // not support the extension. Thus we need to emulate the driver filling out the structs in that case. - if (!icd_term->enabled_instance_extensions.ext_surface_maintenance1) { - emulate_VK_EXT_surface_maintenance1(icd_term, pSurfaceInfo, pSurfaceCapabilities); + if (!icd_term->enabled_instance_extensions.khr_surface_maintenance1 && + !icd_term->enabled_instance_extensions.ext_surface_maintenance1) { + emulate_VK_KHR_surface_maintenance1(pSurfaceInfo, pSurfaceCapabilities); } return res; @@ -2708,7 +2705,10 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_GetPhysicalDeviceSurfaceCapabilities2K VkResult res = icd_term->dispatch.GetPhysicalDeviceSurfaceCapabilitiesKHR(phys_dev_term->phys_dev, surface, &pSurfaceCapabilities->surfaceCapabilities); - emulate_VK_EXT_surface_maintenance1(icd_term, pSurfaceInfo, pSurfaceCapabilities); + if (!icd_term->enabled_instance_extensions.khr_surface_maintenance1 && + !icd_term->enabled_instance_extensions.ext_surface_maintenance1) { + emulate_VK_KHR_surface_maintenance1(pSurfaceInfo, pSurfaceCapabilities); + } return res; } } diff --git a/scripts/known_good.json b/scripts/known_good.json index a69f843e5..836385048 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.320" + "commit": "v1.4.321" }, { "name": "googletest", diff --git a/tests/framework/layer/generated/vk_dispatch_table_helper.h b/tests/framework/layer/generated/vk_dispatch_table_helper.h index f8379812b..498352781 100644 --- a/tests/framework/layer/generated/vk_dispatch_table_helper.h +++ b/tests/framework/layer/generated/vk_dispatch_table_helper.h @@ -361,6 +361,7 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->GetPipelineKeyKHR = (PFN_vkGetPipelineKeyKHR)gpa(device, "vkGetPipelineKeyKHR"); table->GetPipelineBinaryDataKHR = (PFN_vkGetPipelineBinaryDataKHR)gpa(device, "vkGetPipelineBinaryDataKHR"); table->ReleaseCapturedPipelineDataKHR = (PFN_vkReleaseCapturedPipelineDataKHR)gpa(device, "vkReleaseCapturedPipelineDataKHR"); + table->ReleaseSwapchainImagesKHR = (PFN_vkReleaseSwapchainImagesKHR)gpa(device, "vkReleaseSwapchainImagesKHR"); table->CmdSetLineStippleKHR = (PFN_vkCmdSetLineStippleKHR)gpa(device, "vkCmdSetLineStippleKHR"); table->GetCalibratedTimestampsKHR = (PFN_vkGetCalibratedTimestampsKHR)gpa(device, "vkGetCalibratedTimestampsKHR"); table->CmdBindDescriptorSets2KHR = (PFN_vkCmdBindDescriptorSets2KHR)gpa(device, "vkCmdBindDescriptorSets2KHR"); From 235d1d2cf617af03a2ecbf6e951287595138feda Mon Sep 17 00:00:00 2001 From: Mike Schuchardt Date: Mon, 14 Jul 2025 14:49:38 -0700 Subject: [PATCH 05/65] build: Update to header 1.4.322 --- CMakeLists.txt | 2 +- loader/loader.rc | 4 ++-- scripts/known_good.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a30752492..7963186ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.321 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.322 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/loader.rc b/loader/loader.rc index 242a54f79..3bd3864cf 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 321, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.321.Dev Build" +#define VER_FILE_VERSION 1, 4, 322, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.322.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index 836385048..505b47ba2 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.321" + "commit": "v1.4.322" }, { "name": "googletest", From 0817c639631fbafd1b65c03f8dced223ad65c400 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 2 May 2025 12:57:16 -0500 Subject: [PATCH 06/65] Create prepending variants of add_if_manifest_file & string funcs --- loader/loader.c | 67 ++++++++++++++++++++++++++++++++++++++++--------- loader/loader.h | 17 ++++++++++--- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 9064cd633..0e3db1b84 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -305,14 +305,13 @@ VkResult create_string_list(const struct loader_instance *inst, uint32_t allocat return VK_SUCCESS; } -VkResult append_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, char *str) { - assert(string_list && str); +VkResult incrase_str_capacity_by_at_least_one(const struct loader_instance *inst, struct loader_string_list *string_list) { + assert(string_list); if (string_list->allocated_count == 0) { string_list->allocated_count = 32; string_list->list = loader_instance_heap_calloc(inst, sizeof(char *) * string_list->allocated_count, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); if (NULL == string_list->list) { - loader_instance_heap_free(inst, str); // Must clean up in case of failure return VK_ERROR_OUT_OF_HOST_MEMORY; } } else if (string_list->count + 1 > string_list->allocated_count) { @@ -320,15 +319,38 @@ VkResult append_str_to_string_list(const struct loader_instance *inst, struct lo string_list->list = loader_instance_heap_realloc(inst, string_list->list, sizeof(char *) * string_list->allocated_count, sizeof(char *) * new_allocated_count, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); if (NULL == string_list->list) { - loader_instance_heap_free(inst, str); // Must clean up in case of failure return VK_ERROR_OUT_OF_HOST_MEMORY; } string_list->allocated_count *= 2; } + return VK_SUCCESS; +} + +VkResult append_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, char *str) { + assert(string_list && str); + VkResult res = incrase_str_capacity_by_at_least_one(inst, string_list); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + loader_instance_heap_free(inst, str); // Must clean up in case of failure + return res; + } string_list->list[string_list->count++] = str; return VK_SUCCESS; } +VkResult prepend_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, char *str) { + assert(string_list && str); + VkResult res = incrase_str_capacity_by_at_least_one(inst, string_list); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + loader_instance_heap_free(inst, str); // Must clean up in case of failure + return res; + } + // Shift everything down one + void *ptr_to_list = memmove(string_list->list + 1, string_list->list, sizeof(char *) * string_list->count); + if (ptr_to_list) string_list->list[0] = str; // Write new string to start of list + string_list->count++; + return VK_SUCCESS; +} + VkResult copy_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, const char *str, size_t str_len) { assert(string_list && str); @@ -341,6 +363,18 @@ VkResult copy_str_to_string_list(const struct loader_instance *inst, struct load return append_str_to_string_list(inst, string_list, new_str); } +VkResult copy_str_to_start_of_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, + const char *str, size_t str_len) { + assert(string_list && str); + char *new_str = loader_instance_heap_calloc(inst, sizeof(char *) * str_len + 1, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == new_str) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + loader_strncpy(new_str, sizeof(char *) * str_len + 1, str, str_len); + new_str[str_len] = '\0'; + return prepend_str_to_string_list(inst, string_list, new_str); +} + void free_string_list(const struct loader_instance *inst, struct loader_string_list *string_list) { assert(string_list); if (string_list->list) { @@ -3002,10 +3036,8 @@ void copy_data_file_info(const char *cur_path, const char *relative_path, size_t } } -// If the file found is a manifest file name, add it to the out_files manifest list. +// If the file found is a manifest file name, add it to the end of out_files manifest list. VkResult add_if_manifest_file(const struct loader_instance *inst, const char *file_name, struct loader_string_list *out_files) { - VkResult vk_result = VK_SUCCESS; - assert(NULL != file_name && "add_if_manifest_file: Received NULL pointer for file_name"); assert(NULL != out_files && "add_if_manifest_file: Received NULL pointer for out_files"); @@ -3014,15 +3046,26 @@ VkResult add_if_manifest_file(const struct loader_instance *inst, const char *fi const char *name_suffix = file_name + name_len - 5; if (!is_json(name_suffix, name_len)) { // Use incomplete to indicate invalid name, but to keep going. - vk_result = VK_INCOMPLETE; - goto out; + return VK_INCOMPLETE; } - vk_result = copy_str_to_string_list(inst, out_files, file_name, name_len); + return copy_str_to_string_list(inst, out_files, file_name, name_len); +} -out: +// If the file found is a manifest file name, add it to the start of the out_files manifest list. +VkResult prepend_if_manifest_file(const struct loader_instance *inst, const char *file_name, struct loader_string_list *out_files) { + assert(NULL != file_name && "prepend_if_manifest_file: Received NULL pointer for file_name"); + assert(NULL != out_files && "prepend_if_manifest_file: Received NULL pointer for out_files"); - return vk_result; + // Look for files ending with ".json" suffix + size_t name_len = strlen(file_name); + const char *name_suffix = file_name + name_len - 5; + if (!is_json(name_suffix, name_len)) { + // Use incomplete to indicate invalid name, but to keep going. + return VK_INCOMPLETE; + } + + return copy_str_to_start_of_string_list(inst, out_files, file_name, name_len); } // Add any files found in the search_path. If any path in the search path points to a specific JSON, attempt to diff --git a/loader/loader.h b/loader/loader.h index a5527b964..c2038ab79 100644 --- a/loader/loader.h +++ b/loader/loader.h @@ -110,14 +110,23 @@ VkResult loader_copy_to_new_str(const struct loader_instance *inst, const char * // Allocate a loader_string_list with enough space for allocated_count strings inside of it VkResult create_string_list(const struct loader_instance *inst, uint32_t allocated_count, struct loader_string_list *string_list); // Resize if there isn't enough space, then add the string str to the end of the loader_string_list -// This function takes ownership of the str passed in - but only when it succeeds +// This function takes ownership of the str passed in VkResult append_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, char *str); -// Resize if there isn't enough space, then copy the string str to a new string the end of the loader_string_list +// Resize if there isn't enough space, then add the string str to the start of the loader_string_list +// This function takes ownership of the str passed in +VkResult prepend_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, char *str); +// Copy the string str to a new string and append it to string_list, resizing string_list if there isn't enough space. // This function does not take ownership of the string, it merely copies it. -// This function appends a null terminator to the string automatically +// This function automatically appends a null terminator to the string being copied // The str_len parameter does not include the null terminator VkResult copy_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, const char *str, size_t str_len); +// Copy the string str to a new string and prepend it to string_list, resizing string_list if there isn't enough space. +// This function does not take ownership of the string, it merely copies it. +// This function automatically appends a null terminator to the string being copied +// The str_len parameter does not include the null terminator +VkResult copy_str_to_start_of_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, + const char *str, size_t str_len); // Free any string inside of loader_string_list and then free the list itself void free_string_list(const struct loader_instance *inst, struct loader_string_list *string_list); @@ -214,6 +223,8 @@ void unload_drivers_without_physical_devices(struct loader_instance *inst); VkStringErrorFlags vk_string_validate(const int max_length, const char *char_array); char *loader_get_next_path(char *path); +VkResult add_if_manifest_file(const struct loader_instance *inst, const char *file_name, struct loader_string_list *out_files); +VkResult prepend_if_manifest_file(const struct loader_instance *inst, const char *file_name, struct loader_string_list *out_files); VkResult add_data_files(const struct loader_instance *inst, char *search_path, struct loader_string_list *out_files, bool use_first_found_manifest); From 4775d547fa91def14398797bec5c160a8dde9536 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 5 May 2025 13:35:06 -0500 Subject: [PATCH 07/65] Add 30 minute timeout to github actions --- .github/workflows/build.yml | 222 +++++++++++++++++++----------------- 1 file changed, 119 insertions(+), 103 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44c7cc39c..0e8542cb7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,6 +37,7 @@ jobs: linux: needs: codegen runs-on: ${{matrix.os}} + timeout-minutes: 30 strategy: matrix: compiler: [ {cc: gcc, cxx: g++}, {cc: clang, cxx: clang++} ] @@ -74,6 +75,7 @@ jobs: codegen: runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - run: scripts/update_deps.py --dir ext --no-build @@ -82,6 +84,7 @@ jobs: linux-no-asm: needs: codegen runs-on: ubuntu-24.04 + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - run: sudo apt update @@ -102,6 +105,7 @@ jobs: linux-32: needs: codegen runs-on: ubuntu-24.04 + timeout-minutes: 30 strategy: matrix: config: [ Debug, Release ] @@ -140,6 +144,7 @@ jobs: linux-32-no-asm: needs: codegen runs-on: ubuntu-24.04 + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -175,6 +180,7 @@ jobs: # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well needs: linux-no-asm runs-on: windows-latest + timeout-minutes: 30 strategy: matrix: arch: [ Win32, x64 ] @@ -196,6 +202,7 @@ jobs: # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well needs: linux-no-asm runs-on: windows-latest + timeout-minutes: 30 strategy: matrix: arch: [ Win32, x64 ] @@ -215,13 +222,14 @@ jobs: # Test both clang and clang-cl (Chromium project uses clang-cl) windows_clang: # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well - needs: linux-no-asm - runs-on: windows-2022 - strategy: - matrix: - compiler: [ clang, clang-cl ] - config: [ Debug, Release ] - steps: + needs: linux-no-asm + runs-on: windows-2022 + timeout-minutes: 30 + strategy: + matrix: + compiler: [ clang, clang-cl ] + config: [ Debug, Release ] + steps: - uses: actions/checkout@v4 - uses: ilammy/msvc-dev-cmd@v1 - run: | @@ -241,6 +249,7 @@ jobs: # Mac is 10x expensive to run on GitHub machines, so only run if we know something else passed as well needs: windows_clang runs-on: macos-13 + timeout-minutes: 30 strategy: matrix: config: [ Debug, Release ] @@ -267,14 +276,15 @@ jobs: - run: ctest --parallel --output-on-failure --test-dir build/ apple-cross-compile: - # Mac is 10x expensive to run on GitHub machines, so only run if we know something else passed as well - needs: windows_clang - name: ${{ matrix.CMAKE_SYSTEM_NAME }} - runs-on: macos-13 - strategy: - matrix: - CMAKE_SYSTEM_NAME: [ iOS, tvOS ] - steps: + # Mac is 10x expensive to run on GitHub machines, so only run if we know something else passed as well + needs: windows_clang + name: ${{ matrix.CMAKE_SYSTEM_NAME }} + runs-on: macos-13 + timeout-minutes: 30 + strategy: + matrix: + CMAKE_SYSTEM_NAME: [ iOS, tvOS ] + steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: @@ -303,6 +313,7 @@ jobs: needs: windows_clang name: "Universal Binary Testing (STATIC ${{ matrix.static }}) w/ ${{ matrix.generator }}" runs-on: macos-latest + timeout-minutes: 30 strategy: matrix: static: [ 'ON', 'OFF' ] @@ -333,99 +344,104 @@ jobs: vtool -show-build /tmp/lib/libvulkan.dylib | grep 'architecture arm64' chromium: - needs: codegen - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: scripts/gn/gn.py + needs: codegen + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - run: scripts/gn/gn.py mingw: - # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well - if: false # Disabled due to issues with msys2 making CMake unable to find a working compiler - needs: linux-no-asm - runs-on: windows-2022 - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: Setup uasm - run: | - C:/msys64/usr/bin/pacman -Sy --noconfirm --needed mingw-w64-x86_64-uasm - printf '%s\n' 'C:/msys64/mingw64/bin' >> $GITHUB_PATH - - name: UASM Check - run: uasm -? - - run: | - cmake -S. -B build \ - -D UPDATE_DEPS=ON \ - -D CMAKE_BUILD_TYPE=Release \ - -D BUILD_WERROR=ON \ - -G Ninja - - run: cmake --build build - - run: cmake --install build --prefix /tmp + # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well + needs: linux-no-asm + if: false # Disabled due to issues with msys2 making CMake unable to find a working compiler + runs-on: windows-2022 + timeout-minutes: 30 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Setup uasm + run: | + C:/msys64/usr/bin/pacman -Sy --noconfirm --needed mingw-w64-x86_64-uasm + printf '%s\n' 'C:/msys64/mingw64/bin' >> $GITHUB_PATH + - name: UASM Check + run: uasm -? + - run: | + cmake -S. -B build \ + -D UPDATE_DEPS=ON \ + -D CMAKE_BUILD_TYPE=Release \ + -D BUILD_WERROR=ON \ + -G Ninja + - run: cmake --build build + - run: cmake --install build --prefix /tmp mingw-use-gas: - # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well - needs: linux-no-asm - runs-on: windows-2022 - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - run: | - cmake -S. -B build \ - -D UPDATE_DEPS=ON \ - -D CMAKE_BUILD_TYPE=Release \ - -D BUILD_WERROR=ON \ - -D USE_GAS=ON \ - -G Ninja - - run: cmake --build build - - run: cmake --install build --prefix /tmp + # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well + needs: linux-no-asm + runs-on: windows-2022 + timeout-minutes: 30 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - run: | + cmake -S. -B build \ + -D UPDATE_DEPS=ON \ + -D CMAKE_BUILD_TYPE=Release \ + -D BUILD_WERROR=ON \ + -D USE_GAS=ON \ + -G Ninja + - run: cmake --build build + - run: cmake --install build --prefix /tmp mingw-no-asm: - # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well - needs: linux-no-asm - runs-on: windows-2022 - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - # Make sure this doesn't fail even without explicitly setting '-D USE_MASM=OFF' and without uasm - - run: | - cmake -S. -B build \ - -D UPDATE_DEPS=ON \ - -D CMAKE_BUILD_TYPE=Release \ - -D BUILD_WERROR=ON \ - -G Ninja - - run: cmake --build build - - run: cmake --install build --prefix /tmp + # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well + needs: linux-no-asm + runs-on: windows-2022 + timeout-minutes: 30 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + # Make sure this doesn't fail even without explicitly setting '-D USE_MASM=OFF' and without uasm + - run: | + cmake -S. -B build \ + -D UPDATE_DEPS=ON \ + -D CMAKE_BUILD_TYPE=Release \ + -D BUILD_WERROR=ON \ + -G Ninja + - run: cmake --build build + - run: cmake --install build --prefix /tmp mingw-no-asm-explicit: - # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well - needs: linux-no-asm - runs-on: windows-2022 - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - run: | - cmake -S. -B build \ - -D UPDATE_DEPS=ON \ - -D CMAKE_BUILD_TYPE=Release \ - -D BUILD_WERROR=ON \ - -D USE_MASM=OFF \ - -G Ninja - - run: cmake --build build - - run: cmake --install build --prefix /tmp + # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well + needs: linux-no-asm + runs-on: windows-2022 + timeout-minutes: 30 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + - run: | + cmake -S. -B build \ + -D UPDATE_DEPS=ON \ + -D CMAKE_BUILD_TYPE=Release \ + -D BUILD_WERROR=ON \ + -D USE_MASM=OFF \ + -G Ninja + - run: cmake --build build + - run: cmake --install build --prefix /tmp From 1d2a2fdb5c574fb6fdb17954a88cad60673ca654 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 2 May 2025 12:59:10 -0500 Subject: [PATCH 08/65] Add device and drivers fields to settings file The loader settings file now can specify additional drivers to include, whether to use those drivers exclusively, and filter/sort the VkPhysicalDevices using the settings file provided list of deviceUUID's. This lets the settings file control which drivers and/or devices are reported by the loader, giving greater control over the runtime behavior of the loader. device_configurations is an array of JSON objects which contain two fields each, deviceUUID and deviceName. deviceUUID is an array of 16 uint8_t's (expressed simply as numbers) which takes the value of the VkPhysicalDeviceVulkan11Properties::deviceUUID struct member. deviceName is a string taken from VkPhysicalDeviceProperties::deviceName. Only deviceUUID is used to filter and sort VkPhysicalDevices, deviceName is for convenience and ease of understanding the JSON loader settings file. additional_drivers is an array of JSON object which contain just one field, path, which is a path to the Manifest ICD JSON file for a driver. additional_drivers_use_exclusively is a boolean field indicating whether the loader should: if false (default if unspecified) - look for drivers in the normal locations and append the additional_drivers to the back of the driver list. if true - only use drivers in the additional_drivers, ignoring all other driver search locations. --- loader/loader.c | 190 ++++++++++-- loader/loader.h | 3 + loader/settings.c | 314 +++++++++++++++++-- loader/settings.h | 25 ++ tests/framework/data/fuzzer_output.json | 5 +- tests/framework/icd/test_icd.cpp | 4 + tests/framework/icd/test_icd.h | 1 + tests/framework/json_writer.h | 22 ++ tests/framework/test_environment.cpp | 36 ++- tests/framework/test_environment.h | 13 + tests/framework/test_util.h | 2 + tests/loader_settings_tests.cpp | 386 +++++++++++++++++++++++- 12 files changed, 945 insertions(+), 56 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 0e3db1b84..9832bb7b0 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -3257,12 +3257,14 @@ VkResult read_data_files_in_search_paths(const struct loader_instance *inst, enu switch (manifest_type) { case LOADER_DATA_FILE_MANIFEST_DRIVER: - override_env = loader_secure_getenv(VK_DRIVER_FILES_ENV_VAR, inst); - if (NULL == override_env) { - // Not there, so fall back to the old name - override_env = loader_secure_getenv(VK_ICD_FILENAMES_ENV_VAR, inst); + if (loader_settings_should_use_driver_environment_variables(inst)) { + override_env = loader_secure_getenv(VK_DRIVER_FILES_ENV_VAR, inst); + if (NULL == override_env) { + // Not there, so fall back to the old name + override_env = loader_secure_getenv(VK_ICD_FILENAMES_ENV_VAR, inst); + } + additional_env = loader_secure_getenv(VK_ADDITIONAL_DRIVER_FILES_ENV_VAR, inst); } - additional_env = loader_secure_getenv(VK_ADDITIONAL_DRIVER_FILES_ENV_VAR, inst); #if COMMON_UNIX_PLATFORMS relative_location = VK_DRIVERS_INFO_RELATIVE_DIR; #endif @@ -3801,18 +3803,26 @@ VkResult loader_icd_scan(const struct loader_instance *inst, struct loader_icd_t goto out; } - // Parse the filter environment variables to determine if we have any special behavior - res = parse_generic_filter_environment_var(inst, VK_DRIVERS_SELECT_ENV_VAR, &select_filter); - if (VK_SUCCESS != res) { - goto out; + if (loader_settings_should_use_driver_environment_variables(inst)) { + // Parse the filter environment variables to determine if we have any special behavior + res = parse_generic_filter_environment_var(inst, VK_DRIVERS_SELECT_ENV_VAR, &select_filter); + if (VK_SUCCESS != res) { + goto out; + } + res = parse_generic_filter_environment_var(inst, VK_DRIVERS_DISABLE_ENV_VAR, &disable_filter); + if (VK_SUCCESS != res) { + goto out; + } } - res = parse_generic_filter_environment_var(inst, VK_DRIVERS_DISABLE_ENV_VAR, &disable_filter); + + // Get a list of manifest files for ICDs + res = loader_get_data_files(inst, LOADER_DATA_FILE_MANIFEST_DRIVER, NULL, &manifest_files); if (VK_SUCCESS != res) { goto out; } - // Get a list of manifest files for ICDs - res = loader_get_data_files(inst, LOADER_DATA_FILE_MANIFEST_DRIVER, NULL, &manifest_files); + // Add any drivers provided by the loader settings file + res = loader_settings_get_additional_driver_files(inst, &manifest_files); if (VK_SUCCESS != res) { goto out; } @@ -5459,10 +5469,10 @@ VkResult loader_validate_device_extensions(struct loader_instance *this_instance // All named terminator_ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateInstance(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance) { - struct loader_icd_term *icd_term; - VkExtensionProperties *prop; + struct loader_icd_term *icd_term = NULL; + VkExtensionProperties *prop = NULL; char **filtered_extension_names = NULL; - VkInstanceCreateInfo icd_create_info; + VkInstanceCreateInfo icd_create_info = {0}; VkResult res = VK_SUCCESS; bool one_icd_successful = false; @@ -5650,7 +5660,7 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateInstance(const VkInstanceCreateI } // Create an instance, substituting the version to 1.0 if necessary - VkApplicationInfo icd_app_info; + VkApplicationInfo icd_app_info = {0}; const uint32_t api_variant = 0; const uint32_t api_version_1_0 = VK_API_VERSION_1_0; uint32_t icd_version_nopatch = @@ -5667,6 +5677,25 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateInstance(const VkInstanceCreateI icd_app_info.apiVersion = icd_version; icd_create_info.pApplicationInfo = &icd_app_info; } + + // If the settings file has device_configurations, we need to raise the ApiVersion drivers use to 1.1 if the driver + // supports 1.1 or higher. This allows 1.0 apps to use the device_configurations without the app having to set its own + // ApiVersion to 1.1 on its own. + if (ptr_instance->settings.settings_active && ptr_instance->settings.device_configuration_count > 0 && + icd_version >= VK_API_VERSION_1_1 && requested_version < VK_API_VERSION_1_1) { + if (NULL != pCreateInfo->pApplicationInfo) { + memcpy(&icd_app_info, pCreateInfo->pApplicationInfo, sizeof(VkApplicationInfo)); + } + icd_app_info.apiVersion = VK_API_VERSION_1_1; + icd_create_info.pApplicationInfo = &icd_app_info; + + loader_log( + ptr_instance, VULKAN_LOADER_INFO_BIT, 0, + "terminator_CreateInstance: Raising the VkApplicationInfo::apiVersion from 1.0 to 1.1 on driver \"%s\" so that " + "the loader settings file is able to use this driver in the device_configuration selection logic.", + icd_term->scanned_icd->lib_name); + } + icd_result = ptr_instance->icd_tramp_list.scanned_list[i].CreateInstance(&icd_create_info, pAllocator, &(icd_term->instance)); if (VK_ERROR_OUT_OF_HOST_MEMORY == icd_result) { @@ -6780,28 +6809,133 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_EnumeratePhysicalDevices(VkInstance in goto out; } - uint32_t copy_count = inst->phys_dev_count_term; - if (NULL != pPhysicalDevices) { - if (copy_count > *pPhysicalDeviceCount) { - copy_count = *pPhysicalDeviceCount; - loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, - "terminator_EnumeratePhysicalDevices : Trimming device count from %d to %d.", inst->phys_dev_count_term, - copy_count); - res = VK_INCOMPLETE; + if (inst->settings.settings_active && inst->settings.device_configuration_count > 0) { + // Use settings file device_configurations if present + if (NULL == pPhysicalDevices) { + // take the minimum of the settings configurations count and number of terminators + *pPhysicalDeviceCount = (inst->settings.device_configuration_count < inst->phys_dev_count_term) + ? inst->settings.device_configuration_count + : inst->phys_dev_count_term; + } else { + res = loader_apply_settings_device_configurations(inst, pPhysicalDeviceCount, pPhysicalDevices); } + } else { + // Otherwise just copy the physical devices up normally and pass it up the chain + uint32_t copy_count = inst->phys_dev_count_term; + if (NULL != pPhysicalDevices) { + if (copy_count > *pPhysicalDeviceCount) { + copy_count = *pPhysicalDeviceCount; + loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, + "terminator_EnumeratePhysicalDevices : Trimming device count from %d to %d.", inst->phys_dev_count_term, + copy_count); + res = VK_INCOMPLETE; + } - for (uint32_t i = 0; i < copy_count; i++) { - pPhysicalDevices[i] = (VkPhysicalDevice)inst->phys_devs_term[i]; + for (uint32_t i = 0; i < copy_count; i++) { + pPhysicalDevices[i] = (VkPhysicalDevice)inst->phys_devs_term[i]; + } } - } - *pPhysicalDeviceCount = copy_count; + *pPhysicalDeviceCount = copy_count; + } out: return res; } +// Apply the device_configurations in the settings file to the output VkPhysicalDeviceList. +// That means looking up each VkPhysicalDevice's deviceUUID, filtering using that, and putting them in the order of +// device_configurations in the settings file. +VkResult loader_apply_settings_device_configurations(struct loader_instance *inst, uint32_t *pPhysicalDeviceCount, + VkPhysicalDevice *pPhysicalDevices) { + bool *pd_supports_11 = loader_stack_alloc(inst->phys_dev_count_term * sizeof(bool)); + if (NULL == pd_supports_11) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + memset(pd_supports_11, 0, inst->phys_dev_count_term * sizeof(bool)); + + VkPhysicalDeviceProperties *pd_props = loader_stack_alloc(inst->phys_dev_count_term * sizeof(VkPhysicalDeviceProperties)); + if (NULL == pd_props) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + memset(pd_props, 0, inst->phys_dev_count_term * sizeof(VkPhysicalDeviceProperties)); + + VkPhysicalDeviceVulkan11Properties *pd_vulkan_11_props = + loader_stack_alloc(inst->phys_dev_count_term * sizeof(VkPhysicalDeviceVulkan11Properties)); + if (NULL == pd_vulkan_11_props) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + memset(pd_vulkan_11_props, 0, inst->phys_dev_count_term * sizeof(VkPhysicalDeviceVulkan11Properties)); + + for (uint32_t i = 0; i < inst->phys_dev_count_term; i++) { + pd_vulkan_11_props[i].sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; + + inst->phys_devs_term[i]->this_icd_term->dispatch.GetPhysicalDeviceProperties(inst->phys_devs_term[i]->phys_dev, + &pd_props[i]); + if (pd_props[i].apiVersion >= VK_API_VERSION_1_1) { + pd_supports_11[i] = true; + VkPhysicalDeviceProperties2 props2 = {0}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = (void *)&pd_vulkan_11_props[i]; + if (inst->phys_devs_term[i]->this_icd_term->dispatch.GetPhysicalDeviceProperties2) { + inst->phys_devs_term[i]->this_icd_term->dispatch.GetPhysicalDeviceProperties2(inst->phys_devs_term[i]->phys_dev, + &props2); + } + } + } + + // Loop over the setting's device configurations, find each VkPhysicalDevice which matches the deviceUUID given, add to the + // pPhysicalDevices output list. + uint32_t written_output_index = 0; + + for (uint32_t i = 0; i < inst->settings.device_configuration_count; i++) { + uint8_t *current_deviceUUID = inst->settings.device_configurations[i].deviceUUID; + bool configuration_found = false; + for (uint32_t j = 0; j < inst->phys_dev_count_term; j++) { + // Don't compare deviceUUID's if they have nothing, since we require deviceUUID's to effectively sort them. + if (!pd_supports_11[j]) { + continue; + } + if (memcmp(current_deviceUUID, pd_vulkan_11_props[j].deviceUUID, sizeof(uint8_t) * VK_UUID_SIZE) == 0) { + configuration_found = true; + // Catch when there are more device_configurations than space available in the output + if (written_output_index >= *pPhysicalDeviceCount) { + *pPhysicalDeviceCount = written_output_index; // write out how many were written + return VK_INCOMPLETE; + } + pPhysicalDevices[written_output_index++] = (VkPhysicalDevice)inst->phys_devs_term[j]; + loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, "Insert VkPhysicalDevice \"%s\" to the pPhysicalDevices list", + pd_props[j].deviceName); + break; + } + } + if (!configuration_found) { + uint8_t *id = current_deviceUUID; + // Log that this configuration was missing. + if (inst->settings.device_configurations[i].deviceName[0] != '\0') { + loader_log( + inst, VULKAN_LOADER_WARN_BIT, 0, + "loader_apply_settings_device_configurations: settings file contained device_configuration which does not " + "appear in the enumerated VkPhysicalDevices. Missing VkPhysicalDevice with deviceName: \"%s\" and deviceUUID: " + "%x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x", + inst->settings.device_configurations[i].deviceName, id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], + id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); + } else { + loader_log( + inst, VULKAN_LOADER_WARN_BIT, 0, + "loader_apply_settings_device_configurations: settings file contained device_configuration which does not " + "appear in the enumerated VkPhysicalDevices. Missing VkPhysicalDevice with deviceUUID: " + "%x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x", + id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], + id[15]); + } + } + } + *pPhysicalDeviceCount = written_output_index; // update with how many were written + return VK_SUCCESS; +} + VKAPI_ATTR VkResult VKAPI_CALL terminator_EnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties) { diff --git a/loader/loader.h b/loader/loader.h index c2038ab79..2ed309c0e 100644 --- a/loader/loader.h +++ b/loader/loader.h @@ -221,6 +221,9 @@ VkResult setup_loader_tramp_phys_dev_groups(struct loader_instance *inst, uint32 VkPhysicalDeviceGroupProperties *groups); void unload_drivers_without_physical_devices(struct loader_instance *inst); +VkResult loader_apply_settings_device_configurations(struct loader_instance *inst, uint32_t *pPhysicalDeviceCount, + VkPhysicalDevice *pPhysicalDevices); + VkStringErrorFlags vk_string_validate(const int max_length, const char *char_array); char *loader_get_next_path(char *path); VkResult add_if_manifest_file(const struct loader_instance *inst, const char *file_name, struct loader_string_list *out_files); diff --git a/loader/settings.c b/loader/settings.c index fce896e83..a28c34fae 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -44,13 +44,35 @@ void free_layer_configuration(const struct loader_instance* inst, loader_setting memset(layer_configuration, 0, sizeof(loader_settings_layer_configuration)); } +void free_driver_configuration(const struct loader_instance* inst, loader_settings_driver_configuration* driver_configuration) { + loader_instance_heap_free(inst, driver_configuration->path); + memset(driver_configuration, 0, sizeof(loader_settings_driver_configuration)); +} + +void free_device_configuration(const struct loader_instance* inst, loader_settings_device_configuration* device_configuration) { + (void)inst; + memset(device_configuration, 0, sizeof(loader_settings_device_configuration)); +} + void free_loader_settings(const struct loader_instance* inst, loader_settings* settings) { if (NULL != settings->layer_configurations) { for (uint32_t i = 0; i < settings->layer_configuration_count; i++) { free_layer_configuration(inst, &settings->layer_configurations[i]); } + loader_instance_heap_free(inst, settings->layer_configurations); + } + if (NULL != settings->additional_drivers) { + for (uint32_t i = 0; i < settings->additional_driver_count; i++) { + free_driver_configuration(inst, &settings->additional_drivers[i]); + } + loader_instance_heap_free(inst, settings->additional_drivers); + } + if (NULL != settings->device_configurations) { + for (uint32_t i = 0; i < settings->device_configuration_count; i++) { + free_device_configuration(inst, &settings->device_configurations[i]); + } + loader_instance_heap_free(inst, settings->device_configurations); } - loader_instance_heap_free(inst, settings->layer_configurations); loader_instance_heap_free(inst, settings->settings_file_path); memset(settings, 0, sizeof(loader_settings)); } @@ -207,6 +229,164 @@ VkResult parse_layer_configurations(const struct loader_instance* inst, cJSON* s return res; } +VkResult parse_additional_driver(const struct loader_instance* inst, cJSON* additional_driver_json, + loader_settings_driver_configuration* additional_driver) { + VkResult res = VK_SUCCESS; + res = loader_parse_json_string(additional_driver_json, "path", &(additional_driver->path)); + if (res != VK_SUCCESS) { + goto out; + } +out: + if (res != VK_SUCCESS) { + free_driver_configuration(inst, additional_driver); + } + return res; +} + +VkResult parse_additional_drivers(const struct loader_instance* inst, cJSON* settings_object, loader_settings* loader_settings) { + VkResult res = VK_SUCCESS; + + cJSON* additional_drivers_use_exclusively_json = + loader_cJSON_GetObjectItem(settings_object, "additional_drivers_use_exclusively"); + if (additional_drivers_use_exclusively_json && additional_drivers_use_exclusively_json->type == cJSON_True) { + loader_settings->additional_drivers_use_exclusively = true; + } + + cJSON* additional_drivers_json = loader_cJSON_GetObjectItem(settings_object, "additional_drivers"); + if (NULL == additional_drivers_json) { + return VK_SUCCESS; + } + + uint32_t additional_driver_count = loader_cJSON_GetArraySize(additional_drivers_json); + if (additional_driver_count == 0) { + return VK_SUCCESS; + } + + loader_settings->additional_driver_count = additional_driver_count; + + loader_settings->additional_drivers = loader_instance_heap_calloc( + inst, sizeof(loader_settings_layer_configuration) * additional_driver_count, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == loader_settings->additional_drivers) { + res = VK_ERROR_OUT_OF_HOST_MEMORY; + goto out; + } + + cJSON* driver = NULL; + size_t i = 0; + cJSON_ArrayForEach(driver, additional_drivers_json) { + if (driver->type != cJSON_Object) { + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + res = parse_additional_driver(inst, driver, &(loader_settings->additional_drivers[i++])); + if (VK_SUCCESS != res) { + goto out; + } + } +out: + if (res != VK_SUCCESS) { + if (loader_settings->additional_drivers) { + for (size_t index = 0; index < loader_settings->additional_driver_count; index++) { + free_driver_configuration(inst, &(loader_settings->additional_drivers[index])); + } + loader_settings->additional_driver_count = 0; + loader_instance_heap_free(inst, loader_settings->additional_drivers); + loader_settings->additional_drivers = NULL; + } + } + return res; +} + +VkResult parse_device_configuration(const struct loader_instance* inst, cJSON* device_configuration_json, + loader_settings_device_configuration* device_configuration) { + (void)inst; + VkResult res = VK_SUCCESS; + cJSON* deviceUUID_array = loader_cJSON_GetObjectItem(device_configuration_json, "deviceUUID"); + if (NULL == deviceUUID_array) { + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + if (VK_UUID_SIZE != loader_cJSON_GetArraySize(deviceUUID_array)) { + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + + cJSON* uuid_field = NULL; + size_t i = 0; + cJSON_ArrayForEach(uuid_field, deviceUUID_array) { + if (uuid_field->type != cJSON_Number) { + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + if (uuid_field->valueint < 0 || uuid_field->valueint > 255) { + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + device_configuration->deviceUUID[i] = (uint8_t)uuid_field->valueint; + i++; + } + + VkResult deviceNameRes = loader_parse_json_string_to_existing_str( + device_configuration_json, "deviceName", VK_MAX_PHYSICAL_DEVICE_NAME_SIZE, device_configuration->deviceName); + if (VK_ERROR_OUT_OF_HOST_MEMORY == deviceNameRes) { + res = deviceNameRes; + goto out; + } +out: + if (res != VK_SUCCESS) { + memset(device_configuration, 0, sizeof(loader_settings_device_configuration)); + } + return res; +} + +VkResult parse_device_configurations(const struct loader_instance* inst, cJSON* settings_object, loader_settings* loader_settings) { + VkResult res = VK_SUCCESS; + + cJSON* device_configurations = loader_cJSON_GetObjectItem(settings_object, "device_configurations"); + if (NULL == device_configurations) { + return VK_SUCCESS; + } + + uint32_t device_configuration_count = loader_cJSON_GetArraySize(device_configurations); + if (device_configuration_count == 0) { + return VK_SUCCESS; + } + + loader_settings->device_configuration_count = device_configuration_count; + + loader_settings->device_configurations = loader_instance_heap_calloc( + inst, sizeof(loader_settings_device_configuration) * device_configuration_count, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == loader_settings->device_configurations) { + res = VK_ERROR_OUT_OF_HOST_MEMORY; + goto out; + } + + cJSON* device = NULL; + size_t i = 0; + cJSON_ArrayForEach(device, device_configurations) { + if (device->type != cJSON_Object) { + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + res = parse_device_configuration(inst, device, &(loader_settings->device_configurations[i++])); + if (VK_SUCCESS != res) { + goto out; + } + } +out: + if (res != VK_SUCCESS) { + if (loader_settings->device_configurations) { + for (size_t index = 0; index < loader_settings->device_configuration_count; index++) { + free_device_configuration(inst, &(loader_settings->device_configurations[index])); + } + loader_settings->device_configuration_count = 0; + loader_instance_heap_free(inst, loader_settings->device_configurations); + loader_settings->device_configurations = NULL; + } + } + return res; +} + VkResult check_if_settings_path_exists(const struct loader_instance* inst, const char* base, const char* suffix, char** settings_file_path) { if (NULL == base || NULL == suffix) { @@ -248,6 +428,30 @@ VkResult get_unix_settings_path(const struct loader_instance* inst, char** setti settings_file_path); } +bool check_if_layer_configurations_are_equal(loader_settings_layer_configuration* a, loader_settings_layer_configuration* b) { + if (!a->name || !b->name || 0 != strcmp(a->name, b->name)) { + return false; + } + if (!a->path || !b->path || 0 != strcmp(a->path, b->path)) { + return false; + } + return a->control == b->control; +} + +bool check_if_driver_configurations_are_equal(loader_settings_driver_configuration* a, loader_settings_driver_configuration* b) { + if (!a->path || !b->path || 0 != strcmp(a->path, b->path)) { + return false; + } + return true; +} + +bool check_if_device_configurations_are_equal(loader_settings_device_configuration* a, loader_settings_device_configuration* b) { + for (uint32_t i = 0; i < VK_UUID_SIZE; i++) { + if (a->deviceUUID[i] != b->deviceUUID[i]) return false; + } + return true; +} + bool check_if_settings_are_equal(loader_settings* a, loader_settings* b) { // If either pointer is null, return true if (NULL == a || NULL == b) return false; @@ -256,19 +460,17 @@ bool check_if_settings_are_equal(loader_settings* a, loader_settings* b) { are_equal &= a->has_unordered_layer_location == b->has_unordered_layer_location; are_equal &= a->debug_level == b->debug_level; are_equal &= a->layer_configuration_count == b->layer_configuration_count; + are_equal &= a->additional_driver_count == b->additional_driver_count; + are_equal &= a->device_configuration_count == b->device_configuration_count; if (!are_equal) return false; for (uint32_t i = 0; i < a->layer_configuration_count && i < b->layer_configuration_count; i++) { - if (a->layer_configurations[i].name && b->layer_configurations[i].name) { - are_equal &= 0 == strcmp(a->layer_configurations[i].name, b->layer_configurations[i].name); - } else { - are_equal = false; - } - if (a->layer_configurations[i].path && b->layer_configurations[i].path) { - are_equal &= 0 == strcmp(a->layer_configurations[i].path, b->layer_configurations[i].path); - } else { - are_equal = false; - } - are_equal &= a->layer_configurations[i].control == b->layer_configurations[i].control; + are_equal &= check_if_layer_configurations_are_equal(&a->layer_configurations[i], &b->layer_configurations[i]); + } + for (uint32_t i = 0; i < a->additional_driver_count && i < b->additional_driver_count; i++) { + are_equal &= check_if_driver_configurations_are_equal(&a->additional_drivers[i], &b->additional_drivers[i]); + } + for (uint32_t i = 0; i < a->device_configuration_count && i < b->device_configuration_count; i++) { + are_equal &= check_if_device_configurations_are_equal(&a->device_configurations[i], &b->device_configurations[i]); } return are_equal; } @@ -302,6 +504,27 @@ void log_settings(const struct loader_instance* inst, loader_settings* settings) loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "Control: %s", loader_settings_layer_control_to_string(settings->layer_configurations[i].control)); } + if (settings->additional_driver_count > 0) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "----"); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "Use Additional Drivers Exclusively = %s", + settings->additional_drivers_use_exclusively ? "true" : "false"); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "Additional Driver Configurations count = %d", + settings->additional_driver_count); + for (uint32_t i = 0; i < settings->additional_driver_count; i++) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "---- Driver Configuration [%d] ----", i); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "Path: %s", settings->additional_drivers[i].path); + } + } + if (settings->device_configuration_count > 0) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "----"); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "Device Configurations count = %d", settings->device_configuration_count); + for (uint32_t i = 0; i < settings->device_configuration_count; i++) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "---- Device Configuration [%d] ----", i); + uint8_t* id = settings->device_configurations[i].deviceUUID; + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "deviceUUID: %x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x", id[0], id[1], id[2], + id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); + } + } loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "---------------------------------"); } @@ -457,8 +680,9 @@ VkResult get_loader_settings(const struct loader_instance* inst, loader_settings } } - res = parse_layer_configurations(inst, settings_to_use, loader_settings); - if (res != VK_SUCCESS) { + VkResult layer_configurations_res = parse_layer_configurations(inst, settings_to_use, loader_settings); + if (VK_ERROR_OUT_OF_HOST_MEMORY == layer_configurations_res) { + res = layer_configurations_res; goto out; } @@ -471,9 +695,29 @@ VkResult get_loader_settings(const struct loader_instance* inst, loader_settings } } - loader_settings->settings_file_path = settings_file_path; - settings_file_path = NULL; - loader_settings->settings_active = true; + VkResult additional_drivers_res = parse_additional_drivers(inst, settings_to_use, loader_settings); + if (VK_ERROR_OUT_OF_HOST_MEMORY == additional_drivers_res) { + res = additional_drivers_res; + goto out; + } + + VkResult device_configurations_res = parse_device_configurations(inst, settings_to_use, loader_settings); + if (VK_ERROR_OUT_OF_HOST_MEMORY == device_configurations_res) { + res = device_configurations_res; + goto out; + } + + // Only consider the settings active if there is at least one "setting" active. + // Those are either logging, layers, additional_drivers, or device_configurations. + if (loader_settings->debug_level != 0 || loader_settings->layer_configuration_count != 0 || + loader_settings->additional_driver_count != 0 || loader_settings->device_configuration_count != 0) { + loader_settings->settings_file_path = settings_file_path; + settings_file_path = NULL; + loader_settings->settings_active = true; + } else { + loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, + "vk_loader_settings.json file found at \"%s\" but did not contain any valid settings.", settings_file_path); + } out: if (NULL != json) { loader_cJSON_Delete(json); @@ -866,3 +1110,39 @@ VkResult enable_correct_layers_from_settings(const struct loader_instance* inst, out: return res; } + +VkResult loader_settings_get_additional_driver_files(const struct loader_instance* inst, struct loader_string_list* out_files) { + VkResult res = VK_SUCCESS; + + const loader_settings* settings = get_current_settings_and_lock(inst); + + if (NULL == settings || !settings->settings_active) { + goto out; + } + + if (settings->additional_drivers_use_exclusively) { + free_string_list(inst, out_files); + } + + for (uint32_t i = 0; i < settings->additional_driver_count; i++) { + res = prepend_if_manifest_file(inst, settings->additional_drivers[i].path, out_files); + } + +out: + release_current_settings_lock(inst); + return res; +} + +bool loader_settings_should_use_driver_environment_variables(const struct loader_instance* inst) { + bool should_use = true; + const loader_settings* settings = get_current_settings_and_lock(inst); + if (NULL == settings || !settings->settings_active) { + goto out; + } + if (settings->device_configuration_count > 0) { + should_use = false; + } +out: + release_current_settings_lock(inst); + return should_use; +} diff --git a/loader/settings.h b/loader/settings.h index 4e776ff0a..9ed5c80bf 100644 --- a/loader/settings.h +++ b/loader/settings.h @@ -34,6 +34,7 @@ struct loader_instance; struct loader_layer_list; +struct loader_string_list; struct loader_pointer_layer_list; struct loader_envvar_all_filters; typedef struct log_configuration log_configuration; @@ -61,6 +62,15 @@ typedef struct loader_settings_layer_configuration { } loader_settings_layer_configuration; +typedef struct loader_settings_driver_configuration { + char* path; +} loader_settings_driver_configuration; + +typedef struct loader_settings_device_configuration { + char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE]; + uint8_t deviceUUID[VK_UUID_SIZE]; +} loader_settings_device_configuration; + typedef struct loader_settings { bool settings_active; bool has_unordered_layer_location; @@ -69,6 +79,13 @@ typedef struct loader_settings { uint32_t layer_configuration_count; loader_settings_layer_configuration* layer_configurations; + bool additional_drivers_use_exclusively; + uint32_t additional_driver_count; + loader_settings_driver_configuration* additional_drivers; + + uint32_t device_configuration_count; + loader_settings_device_configuration* device_configurations; + char* settings_file_path; } loader_settings; @@ -112,3 +129,11 @@ VkResult enable_correct_layers_from_settings(const struct loader_instance* inst, const struct loader_layer_list* instance_layers, struct loader_pointer_layer_list* target_layer_list, struct loader_pointer_layer_list* activated_layer_list); + +// Add any drivers that the loader settings file contains to the out_files list. If the additional_drivers_use_exclusively field is +// true, clear the out_files list before adding any additional drivers +VkResult loader_settings_get_additional_driver_files(const struct loader_instance* inst, struct loader_string_list* out_files); + +// Check if there are any device_configurations. If so, we don't want to allow environment variables from selecting or ignoring +// drivers. This is because the VkPhysicalDevices corresponding to a driver_configurations might not be present otherwise. +bool loader_settings_should_use_driver_environment_variables(const struct loader_instance* inst); diff --git a/tests/framework/data/fuzzer_output.json b/tests/framework/data/fuzzer_output.json index 571378087..6032f8079 100644 --- a/tests/framework/data/fuzzer_output.json +++ b/tests/framework/data/fuzzer_output.json @@ -6,7 +6,7 @@ "/out/settings_fuzzer", "/work/settings_fuzzer" ], - "stderr_log": [ + "stderr_logg": [ "all", "info", "warn", @@ -15,7 +15,8 @@ "debug", "layer", "driver", - "validation" + "validation", + "not_real" ], "log_locations": [ { diff --git a/tests/framework/icd/test_icd.cpp b/tests/framework/icd/test_icd.cpp index 042184622..7e09c6d61 100644 --- a/tests/framework/icd/test_icd.cpp +++ b/tests/framework/icd/test_icd.cpp @@ -1216,6 +1216,10 @@ VKAPI_ATTR void VKAPI_CALL test_vkGetPhysicalDeviceProperties2(VkPhysicalDevice auto* layered_driver_props = reinterpret_cast(pNext); layered_driver_props->underlyingAPI = phys_dev.layered_driver_underlying_api; } + if (pNext->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES) { + auto* vulkan_11_props = reinterpret_cast(pNext); + memcpy(vulkan_11_props->deviceUUID, phys_dev.deviceUUID.data(), VK_UUID_SIZE); + } pNext = reinterpret_cast(const_cast(pNext->pNext)); } } diff --git a/tests/framework/icd/test_icd.h b/tests/framework/icd/test_icd.h index b49c518c5..33b1dc714 100644 --- a/tests/framework/icd/test_icd.h +++ b/tests/framework/icd/test_icd.h @@ -80,6 +80,7 @@ struct PhysicalDevice { DispatchableHandle vk_physical_device; BUILDER_VALUE(std::string, deviceName) + BUILDER_VALUE(VulkanUUID, deviceUUID) BUILDER_VALUE(VkPhysicalDeviceProperties, properties) BUILDER_VALUE(VkPhysicalDeviceFeatures, features) BUILDER_VALUE(VkPhysicalDeviceMemoryProperties, memory_properties) diff --git a/tests/framework/json_writer.h b/tests/framework/json_writer.h index f22db2892..4a4cd02a0 100644 --- a/tests/framework/json_writer.h +++ b/tests/framework/json_writer.h @@ -110,6 +110,28 @@ struct JsonWriter { output += std::string(value ? "true" : "false"); } + void AddKeyedNumber(std::string const& key, double number) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": " + std::to_string(number); + } + void AddNumber(double number) { + CommaAndNewLine(); + Indent(); + output += std::to_string(number); + } + + void AddKeyedInteger(std::string const& key, int64_t number) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": " + std::to_string(number); + } + void AddInteger(int64_t number) { + CommaAndNewLine(); + Indent(); + output += std::to_string(number); + } + // Json doesn't allow `\` in strings, it must be escaped. Thus we have to convert '\\' to '\\\\' in strings static std::string escape(std::string const& in_path) { std::string out; diff --git a/tests/framework/test_environment.cpp b/tests/framework/test_environment.cpp index d3295d90f..0dea3f794 100644 --- a/tests/framework/test_environment.cpp +++ b/tests/framework/test_environment.cpp @@ -680,19 +680,21 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { } platform_shim->add_known_path(folder.location()); break; - case (ManifestDiscoveryType::override_folder): case (ManifestDiscoveryType::macos_bundle): platform_shim->add_manifest(ManifestCategory::icd, icds.back().manifest_path); break; case (ManifestDiscoveryType::unsecured_generic): platform_shim->add_unsecured_manifest(ManifestCategory::icd, icds.back().manifest_path); break; - case (ManifestDiscoveryType::null_dir): - case (ManifestDiscoveryType::none): - break; case (ManifestDiscoveryType::windows_app_package): platform_shim->set_app_package_path(folder.location()); break; + + case (ManifestDiscoveryType::override_folder): // should be found through override layer/settings file, not 'normal' + // search paths + case (ManifestDiscoveryType::null_dir): + case (ManifestDiscoveryType::none): + break; } } return icds.back().get_test_icd(); @@ -907,6 +909,32 @@ std::string get_loader_settings_file_contents(const LoaderSettings& loader_setti } writer.EndArray(); } + if (!setting.driver_configurations.empty()) { + writer.AddKeyedBool("additional_drivers_use_exclusively", setting.additional_drivers_use_exclusively); + writer.StartKeyedArray("additional_drivers"); + for (const auto& driver : setting.driver_configurations) { + writer.StartObject(); + writer.AddKeyedString("path", driver.path); + writer.EndObject(); + } + writer.EndArray(); + } + if (!setting.device_configurations.empty()) { + writer.StartKeyedArray("device_configurations"); + for (const auto& device : setting.device_configurations) { + writer.StartObject(); + if (!device.deviceName.empty()) { + writer.AddKeyedString("deviceName", device.deviceName); + } + writer.StartKeyedArray("deviceUUID"); + for (const auto& u : device.deviceUUID) { + writer.AddInteger(u); + } + writer.EndArray(); + writer.EndObject(); + } + writer.EndArray(); + } writer.EndObject(); } if (!one_setting_file) { diff --git a/tests/framework/test_environment.h b/tests/framework/test_environment.h index 29703e228..a7b9f15b2 100644 --- a/tests/framework/test_environment.h +++ b/tests/framework/test_environment.h @@ -513,6 +513,7 @@ struct LoaderSettingsLayerConfiguration { BUILDER_VALUE(std::string, control) BUILDER_VALUE(bool, treat_as_implicit_manifest) }; +// Needed for next_permutation inline bool operator==(LoaderSettingsLayerConfiguration const& a, LoaderSettingsLayerConfiguration const& b) { return a.name == b.name && a.path == b.path && a.control == b.control && a.treat_as_implicit_manifest == b.treat_as_implicit_manifest; @@ -525,6 +526,15 @@ inline bool operator>(LoaderSettingsLayerConfiguration const& a, LoaderSettingsL inline bool operator<=(LoaderSettingsLayerConfiguration const& a, LoaderSettingsLayerConfiguration const& b) { return !(b < a); } inline bool operator>=(LoaderSettingsLayerConfiguration const& a, LoaderSettingsLayerConfiguration const& b) { return !(a < b); } +struct LoaderSettingsDriverConfiguration { + BUILDER_VALUE(std::filesystem::path, path) +}; + +struct LoaderSettingsDeviceConfiguration { + BUILDER_VALUE(VulkanUUID, deviceUUID) + BUILDER_VALUE(std::string, deviceName) +}; + // Log files and their associated filter struct LoaderLogConfiguration { BUILDER_VECTOR(std::string, destinations, destination) @@ -533,6 +543,9 @@ struct LoaderLogConfiguration { struct AppSpecificSettings { BUILDER_VECTOR(std::string, app_keys, app_key) BUILDER_VECTOR(LoaderSettingsLayerConfiguration, layer_configurations, layer_configuration) + BUILDER_VECTOR(LoaderSettingsDriverConfiguration, driver_configurations, driver_configuration) + BUILDER_VECTOR(LoaderSettingsDeviceConfiguration, device_configurations, device_configuration) + BUILDER_VALUE(bool, additional_drivers_use_exclusively) BUILDER_VECTOR(std::string, stderr_log, stderr_log_filter) BUILDER_VECTOR(LoaderLogConfiguration, log_configurations, log_configuration) }; diff --git a/tests/framework/test_util.h b/tests/framework/test_util.h index 51decc734..06d62d45d 100644 --- a/tests/framework/test_util.h +++ b/tests/framework/test_util.h @@ -839,6 +839,8 @@ struct VulkanFunction { PFN_vkVoidFunction function = nullptr; }; +using VulkanUUID = std::array; + template bool check_permutation(std::initializer_list expected, std::array const& returned) { if (expected.size() != returned.size()) return false; diff --git a/tests/loader_settings_tests.cpp b/tests/loader_settings_tests.cpp index b3313c7b0..adc2ae995 100644 --- a/tests/loader_settings_tests.cpp +++ b/tests/loader_settings_tests.cpp @@ -41,6 +41,20 @@ std::string get_settings_location_log_message([[maybe_unused]] FrameworkEnvironm return s + "/home/fake_home/.local/share/vulkan/loader_settings.d/vk_loader_settings.json"; #endif } + +std::string get_settings_not_in_use_log_message([[maybe_unused]] FrameworkEnvironment const& env, + [[maybe_unused]] bool use_secure = false) { + std::string s = "vk_loader_settings.json file found at \""; +#if defined(WIN32) + s += (env.get_folder(ManifestLocation::settings_location).location() / "vk_loader_settings.json").string(); +#elif COMMON_UNIX_PLATFORMS + if (use_secure) + s += "/etc/vulkan/loader_settings.d/vk_loader_settings.json"; + else + s += "/home/fake_home/.local/share/vulkan/loader_settings.d/vk_loader_settings.json"; +#endif + return s + "\" but did not contain any valid settings."; +} enum class LayerType { exp, imp, @@ -487,19 +501,21 @@ TEST(SettingsFile, LayerListIsEmpty) { writer.StartObject(); writer.AddKeyedString("file_format_version", "1.0.0"); writer.StartKeyedObject("settings"); - writer.StartKeyedObject("layers"); - writer.EndObject(); + writer.StartKeyedArray("layers"); + writer.EndArray(); writer.EndObject(); writer.EndObject(); env.write_settings_file(writer.output); - ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + auto layer_props = env.GetLayerProperties(1); + ASSERT_TRUE(string_eq(layer_props.at(0).layerName, implicit_layer_name)); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env))); - ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); + ASSERT_TRUE(env.debug_log.find(get_settings_not_in_use_log_message(env))); + auto actice_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 1); + ASSERT_TRUE(string_eq(actice_layer_props.at(0).layerName, implicit_layer_name)); } // If a settings file exists but contains no valid settings - don't consider it @@ -3018,3 +3034,363 @@ TEST(SettingsFile, EnvVarsWorkTogether) { EXPECT_TRUE(env.platform_shim->find_in_log("Insert instance layer \"VK_LAYER_add_env_var_implicit_layer\"")); } } + +// additional drivers being provided by settings file +TEST(SettingsFile, AdditionalDrivers) { + FrameworkEnvironment env{FrameworkSettings{}.set_log_filter("")}; + const char* regular_driver_name = "regular"; + const char* settings_driver_name = "settings"; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .add_physical_device(PhysicalDevice{}.set_deviceName(regular_driver_name).finish()); + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::override_folder)) + .add_physical_device(PhysicalDevice{}.set_deviceName(settings_driver_name).finish()); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.add_stderr_log_filter("all").add_driver_configuration( + LoaderSettingsDriverConfiguration{}.set_path(env.get_icd_manifest_path(1)))); + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pds = inst.GetPhysDevs(); + ASSERT_EQ(pds.size(), 2U); + VkPhysicalDeviceProperties props1{}, props2{}; + inst.functions->vkGetPhysicalDeviceProperties(pds.at(0), &props1); + inst.functions->vkGetPhysicalDeviceProperties(pds.at(1), &props2); + ASSERT_TRUE(string_eq(props1.deviceName, regular_driver_name)); + ASSERT_TRUE(string_eq(props2.deviceName, settings_driver_name)); +} +// settings file provided drivers replacing system found drivers +TEST(SettingsFile, ExclusiveAdditionalDrivers) { + FrameworkEnvironment env{}; + const char* regular_driver_name = "regular"; + const char* settings_driver_name = "settings"; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .add_physical_device(PhysicalDevice{}.set_deviceName(regular_driver_name).finish()); + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::override_folder)) + .add_physical_device(PhysicalDevice{}.set_deviceName(settings_driver_name).finish()); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.set_additional_drivers_use_exclusively(true).add_driver_configuration( + LoaderSettingsDriverConfiguration{}.set_path(env.get_icd_manifest_path(1)))); + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pds = inst.GetPhysDevs(); + ASSERT_EQ(pds.size(), 1U); + VkPhysicalDeviceProperties props1{}; + inst.functions->vkGetPhysicalDeviceProperties(pds.at(0), &props1); + ASSERT_TRUE(string_eq(props1.deviceName, settings_driver_name)); +} +// settings file provided drivers + VK_LOADER_DRIVERS_SELECT +TEST(SettingsFile, AdditionalDriversReplacesVK_LOADER_DRIVERS_SELECT) { + FrameworkEnvironment env{}; + const char* regular_driver_name = "regular"; + const char* settings_driver_name = "settings"; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .add_physical_device(PhysicalDevice{}.set_deviceName(regular_driver_name).finish()); + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::override_folder)) + .add_physical_device(PhysicalDevice{}.set_deviceName(settings_driver_name).finish()); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.add_driver_configuration(LoaderSettingsDriverConfiguration{}.set_path(env.get_icd_manifest_path(1)))); + env.update_loader_settings(env.loader_settings); + + { + EnvVarWrapper VK_LOADER_DRIVERS_SELECT{"VK_LOADER_DRIVERS_SELECT", + std::string("*") + env.get_icd_manifest_path(0).stem().string() + std::string("*")}; + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pds = inst.GetPhysDevs(); + ASSERT_EQ(pds.size(), 1U); + VkPhysicalDeviceProperties props1{}; + inst.functions->vkGetPhysicalDeviceProperties(pds.at(0), &props1); + ASSERT_TRUE(string_eq(props1.deviceName, regular_driver_name)); + } + { + EnvVarWrapper VK_LOADER_DRIVERS_SELECT{"VK_LOADER_DRIVERS_SELECT", + std::string("*") + env.get_icd_manifest_path(1).stem().string() + std::string("*")}; + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pds = inst.GetPhysDevs(); + ASSERT_EQ(pds.size(), 1U); + VkPhysicalDeviceProperties props1{}; + inst.functions->vkGetPhysicalDeviceProperties(pds.at(0), &props1); + ASSERT_TRUE(string_eq(props1.deviceName, settings_driver_name)); + } + env.loader_settings.app_specific_settings.at(0).set_additional_drivers_use_exclusively(true); + env.update_loader_settings(env.loader_settings); + { + EnvVarWrapper VK_LOADER_DRIVERS_SELECT{"VK_LOADER_DRIVERS_SELECT", + std::string("*") + env.get_icd_manifest_path(0).stem().string() + std::string("*")}; + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); + } + { + EnvVarWrapper VK_LOADER_DRIVERS_SELECT{"VK_LOADER_DRIVERS_SELECT", + std::string("*") + env.get_icd_manifest_path(1).stem().string() + std::string("*")}; + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pds = inst.GetPhysDevs(); + ASSERT_EQ(pds.size(), 1U); + VkPhysicalDeviceProperties props1{}; + inst.functions->vkGetPhysicalDeviceProperties(pds.at(0), &props1); + ASSERT_TRUE(string_eq(props1.deviceName, settings_driver_name)); + } +} + +// settings file provided drivers + VK_LOADER_DRIVERS_DISABLE +TEST(SettingsFile, AdditionalDriversReplacesVK_LOADER_DRIVERS_DISABLE) { + // TODO +} + +TEST(SettingsFile, InvalidAdditionalDriversField) { + FrameworkEnvironment env{}; + const char* layer_name = "VK_LAYER_layer"; + const char* driver_name = "driver"; + const char* settings_driver_name = "settings_driver"; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::override_folder)) + .add_physical_device(PhysicalDevice{}.set_deviceName(settings_driver_name).finish()); + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device(PhysicalDevice{}.set_deviceName(driver_name).finish()); + + env.add_explicit_layer(TestLayerDetails{ + ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name("VK_LAYER_layer").set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "regular_test_layer.json"} + .set_discovery_type(ManifestDiscoveryType::override_folder)); + JsonWriter writer; + writer.StartObject(); + writer.AddKeyedString("file_format_version", "1.0.0"); + + writer.StartKeyedObject("settings"); + writer.StartKeyedArray("additional_drivers"); + writer.AddString(env.get_shimmed_icd_manifest_path(0)); + writer.EndArray(); + writer.StartKeyedArray("layers"); + writer.StartObject(); + writer.AddKeyedString("name", layer_name); + writer.AddKeyedString("path", env.get_shimmed_layer_manifest_path(0)); + writer.AddKeyedString("control", "on"); + writer.EndObject(); + writer.EndArray(); + writer.EndObject(); + writer.EndObject(); + env.write_settings_file(writer.output); + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pd = inst.GetPhysDev(); + VkPhysicalDeviceProperties props1{}; + inst.functions->vkGetPhysicalDeviceProperties(pd, &props1); + ASSERT_TRUE(string_eq(props1.deviceName, driver_name)); + + auto active_layer_props = inst.GetActiveLayers(pd, 1); + EXPECT_TRUE(string_eq(active_layer_props.at(0).layerName, layer_name)); +} + +TEST(SettingsFile, DriverConfigurationsInSpecifiedOrder) { + FrameworkEnvironment env{}; + std::vector uuids{10, VulkanUUID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}; + + // Mix up the uuid's so that they are all unique + int count = 1; + for (auto& uuid : uuids) { + std::rotate(uuid.begin(), uuid.begin() + count, uuid.end()); + count++; + } + + auto& icd = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_icd_api_version(VK_API_VERSION_1_1); + for (uint32_t i = 0; i < uuids.size(); i++) { + // add the physical devices in reverse order of UUID's + icd.add_physical_device(PhysicalDevice() + .set_deviceName("PhysicalDevice_" + std::to_string(uuids.size() - 1 - i)) + .set_api_version(VK_API_VERSION_1_1) + .set_deviceUUID(uuids[uuids.size() - 1 - i]) + .finish()); + } + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting(AppSpecificSettings{}); + for (uint32_t i = 0; i < uuids.size(); i++) { + env.loader_settings.app_specific_settings.at(0).add_device_configuration( + LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[i])); + } + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pds = inst.GetPhysDevs(); + ASSERT_EQ(pds.size(), uuids.size()); + for (uint32_t i = 0; i < uuids.size(); i++) { + VkPhysicalDeviceProperties props{}; + + inst->vkGetPhysicalDeviceProperties(pds.at(i), &props); + std::string s = "PhysicalDevice_" + std::to_string(i); + ASSERT_TRUE(string_eq(s.c_str(), props.deviceName)); + } +} + +TEST(SettingsFile, OnlyOneDriverConfiguration) { + FrameworkEnvironment env{}; + std::vector uuids{10, VulkanUUID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}; + + // Mix up the uuid's so that they are all unique + int count = 1; + for (auto& uuid : uuids) { + std::rotate(uuid.begin(), uuid.begin() + count, uuid.end()); + count++; + } + + auto& icd = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_icd_api_version(VK_API_VERSION_1_1); + for (uint32_t i = 0; i < uuids.size(); i++) { + // add the physical devices in reverse order of UUID's + icd.add_physical_device( + PhysicalDevice().set_api_version(VK_API_VERSION_1_1).set_deviceUUID(uuids[uuids.size() - 1 - i]).finish()); + } + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting(AppSpecificSettings{}); + + for (uint32_t i = 0; i < uuids.size(); i++) { + env.loader_settings.app_specific_settings.at(0).device_configurations.clear(); + env.loader_settings.app_specific_settings.at(0).add_device_configuration( + LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[i])); + + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(VK_API_VERSION_1_1); + inst.CheckCreate(); + auto pd = inst.GetPhysDev(); + + VkPhysicalDeviceVulkan11Properties vulkan_11_props{}; + vulkan_11_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; + VkPhysicalDeviceProperties2 props2{}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &vulkan_11_props; + inst->vkGetPhysicalDeviceProperties2(pd, &props2); + + ASSERT_TRUE(0 == memcmp(vulkan_11_props.deviceUUID, uuids[i].data(), VK_UUID_SIZE * sizeof(uint8_t))); + } +} + +TEST(SettingsFile, MissingDriverConfiguration) { + FrameworkEnvironment env{}; + std::vector uuids{2, VulkanUUID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}; + + // Mix up the uuid's so that they are all unique + int count = 1; + for (auto& uuid : uuids) { + std::rotate(uuid.begin(), uuid.begin() + count, uuid.end()); + count++; + } + + auto& icd = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_icd_api_version(VK_API_VERSION_1_1); + icd.add_physical_device(PhysicalDevice().set_api_version(VK_API_VERSION_1_1).set_deviceUUID(uuids[1]).finish()); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting(AppSpecificSettings{}); + + env.loader_settings.app_specific_settings.at(0).device_configurations.clear(); + env.loader_settings.app_specific_settings.at(0).add_device_configuration( + LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[0])); + + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pd = inst.GetPhysDev(); +} + +// Three drivers, second on has the matching UUID in the settings file. +TEST(SettingsFile, DriverConfigurationIgnoresDriverEnvVars) { + FrameworkEnvironment env{}; + std::vector uuids{3, VulkanUUID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}; + + // Mix up the uuid's so that they are all unique + int count = 1; + for (auto& uuid : uuids) { + std::rotate(uuid.begin(), uuid.begin() + count, uuid.end()); + count++; + } + + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .add_physical_device(PhysicalDevice().set_deviceName("A").set_deviceUUID(uuids[0]).finish()); + + auto& icd = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_icd_api_version(VK_API_VERSION_1_1); + icd.add_physical_device( + PhysicalDevice().set_api_version(VK_API_VERSION_1_1).set_deviceName("B").set_deviceUUID(uuids[1]).finish()); + + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::env_var)) + .add_physical_device(PhysicalDevice().set_deviceName("C").set_deviceUUID(uuids[2]).finish()); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting(AppSpecificSettings{}); + + env.loader_settings.app_specific_settings.at(0).device_configurations.clear(); + env.loader_settings.app_specific_settings.at(0).add_device_configuration( + LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[1])); + + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(VK_API_VERSION_1_1); + inst.CheckCreate(); + auto pd = inst.GetPhysDev(); + + VkPhysicalDeviceVulkan11Properties vulkan_11_props{}; + vulkan_11_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; + VkPhysicalDeviceProperties2 props2{}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &vulkan_11_props; + inst->vkGetPhysicalDeviceProperties2(pd, &props2); + + ASSERT_TRUE(0 == memcmp(vulkan_11_props.deviceUUID, uuids[1].data(), VK_UUID_SIZE * sizeof(uint8_t))); +} + +TEST(SettingsFile, DriverConfigurationsAndAdditionalDrivers) { + FrameworkEnvironment env{}; + + std::vector uuids{3, VulkanUUID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}; + // Mix up the uuid's so that they are all unique + int count = 1; + for (auto& uuid : uuids) { + std::rotate(uuid.begin(), uuid.begin() + count, uuid.end()); + count++; + } + + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::override_folder)) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("additional_device") + .set_deviceUUID(uuids[0]) + .finish()); + + auto& icd = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_physical_device(PhysicalDevice() + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("device_configuration_device") + .set_deviceUUID(uuids[1]) + .finish()); + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting(AppSpecificSettings{}); + env.loader_settings.app_specific_settings.at(0).add_driver_configuration( + LoaderSettingsDriverConfiguration().set_path(env.get_icd_manifest_path(0))); + env.loader_settings.app_specific_settings.at(0).add_device_configuration( + LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[1])); + env.update_loader_settings(env.loader_settings); + + env.update_loader_settings(env.loader_settings); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(VK_API_VERSION_1_1); + inst.CheckCreate(); + auto pd = inst.GetPhysDev(); + VkPhysicalDeviceProperties props{}; + inst->vkGetPhysicalDeviceProperties(pd, &props); + ASSERT_TRUE(string_eq("device_configuration_device", props.deviceName)); + VkPhysicalDeviceVulkan11Properties vulkan_11_props{}; + vulkan_11_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; + VkPhysicalDeviceProperties2 props2{}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &vulkan_11_props; + inst->vkGetPhysicalDeviceProperties2(pd, &props2); + + ASSERT_TRUE(0 == memcmp(vulkan_11_props.deviceUUID, uuids[1].data(), VK_UUID_SIZE * sizeof(uint8_t))); +} From f946876731972cb323b021b78d1921aa9244808b Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 13 May 2025 11:21:17 -0500 Subject: [PATCH 09/65] Document Loader Settings File --- docs/LoaderSettingsFile.md | 114 +++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docs/LoaderSettingsFile.md diff --git a/docs/LoaderSettingsFile.md b/docs/LoaderSettingsFile.md new file mode 100644 index 000000000..d73f41076 --- /dev/null +++ b/docs/LoaderSettingsFile.md @@ -0,0 +1,114 @@ + +[![Khronos Vulkan][1]][2] + +[1]: https://vulkan.lunarg.com/img/Vulkan_100px_Dec16.png "https://www.khronos.org/vulkan/" +[2]: https://www.khronos.org/vulkan/ + +# Loader Settings File + +[![Creative Commons][3]][4] + + + +[3]: https://i.creativecommons.org/l/by-nd/4.0/88x31.png "Creative Commons License" +[4]: https://creativecommons.org/licenses/by-nd/4.0/ + + +## Table of Contents + +- [Purpose of the Settings File](#purpose-of-the-settings-file) +- [Settings File Discovery](#settings-file-discovery) + - [Windows](#windows) + - [Linux/MacOS/BSD/QNX/Fuchsia/GNU](#linuxmacosbsdqnxfuchsiagnu) + - [Other Platforms](#other-platforms) + - [Exception for Elevated Privileges](#exception-for-elevated-privileges) +- [Per-Application Settings File](#per-application-settings-file) +- [File Format](#file-format) +- [Example Settings File](#example-settings-file) + - [Fields](#fields) +- [Behavior](#behavior) + + +## Purpose of the Settings File + +The purpose of the Loader Settings File is to give developers superb control over the +behavior of the Vulkan-Loader. +It enables enhanced controls over which layers to load, the order layers in the call chain, +logging, and which drivers are available. + +The Loader Settings File is intended to be used by "Developer Control Panels" for the Vulkan API, such as the Vulkan Configurator, as a replacement for setting debug envrionment variables. + +## Settings File Discovery + +The Loader Settings File is located by searching in specific file system paths or through +platform specific mechanisms such as the Windows Registry. + +### Windows + +The Vulkan Loader first searches the Registry Key HKEY_CURRENT_USER\SOFTWARE\Khronos\Vulkan\LoaderSettings for a DWORD value whose name is +a valid path to a file named 'vk_loader_settings.json'. +If there are no matching values or the file doesn't exist, the Vulkan Loader performs the +same behavior as described above for the Registry Key HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\LoaderSettings. + +### Linux/MacOS/BSD/QNX/Fuchsia/GNU + +The Loader Settings File is located by searching for a file named vk_loader_settings.json in the following locations: + +`$HOME/.local/share/vulkan/loader_settings.d/` +`$XDG_DATA_HOME/vulkan/loader_settings.d/` +`/etc/vulkan/loader_settings.d/` + +Where $HOME and %XDG_DATA_HOME refer to the values contained in the environment variables of the same name. +If a given environment variables is not present, that path is ignored. + +### Other Platforms + +Platforms not listed above currently do not support the Loader Settings File due to not having an appropriate search mechanism. + +### Exception for Elevated Privileges + +Because the Loader Settings File contains paths to Layer and ICD manifests, which contain +the paths to various executable binaries, it is necessary to restrict the use of the Loader +Settings File when the application is running with elevated privileges. + +This is accomplished by not using any Loader Settings Files that are found in non-privileged locations. + +On Windows, running with Elevated Privileges will ignore HKEY_CURRENT_USER\SOFTWARE\Khronos\Vulkan\LoaderSettings. + +On Linux/MacOS/BSD/QNX/Fuchsia/GNU, running with Elevated Privileges will use a secure method of querying $HOME and $XDG_DATA_HOME to prevent +malicious injection of unsecure search directories. + +## Per-Application Settings File + +## File Format + +The Loader Settings File is a JSON file with a + + +## Example Settings File + + +```json +{ + "file_format_version" : "1.0.1", + "settings": { + + } +} +``` + +### Fields + + + + + + + + + + + + + +## Behavior From 8f35302d38a547f60dc5f58ce042cc39e5874519 Mon Sep 17 00:00:00 2001 From: Carlo Bramini <30959007+carlo-bramini@users.noreply.github.com> Date: Fri, 18 Jul 2025 11:03:49 +0200 Subject: [PATCH 10/65] Fix build for CYGWIN Attached patch fixes build on CYGWIN. Hopefully, the patch is small and easy because it is just needed to add CYGWIN to the list of existing UNIX platforms. --- CMakeLists.txt | 2 +- loader/vk_loader_platform.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7963186ab..d20339a42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,7 @@ elseif(APPLE) if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") target_compile_definitions(platform_wsi INTERFACE VK_USE_PLATFORM_MACOS_MVK) endif() -elseif(CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|DragonFly|GNU") +elseif(CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|DragonFly|GNU|CYGWIN") option(BUILD_WSI_XCB_SUPPORT "Build XCB WSI support" ON) option(BUILD_WSI_XLIB_SUPPORT "Build Xlib WSI support" ON) option(BUILD_WSI_XLIB_XRANDR_SUPPORT "Build X11 Xrandr WSI support" ON) diff --git a/loader/vk_loader_platform.h b/loader/vk_loader_platform.h index 9a3b9c1c9..272f6f8ec 100644 --- a/loader/vk_loader_platform.h +++ b/loader/vk_loader_platform.h @@ -44,7 +44,7 @@ // Set of platforms with a common set of functionality which is queried throughout the program #if defined(__linux__) || defined(__APPLE__) || defined(__Fuchsia__) || defined(__QNX__) || defined(__FreeBSD__) || \ - defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__GNU__) + defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__GNU__) || defined(__CYGWIN__) #define COMMON_UNIX_PLATFORMS 1 #else #define COMMON_UNIX_PLATFORMS 0 @@ -298,7 +298,7 @@ static inline char *loader_platform_dirname(char *path) { return dirname(path); // loader_platform_executable_path finds application path + name. // Path cannot be longer than 1024, returns NULL if it is greater than that. -#if defined(__linux__) || defined(__GNU__) +#if defined(__linux__) || defined(__GNU__) || defined(__CYGWIN__) static inline char *loader_platform_executable_path(char *buffer, size_t size) { ssize_t count = readlink("/proc/self/exe", buffer, size); if (count == -1) return NULL; From a1684e4bd23ab130c5e7c37ab72b07dd541897a9 Mon Sep 17 00:00:00 2001 From: Mike Schuchardt Date: Sun, 20 Jul 2025 22:34:09 -0700 Subject: [PATCH 11/65] build: Update to header 1.4.323 --- CMakeLists.txt | 2 +- loader/loader.rc | 4 ++-- scripts/known_good.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d20339a42..659049ef2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.322 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.323 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/loader.rc b/loader/loader.rc index 3bd3864cf..a2f68cbf8 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 322, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.322.Dev Build" +#define VER_FILE_VERSION 1, 4, 323, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.323.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index 505b47ba2..b62307217 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.322" + "commit": "v1.4.323" }, { "name": "googletest", From c8ac60ea2ddc5fc5c4c180c40f0d2f4caa23e1cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 20:17:42 +0000 Subject: [PATCH 12/65] build(deps): bump github/codeql-action from 3.29.2 to 3.29.4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.2 to 3.29.4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/181d5eefc20863364f96762470ba6f862bdef56b...4e828ff8d448a8a6e532957b1811f387a63867e8) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index da4fb2c94..10a04aec1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 - uses: actions/setup-python@v5 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: category: "/language:${{matrix.language}}" From 77ccbe4f422ff89405272873c25bbb8ca88bd699 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 11 Jul 2025 13:27:55 -0500 Subject: [PATCH 13/65] Get real path to layer & driver binaries Queries the real path of loaded layers and drivers so that logging messages contain the exact path used, instead of the path the loader gave to dlopen/ LoadLibrary. This ensures that logs are as accurate as can be, as the dynamic linker may not respect the passed in library (due to environment variables or other mechanisms which change which binary is loaded). This requires normalizing paths before comparison, as the path given to dlopen/LoadLibrary may represent the same location as the queried path, but might not be identical strings. Normalization is done using the realpath() function on platforms that support it, and a fallback implementation is used elsewhere. The implementation only removes extra directory separators ("//"), extra current directory specifiers ("/./"), and removes relative to parent directory specifiers ("/foo/../"). --- CMakeLists.txt | 13 +++ loader/loader.c | 273 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 659049ef2..b9d434af6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -258,6 +258,19 @@ if (NOT (WIN32 OR APPLE)) endif() endif() +if (NOT (WIN32)) + # Check for the existance of the realpath() command + include(CheckFunctionExists) + + check_function_exists(realpath HAVE_REALPATH) + + if (HAVE_REALPATH) + target_compile_definitions(loader_common_options INTERFACE HAVE_REALPATH) + else() + message(INFO "Platform support for realpath() is missing. Using fallback path normalization implementation.") + endif() +endif() + option(LOADER_CODEGEN "Enable vulkan loader code generation") if(LOADER_CODEGEN) find_package(Python3 REQUIRED) diff --git a/loader/loader.c b/loader/loader.c index 9832bb7b0..85e3223af 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -387,6 +387,269 @@ void free_string_list(const struct loader_instance *inst, struct loader_string_l memset(string_list, 0, sizeof(struct loader_string_list)); } +// In place modify the passed in path to do the following: +// If HAVE_REALPATH is defined, then this simply calls realpath() so its behavior is defined by realpath() +// Else: +// * Windows-only: Replace forward slashes with backwards slashes (platform correct directory separator) +// * Replace contiguous directory separators with a single directory separator +// * Replace "/./" separator with "/" (where / is the platform correct directory separator) +// * Replace "//../" with just "/" (where / is the platform correct directory separator) +VkResult normalize_path(const struct loader_instance *inst, char **passed_in_path) { + // passed_in_path doesn't point to anything, can't modify inplace so just return + if (passed_in_path == NULL) { + return VK_SUCCESS; + } + +// POSIX systems has the realpath() function to do this for us, fallback to basic normalization on other platforms +#if defined(HAVE_REALPATH) + char *path = loader_instance_heap_calloc(inst, PATH_MAX, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == path) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + char *ret = realpath(*passed_in_path, path); + if (NULL == ret) { + // error path + int error_code = errno; + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "normalize_path: Call to realpath() failed with error code %d when given the path %s", error_code, + *passed_in_path); + loader_instance_heap_free(inst, path); + } else { + // Replace string pointed to by passed_in_path with the one given to us by realpath() + loader_instance_heap_free(inst, *passed_in_path); + *passed_in_path = path; + } + return VK_SUCCESS; + +// Windows has GetFullPathName which does essentially the same thing. Note that we call GetFullPathNameA because the path has +// already been converted from the wide char format when it was initially gotten +#elif defined(WIN32) + VkResult res = VK_SUCCESS; + DWORD path_len = (DWORD)strlen(*passed_in_path) + 1; + char *path = loader_instance_heap_calloc(inst, (size_t)path_len, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == path) { + res = VK_ERROR_OUT_OF_HOST_MEMORY; + goto out; + } + DWORD actual_len = GetFullPathNameA(*passed_in_path, path_len, path, NULL); + if (actual_len == 0) { + size_t last_error = (size_t)GetLastError(); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "normalize_path: Call to GetFullPathNameA() failed with error code %llu when given the path %s", last_error, + *passed_in_path); + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + + // If path_len wasn't big enough, need to realloc and call again + // actual_len doesn't include null terminator + if (actual_len + 1 > path_len) { + path = loader_instance_heap_realloc(inst, path, path_len, actual_len + 1, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == path) { + res = VK_ERROR_OUT_OF_HOST_MEMORY; + goto out; + } + // store the updated allocation size (sans null terminator) + path_len = actual_len + 1; + + actual_len = GetFullPathNameA(*passed_in_path, path_len, path, NULL); + if (actual_len == 0) { + size_t last_error = (size_t)GetLastError(); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "normalize_path: Call to GetFullPathNameA() failed with error code %llu when given the path %s", last_error, + *passed_in_path); + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + // actual_len doesn't include null terminator + } else if (actual_len + 1 != path_len) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "normalize_path: Call to GetFullPathNameA() with too small of a buffer when given the path %s after the " + "initial call to GetFullPathNameA() failed for the same reason. Buffer size is %llu, actual size is %llu", + *passed_in_path, (size_t)path_len, (size_t)actual_len); + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + } + // Replace string pointed to by passed_in_path with the one given to us by realpath() + loader_instance_heap_free(inst, *passed_in_path); + *passed_in_path = path; +out: + if (VK_SUCCESS != res) { + if (NULL != path) { + loader_instance_heap_free(inst, path); + } + } + return res; + +#else + (void)inst; + char *path = *passed_in_path; + size_t path_len = strlen(path) + 1; + + size_t output_index = 0; + // Iterate through the string up to the last character, excluding the null terminator + for (size_t i = 0; i < path_len - 1; i++) { + if (i + 1 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == DIRECTORY_SYMBOL) { + continue; + } else if (i + 2 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == '.' && path[i + 2] == DIRECTORY_SYMBOL) { + i += 1; + } else { + path[output_index++] = path[i]; + } + } + // Add null terminator and set the new length + path[output_index++] = '\0'; + path_len = output_index; + + // Loop while there are still ..'s in the path. Easiest implementation resolves them one by one, which requires quadratic + // iteration through the string + char *directory_stack = loader_stack_alloc(path_len); + if (directory_stack == NULL) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + size_t top_of_stack = 0; + + // Iterate through the path, push characters as we see them, if we find a "..", pop off the top of the directory stack until the + // current directory is gone. + for (size_t i = 0; i < path_len - 1; i++) { + // if the next part of path is "/../" we need to pop from the directory stack until we hit the previous directory symbol. + if (i + 3 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == '.' && path[i + 2] == '.' && path_len && + path[i + 3] == DIRECTORY_SYMBOL) { + // Pop until we hit the next directory symbol in the stack + while (top_of_stack > 0 && directory_stack[top_of_stack - 1] != DIRECTORY_SYMBOL) { + top_of_stack--; + directory_stack[top_of_stack] = '\0'; + } + // Amend the directory stack so that the top isn't a directory separator + if (top_of_stack > 0 && directory_stack[top_of_stack - 1] == DIRECTORY_SYMBOL) { + top_of_stack--; + directory_stack[top_of_stack] = '\0'; + } + i += 2; // need to skip the second dot & directory separator + } else { + // push characters as we come across them + directory_stack[top_of_stack++] = path[i]; + } + } + + // Can't forget the null terminator + directory_stack[top_of_stack] = '\0'; + + // We now have the path without any ..'s, so just copy it out + loader_strncpy(path, path_len, directory_stack, path_len); + path[top_of_stack] = '\0'; + path_len = top_of_stack + 1; + + return VK_SUCCESS; +#endif +} + +// Queries the path to the library that lib_handle & gipa are assoicated with, allocating a string to hold it and returning it in +// out_path +VkResult get_library_path_of_dl_handle(const struct loader_instance *inst, loader_platform_dl_handle lib_handle, + PFN_vkGetInstanceProcAddr gipa, char **out_path) { +#if COMMON_UNIX_PLATFORMS + (void)lib_handle; + Dl_info dl_info = {0}; + if (dladdr(gipa, &dl_info) != 0 && NULL != dl_info.dli_fname) { + return loader_copy_to_new_str(inst, dl_info.dli_fname, out_path); + } + return VK_SUCCESS; + +#elif defined(WIN32) + (void)gipa; + size_t module_file_name_len = MAX_PATH; // start with reasonably large buffer + wchar_t *buffer_utf16 = (wchar_t *)loader_stack_alloc(module_file_name_len * sizeof(wchar_t)); + DWORD ret = GetModuleFileNameW(lib_handle, buffer_utf16, (DWORD)module_file_name_len); + if (ret == 0) { + return VK_SUCCESS; + } + while (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + module_file_name_len *= 2; + buffer_utf16 = (wchar_t *)loader_stack_alloc(module_file_name_len * sizeof(wchar_t)); + ret = GetModuleFileNameW(lib_handle, buffer_utf16, (DWORD)module_file_name_len); + if (ret == 0) { + return VK_SUCCESS; + } + } + + // Need to convert from utf16 to utf8 + int buffer_utf8_size = WideCharToMultiByte(CP_UTF8, 0, buffer_utf16, -1, NULL, 0, NULL, NULL); + if (buffer_utf8_size <= 0) { + return VK_SUCCESS; + } + + char *buffer_utf8 = loader_instance_heap_calloc(inst, buffer_utf8_size, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == buffer_utf8) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + if (WideCharToMultiByte(CP_UTF8, 0, buffer_utf16, -1, buffer_utf8, buffer_utf8_size, NULL, NULL) != buffer_utf8_size) { + return VK_SUCCESS; + } + + // Successfully got the 'real' path to the library. + *out_path = buffer_utf8; + return VK_SUCCESS; + +#else + // Do nothing, platform doesn't handle getting the path to a library +#endif +} + +// Find and replace the path that was loaded using the lib_name path with the real path of the library. This is done to provide +// accurate logging info for users. +// This function prints a warning if there is a mismatch between the lib_name path and the real path. +VkResult fixup_library_binary_path(const struct loader_instance *inst, char **lib_name, loader_platform_dl_handle lib_handle, + PFN_vkGetInstanceProcAddr gipa) { + if (lib_name == NULL) { + // do nothing as we got an invalid lib_path pointer + return VK_SUCCESS; + } + + bool system_path = true; + size_t lib_name_len = strlen(*lib_name) + 1; + for (size_t i = 0; i < lib_name_len; i++) { + if ((*lib_name)[i] == DIRECTORY_SYMBOL) { + system_path = false; + break; + } + } + + if (!system_path) { + // The OS path we get for a binary is normalized, so we need to normalize the path passed into LoadLibrary/dlopen so that + // mismatches are minimized. EG, do not warn when we give dlopen/LoadLibrary "/foo/./bar" but get "/foo/bar" as the loaded + // binary path from the OS. + VkResult res = normalize_path(inst, lib_name); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + return res; + } + } + char *os_determined_lib_name = NULL; + VkResult res = get_library_path_of_dl_handle(inst, lib_handle, gipa, &os_determined_lib_name); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + return res; + } + + if (NULL != os_determined_lib_name) { + if (0 != strcmp(os_determined_lib_name, *lib_name)) { + // Paths do not match, so we need to replace lib_name with the real path + if (!system_path) { + // Only warn when the library_path is relative or absolute, not system. EG lib_name had no directory separators + loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0, + "Path to given binary %s was found to differ from OS loaded path %s", *lib_name, os_determined_lib_name); + } + loader_instance_heap_free(inst, *lib_name); + *lib_name = os_determined_lib_name; + } else { + // Paths match, so just need to free temporary allocation + loader_instance_heap_free(inst, os_determined_lib_name); + } + } + + return res; +} + // Given string of three part form "maj.min.pat" convert to a vulkan version number. // Also can understand four part form "variant.major.minor.patch" if provided. uint32_t loader_parse_version_string(char *vers_str) { @@ -1996,6 +2259,11 @@ VkResult loader_scanned_icd_add(const struct loader_instance *inst, struct loade } icd_tramp_list->count++; + // Uses OS calls to find the 'true' path to the binary, for more accurate logging later on. + res = fixup_library_binary_path(inst, &(new_scanned_icd->lib_name), new_scanned_icd->handle, fp_get_proc_addr); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + goto out; + } out: if (res != VK_SUCCESS) { if (NULL != handle) { @@ -4807,6 +5075,11 @@ VkResult loader_create_instance_chain(const VkInstanceCreateInfo *pCreateInfo, c chain_info.u.pLayerInfo = &layer_instance_link_info[num_activated_layers]; + res = fixup_library_binary_path(inst, &(layer_prop->lib_name), layer_prop->lib_handle, cur_gipa); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + return res; + } + activated_layers[num_activated_layers].name = layer_prop->info.layerName; activated_layers[num_activated_layers].manifest = layer_prop->manifest_file_name; activated_layers[num_activated_layers].library = layer_prop->lib_name; From 1df688cab995b68e77a68666dfca34c15e3f2d3c Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 15 Jul 2025 16:42:17 -0500 Subject: [PATCH 14/65] Generate VkResult operator<< overload --- scripts/generate_source.py | 7 + .../vk_result_to_string_generator.py | 79 +++++++++++ .../generated/vk_result_to_string_helper.h | 133 ++++++++++++++++++ tests/framework/test_environment.h | 2 + tests/framework/test_util.h | 107 -------------- 5 files changed, 221 insertions(+), 107 deletions(-) create mode 100644 scripts/generators/vk_result_to_string_generator.py create mode 100644 tests/framework/generated/vk_result_to_string_helper.h diff --git a/scripts/generate_source.py b/scripts/generate_source.py index e12f7313d..65661152b 100755 --- a/scripts/generate_source.py +++ b/scripts/generate_source.py @@ -66,6 +66,7 @@ def RunGenerators(api: str, registry: str, directory: str, styleFile: str, targe from generators.dispatch_table_helper_generator import DispatchTableHelperGenerator from generators.helper_file_generator import HelperFileGenerator from generators.loader_extension_generator import LoaderExtensionGenerator + from generators.vk_result_to_string_generator import VkResultToStringGenerator # These set fields that are needed by both OutputGenerator and BaseGenerator, # but are uniform and don't need to be set at a per-generated file level @@ -75,6 +76,7 @@ def RunGenerators(api: str, registry: str, directory: str, styleFile: str, targe # Generated directory and dispatch table helper file name may be API specific (e.g. Vulkan SC) generated_directory = 'loader/generated' dispatch_table_helper_filename = 'vk_dispatch_table_helper.h' + result_to_string_filename = 'vk_result_to_string_helper.h' generators.update({ 'vk_layer_dispatch_table.h': { @@ -101,6 +103,11 @@ def RunGenerators(api: str, registry: str, directory: str, styleFile: str, targe 'generator' : DispatchTableHelperGenerator, 'genCombined': False, 'directory' : 'tests/framework/layer/generated', + }, + f'{result_to_string_filename}': { + 'generator' : VkResultToStringGenerator, + 'genCombined': False, + 'directory' : 'tests/framework/generated', } }) diff --git a/scripts/generators/vk_result_to_string_generator.py b/scripts/generators/vk_result_to_string_generator.py new file mode 100644 index 000000000..9efb4aa24 --- /dev/null +++ b/scripts/generators/vk_result_to_string_generator.py @@ -0,0 +1,79 @@ +#!/usr/bin/python3 -i +# +# Copyright (c) 2015-2021 The Khronos Group Inc. +# Copyright (c) 2015-2021 Valve Corporation +# Copyright (c) 2015-2021 LunarG, Inc. +# Copyright (c) 2015-2021 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. +# +# Author: Mark Lobodzinski +# Author: Charles Giessen + +import os +from base_generator import BaseGenerator + +class VkResultToStringGenerator(BaseGenerator): + def __init__(self): + BaseGenerator.__init__(self) + + def generate(self): + out = [] + + out.append(f'''#pragma once +// *** THIS FILE IS GENERATED - DO NOT EDIT *** +// See {os.path.basename(__file__)} for modifications + +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, 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 +#include + +#include + +''') + + self.OutputVkResultToStringHelper(out) + out.append('\n') + + self.write(''.join(out)) + + # Create a dispatch table from the corresponding table_type and append it to out + def OutputVkResultToStringHelper(self, out: list): + out.append('inline std::ostream& operator<<(std::ostream& os, const VkResult& result) {\n') + out.append(' switch (result) {\n') + for field in self.vk.enums['VkResult'].fields: + out.append(f' case({field.name}):\n') + out.append(f' return os << "{field.name}";\n') + out.append(' default:\n') + out.append(' return os << static_cast(result);\n') + out.append(' }\n') + out.append(' }\n') diff --git a/tests/framework/generated/vk_result_to_string_helper.h b/tests/framework/generated/vk_result_to_string_helper.h new file mode 100644 index 000000000..88dfa6a46 --- /dev/null +++ b/tests/framework/generated/vk_result_to_string_helper.h @@ -0,0 +1,133 @@ +#pragma once +// *** THIS FILE IS GENERATED - DO NOT EDIT *** +// See vk_result_to_string_generator.py for modifications + +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, 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 +#include + +#include + +inline std::ostream& operator<<(std::ostream& os, const VkResult& result) { + switch (result) { + case (VK_SUCCESS): + return os << "VK_SUCCESS"; + case (VK_NOT_READY): + return os << "VK_NOT_READY"; + case (VK_TIMEOUT): + return os << "VK_TIMEOUT"; + case (VK_EVENT_SET): + return os << "VK_EVENT_SET"; + case (VK_EVENT_RESET): + return os << "VK_EVENT_RESET"; + case (VK_INCOMPLETE): + return os << "VK_INCOMPLETE"; + case (VK_ERROR_OUT_OF_HOST_MEMORY): + return os << "VK_ERROR_OUT_OF_HOST_MEMORY"; + case (VK_ERROR_OUT_OF_DEVICE_MEMORY): + return os << "VK_ERROR_OUT_OF_DEVICE_MEMORY"; + case (VK_ERROR_INITIALIZATION_FAILED): + return os << "VK_ERROR_INITIALIZATION_FAILED"; + case (VK_ERROR_DEVICE_LOST): + return os << "VK_ERROR_DEVICE_LOST"; + case (VK_ERROR_MEMORY_MAP_FAILED): + return os << "VK_ERROR_MEMORY_MAP_FAILED"; + case (VK_ERROR_LAYER_NOT_PRESENT): + return os << "VK_ERROR_LAYER_NOT_PRESENT"; + case (VK_ERROR_EXTENSION_NOT_PRESENT): + return os << "VK_ERROR_EXTENSION_NOT_PRESENT"; + case (VK_ERROR_FEATURE_NOT_PRESENT): + return os << "VK_ERROR_FEATURE_NOT_PRESENT"; + case (VK_ERROR_INCOMPATIBLE_DRIVER): + return os << "VK_ERROR_INCOMPATIBLE_DRIVER"; + case (VK_ERROR_TOO_MANY_OBJECTS): + return os << "VK_ERROR_TOO_MANY_OBJECTS"; + case (VK_ERROR_FORMAT_NOT_SUPPORTED): + return os << "VK_ERROR_FORMAT_NOT_SUPPORTED"; + case (VK_ERROR_FRAGMENTED_POOL): + return os << "VK_ERROR_FRAGMENTED_POOL"; + case (VK_ERROR_UNKNOWN): + return os << "VK_ERROR_UNKNOWN"; + case (VK_ERROR_VALIDATION_FAILED): + return os << "VK_ERROR_VALIDATION_FAILED"; + case (VK_ERROR_OUT_OF_POOL_MEMORY): + return os << "VK_ERROR_OUT_OF_POOL_MEMORY"; + case (VK_ERROR_INVALID_EXTERNAL_HANDLE): + return os << "VK_ERROR_INVALID_EXTERNAL_HANDLE"; + case (VK_ERROR_FRAGMENTATION): + return os << "VK_ERROR_FRAGMENTATION"; + case (VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS): + return os << "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; + case (VK_PIPELINE_COMPILE_REQUIRED): + return os << "VK_PIPELINE_COMPILE_REQUIRED"; + case (VK_ERROR_NOT_PERMITTED): + return os << "VK_ERROR_NOT_PERMITTED"; + case (VK_ERROR_SURFACE_LOST_KHR): + return os << "VK_ERROR_SURFACE_LOST_KHR"; + case (VK_ERROR_NATIVE_WINDOW_IN_USE_KHR): + return os << "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; + case (VK_SUBOPTIMAL_KHR): + return os << "VK_SUBOPTIMAL_KHR"; + case (VK_ERROR_OUT_OF_DATE_KHR): + return os << "VK_ERROR_OUT_OF_DATE_KHR"; + case (VK_ERROR_INCOMPATIBLE_DISPLAY_KHR): + return os << "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; + case (VK_ERROR_INVALID_SHADER_NV): + return os << "VK_ERROR_INVALID_SHADER_NV"; + case (VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; + case (VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT): + return os << "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; + case (VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT): + return os << "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; + case (VK_THREAD_IDLE_KHR): + return os << "VK_THREAD_IDLE_KHR"; + case (VK_THREAD_DONE_KHR): + return os << "VK_THREAD_DONE_KHR"; + case (VK_OPERATION_DEFERRED_KHR): + return os << "VK_OPERATION_DEFERRED_KHR"; + case (VK_OPERATION_NOT_DEFERRED_KHR): + return os << "VK_OPERATION_NOT_DEFERRED_KHR"; + case (VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR): + return os << "VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR"; + case (VK_ERROR_COMPRESSION_EXHAUSTED_EXT): + return os << "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; + case (VK_INCOMPATIBLE_SHADER_BINARY_EXT): + return os << "VK_INCOMPATIBLE_SHADER_BINARY_EXT"; + case (VK_PIPELINE_BINARY_MISSING_KHR): + return os << "VK_PIPELINE_BINARY_MISSING_KHR"; + case (VK_ERROR_NOT_ENOUGH_SPACE_KHR): + return os << "VK_ERROR_NOT_ENOUGH_SPACE_KHR"; + default: + return os << static_cast(result); + } +} diff --git a/tests/framework/test_environment.h b/tests/framework/test_environment.h index a7b9f15b2..98ba141d1 100644 --- a/tests/framework/test_environment.h +++ b/tests/framework/test_environment.h @@ -57,6 +57,8 @@ #include "layer/test_layer.h" +#include "generated/vk_result_to_string_helper.h" + #include FRAMEWORK_CONFIG_HEADER // Useful defines diff --git a/tests/framework/test_util.h b/tests/framework/test_util.h index 06d62d45d..2f4b641f0 100644 --- a/tests/framework/test_util.h +++ b/tests/framework/test_util.h @@ -345,113 +345,6 @@ struct FRAMEWORK_EXPORT DispatchableHandle { T handle = nullptr; }; -// Stream operator for VkResult so GTEST will print out error codes as strings (automatically) -inline std::ostream& operator<<(std::ostream& os, const VkResult& result) { - switch (result) { - case (VK_SUCCESS): - return os << "VK_SUCCESS"; - case (VK_NOT_READY): - return os << "VK_NOT_READY"; - case (VK_TIMEOUT): - return os << "VK_TIMEOUT"; - case (VK_EVENT_SET): - return os << "VK_EVENT_SET"; - case (VK_EVENT_RESET): - return os << "VK_EVENT_RESET"; - case (VK_INCOMPLETE): - return os << "VK_INCOMPLETE"; - case (VK_ERROR_OUT_OF_HOST_MEMORY): - return os << "VK_ERROR_OUT_OF_HOST_MEMORY"; - case (VK_ERROR_OUT_OF_DEVICE_MEMORY): - return os << "VK_ERROR_OUT_OF_DEVICE_MEMORY"; - case (VK_ERROR_INITIALIZATION_FAILED): - return os << "VK_ERROR_INITIALIZATION_FAILED"; - case (VK_ERROR_DEVICE_LOST): - return os << "VK_ERROR_DEVICE_LOST"; - case (VK_ERROR_MEMORY_MAP_FAILED): - return os << "VK_ERROR_MEMORY_MAP_FAILED"; - case (VK_ERROR_LAYER_NOT_PRESENT): - return os << "VK_ERROR_LAYER_NOT_PRESENT"; - case (VK_ERROR_EXTENSION_NOT_PRESENT): - return os << "VK_ERROR_EXTENSION_NOT_PRESENT"; - case (VK_ERROR_FEATURE_NOT_PRESENT): - return os << "VK_ERROR_FEATURE_NOT_PRESENT"; - case (VK_ERROR_INCOMPATIBLE_DRIVER): - return os << "VK_ERROR_INCOMPATIBLE_DRIVER"; - case (VK_ERROR_TOO_MANY_OBJECTS): - return os << "VK_ERROR_TOO_MANY_OBJECTS"; - case (VK_ERROR_FORMAT_NOT_SUPPORTED): - return os << "VK_ERROR_FORMAT_NOT_SUPPORTED"; - case (VK_ERROR_FRAGMENTED_POOL): - return os << "VK_ERROR_FRAGMENTED_POOL"; - case (VK_ERROR_UNKNOWN): - return os << "VK_ERROR_UNKNOWN"; - case (VK_ERROR_OUT_OF_POOL_MEMORY): - return os << "VK_ERROR_OUT_OF_POOL_MEMORY"; - case (VK_ERROR_INVALID_EXTERNAL_HANDLE): - return os << "VK_ERROR_INVALID_EXTERNAL_HANDLE"; - case (VK_ERROR_FRAGMENTATION): - return os << "VK_ERROR_FRAGMENTATION"; - case (VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS): - return os << "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; - case (VK_ERROR_SURFACE_LOST_KHR): - return os << "VK_ERROR_SURFACE_LOST_KHR"; - case (VK_ERROR_NATIVE_WINDOW_IN_USE_KHR): - return os << "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; - case (VK_SUBOPTIMAL_KHR): - return os << "VK_SUBOPTIMAL_KHR"; - case (VK_ERROR_OUT_OF_DATE_KHR): - return os << "VK_ERROR_OUT_OF_DATE_KHR"; - case (VK_ERROR_INCOMPATIBLE_DISPLAY_KHR): - return os << "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; - case (VK_ERROR_VALIDATION_FAILED_EXT): - return os << "VK_ERROR_VALIDATION_FAILED_EXT"; - case (VK_ERROR_INVALID_SHADER_NV): - return os << "VK_ERROR_INVALID_SHADER_NV"; - case (VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT): - return os << "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; - case (VK_ERROR_NOT_PERMITTED_EXT): - return os << "VK_ERROR_NOT_PERMITTED_EXT"; - case (VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT): - return os << "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; - case (VK_THREAD_IDLE_KHR): - return os << "VK_THREAD_IDLE_KHR"; - case (VK_THREAD_DONE_KHR): - return os << "VK_THREAD_DONE_KHR"; - case (VK_OPERATION_DEFERRED_KHR): - return os << "VK_OPERATION_DEFERRED_KHR"; - case (VK_OPERATION_NOT_DEFERRED_KHR): - return os << "VK_OPERATION_NOT_DEFERRED_KHR"; - case (VK_PIPELINE_COMPILE_REQUIRED_EXT): - return os << "VK_PIPELINE_COMPILE_REQUIRED_EXT"; - case (VK_RESULT_MAX_ENUM): - return os << "VK_RESULT_MAX_ENUM"; - case (VK_ERROR_COMPRESSION_EXHAUSTED_EXT): - return os << "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; - case (VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; - case (VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR): - return os << "VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR"; - case (VK_ERROR_INCOMPATIBLE_SHADER_BINARY_EXT): - return os << "VK_ERROR_INCOMPATIBLE_SHADER_BINARY_EXT"; - case (VK_PIPELINE_BINARY_MISSING_KHR): - return os << "VK_PIPELINE_BINARY_MISSING_KHR"; - case (VK_ERROR_NOT_ENOUGH_SPACE_KHR): - return os << "VK_ERROR_NOT_ENOUGH_SPACE_KHR"; - } - return os << static_cast(result); -} - const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection); bool string_eq(const char* a, const char* b) noexcept; From bacab744854bfa60be4af85c2459bcae1680ae41 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 21 Jul 2025 11:07:47 -0500 Subject: [PATCH 15/65] Large refactor of test framework Makes many changes to test infrastructure: * Split test_util into separate files for maintainability * Move new files into tests/framework/util * Move FolderManager into separate file from test_environment * Rename COMMON_UNIX_PLATFORMS to TESTING_COMMON_UNIX_PLATFORMS * Rewrote unix path redirection logic, relies on FileSystemManager * Relative paths are actually relative now * Split unsecured folder location into unsecured driver/implicit layer/explicit layer/settings * Fix bugs in tests that are attributable to old path redirection logic. * Use normalized paths in test framework - ie normalize paths so they can be compared easily * Make running with elevated privileges a framework level setting * Split tests that change elevated privileges into separate tests * Framework can set home & xdg env-vars at startup * Platform specific folders are only defined on those platforms * PlatformShim::add_manifest is now windows only, reflecting that only windows implemented it --- .../dispatch_table_helper_generator.py | 2 +- tests/CMakeLists.txt | 8 +- tests/framework/CMakeLists.txt | 53 +- tests/framework/README.md | 3 +- tests/framework/framework_config.h.in | 2 +- tests/framework/icd/test_icd.cpp | 7 + tests/framework/icd/test_icd.h | 11 +- tests/framework/json_writer.h | 164 ---- .../generated/vk_dispatch_table_helper.h | 2 +- tests/framework/layer/layer_util.h | 8 +- tests/framework/layer/test_layer.cpp | 9 +- tests/framework/layer/test_layer.h | 10 +- tests/framework/shim/shim.h | 80 +- tests/framework/shim/shim_common.cpp | 114 +-- tests/framework/shim/unix_shim.cpp | 102 ++- tests/framework/shim/windows_shim.cpp | 12 +- tests/framework/test_environment.cpp | 591 ++++-------- tests/framework/test_environment.h | 321 ++----- tests/framework/test_util.cpp | 375 -------- tests/framework/test_util.h | 861 ------------------ tests/framework/util/CMakeLists.txt | 85 ++ tests/framework/util/builder_defines.h | 64 ++ tests/framework/util/dispatch_table.cpp | 174 ++++ tests/framework/util/dispatch_table.h | 178 ++++ tests/framework/util/dispatchable_handle.h | 61 ++ .../util/dynamic_library_wrapper.cpp | 165 ++++ .../framework/util/dynamic_library_wrapper.h | 71 ++ tests/framework/util/env_var_wrapper.cpp | 184 ++++ tests/framework/util/env_var_wrapper.h | 85 ++ tests/framework/util/equality_helpers.cpp | 206 +++++ tests/framework/util/equality_helpers.h | 83 ++ tests/framework/util/folder_manager.cpp | 243 +++++ tests/framework/util/folder_manager.h | 103 +++ tests/framework/util/functions.h | 54 ++ tests/framework/util/get_executable_path.cpp | 136 +++ tests/framework/util/get_executable_path.h | 32 + tests/framework/util/json_writer.cpp | 152 ++++ tests/framework/util/json_writer.h | 75 ++ tests/framework/util/manifest_builders.cpp | 163 ++++ tests/framework/util/manifest_builders.h | 112 +++ tests/framework/util/platform_wsi.cpp | 72 ++ tests/framework/util/platform_wsi.h | 29 + tests/framework/util/test_defines.h | 97 ++ .../framework/util/vulkan_object_wrappers.cpp | 112 +++ tests/framework/util/vulkan_object_wrappers.h | 101 ++ tests/framework/util/wide_char_handling.cpp | 65 ++ tests/framework/util/wide_char_handling.h | 42 + .../dynamic_loader_behavior/dynamic_library.h | 9 +- .../test_dynamic_linking.cpp | 2 +- tests/loader_alloc_callback_tests.cpp | 4 +- tests/loader_envvar_tests.cpp | 261 ++++-- tests/loader_layer_tests.cpp | 188 ++-- tests/loader_regression_tests.cpp | 54 +- tests/loader_settings_tests.cpp | 186 ++-- tests/loader_testing_main.cpp | 10 +- tests/loader_unknown_ext_tests.cpp | 2 + tests/loader_version_tests.cpp | 6 +- 57 files changed, 3802 insertions(+), 2599 deletions(-) delete mode 100644 tests/framework/json_writer.h delete mode 100644 tests/framework/test_util.cpp delete mode 100644 tests/framework/test_util.h create mode 100644 tests/framework/util/CMakeLists.txt create mode 100644 tests/framework/util/builder_defines.h create mode 100644 tests/framework/util/dispatch_table.cpp create mode 100644 tests/framework/util/dispatch_table.h create mode 100644 tests/framework/util/dispatchable_handle.h create mode 100644 tests/framework/util/dynamic_library_wrapper.cpp create mode 100644 tests/framework/util/dynamic_library_wrapper.h create mode 100644 tests/framework/util/env_var_wrapper.cpp create mode 100644 tests/framework/util/env_var_wrapper.h create mode 100644 tests/framework/util/equality_helpers.cpp create mode 100644 tests/framework/util/equality_helpers.h create mode 100644 tests/framework/util/folder_manager.cpp create mode 100644 tests/framework/util/folder_manager.h create mode 100644 tests/framework/util/functions.h create mode 100644 tests/framework/util/get_executable_path.cpp create mode 100644 tests/framework/util/get_executable_path.h create mode 100644 tests/framework/util/json_writer.cpp create mode 100644 tests/framework/util/json_writer.h create mode 100644 tests/framework/util/manifest_builders.cpp create mode 100644 tests/framework/util/manifest_builders.h create mode 100644 tests/framework/util/platform_wsi.cpp create mode 100644 tests/framework/util/platform_wsi.h create mode 100644 tests/framework/util/test_defines.h create mode 100644 tests/framework/util/vulkan_object_wrappers.cpp create mode 100644 tests/framework/util/vulkan_object_wrappers.h create mode 100644 tests/framework/util/wide_char_handling.cpp create mode 100644 tests/framework/util/wide_char_handling.h diff --git a/scripts/generators/dispatch_table_helper_generator.py b/scripts/generators/dispatch_table_helper_generator.py index 410938a92..cf0060990 100644 --- a/scripts/generators/dispatch_table_helper_generator.py +++ b/scripts/generators/dispatch_table_helper_generator.py @@ -60,7 +60,7 @@ def generate(self): #include #include #include -#include "vk_layer_dispatch_table.h" +#include "loader/generated/vk_layer_dispatch_table.h" ''') diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8145f638d..e719dfce4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -68,6 +68,8 @@ endif() option(ENABLE_LIVE_VERIFICATION_TESTS "Enable tests which expect to run on live drivers. Meant for manual verification only" OFF) +set(TEST_EXECUTION_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/executing_tests") + include(GoogleTest) add_subdirectory(framework) @@ -94,7 +96,11 @@ add_executable( loader_testing_main.cpp loader_fuzz_tests.cpp ) -target_link_libraries(test_fuzzing PUBLIC testing_dependencies vulkan) +target_link_libraries(test_fuzzing PUBLIC testing_dependencies) +# testing_dependencies already links to vulkan when linking statically +if (NOT APPLE_STATIC_LOADER) + target_link_libraries(test_fuzzing PUBLIC vulkan) +endif() target_include_directories(test_fuzzing PUBLIC ${CMAKE_SOURCE_DIR}/loader ${CMAKE_SOURCE_DIR}/loader/generated) # Threading tests live in separate executabe just for threading tests as it'll need support diff --git a/tests/framework/CMakeLists.txt b/tests/framework/CMakeLists.txt index 0dcf46eb8..0e6bff12a 100644 --- a/tests/framework/CMakeLists.txt +++ b/tests/framework/CMakeLists.txt @@ -15,38 +15,15 @@ # limitations under the License. # ~~~ -add_library(testing_framework_util STATIC test_util.cpp) -target_link_libraries(testing_framework_util PUBLIC loader_common_options Vulkan::Headers) - -if(UNIX OR APPLE) - target_link_libraries(testing_framework_util PUBLIC ${CMAKE_DL_LIBS}) -endif() - -if(UNIX) - target_compile_options(testing_framework_util PUBLIC -fPIC) -endif() -# Gives access to all headers in the framework folder, in the framework binary, and in the whole project (mainly for loader/generated) -target_include_directories(testing_framework_util PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}/loader/generated") +#configure the framework_config.h.in file - used to locate all the binaries generated so that it can be used in the tests +#setup framework_config_temp.h.in in the current binary directory +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/framework_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/framework_config_temp.h.in") -if (UNIX) - if (LOADER_ENABLE_ADDRESS_SANITIZER) - target_compile_options(testing_framework_util PUBLIC -fsanitize=address,undefined) - target_link_options(testing_framework_util PUBLIC -fsanitize=address,undefined) - endif() - if (LOADER_ENABLE_THREAD_SANITIZER) - target_compile_options(testing_framework_util PUBLIC -fsanitize=thread) - target_link_options(testing_framework_util PUBLIC -fsanitize=thread) - target_compile_options(gtest PUBLIC -fsanitize=thread) - target_link_options(gtest PUBLIC -fsanitize=thread) - endif() -endif() +# setup framework_config_$ using framework_config_temp.h.in as a source +file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/framework_config_$.h" INPUT "${CMAKE_CURRENT_BINARY_DIR}/framework_config_temp.h.in") -if (MSVC) - # silence hidden class member warnings in test framework - target_compile_options(testing_framework_util PUBLIC /wd4458) - # Make sure exception handling is enabled for the test framework - target_compile_options(testing_framework_util PUBLIC /EHsc) -endif() +# Create a variable to hold the path to the correct header file, used as a compiler definition in the utils target +set(FRAMEWORK_CONFIG_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/framework_config_$.h") function(AddSharedLibrary LIBRARY_NAME) set(singleValueArgs DEF_FILE) @@ -63,26 +40,14 @@ function(AddSharedLibrary LIBRARY_NAME) endif() endfunction() +add_subdirectory(util) add_subdirectory(data) add_subdirectory(shim) add_subdirectory(icd) add_subdirectory(layer) -#configure the framework_config.h.in file - used to locate all the binaries generated so that it can be used in the tests -#setup framework_config_temp.h.in in the current binary directory -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/framework_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/framework_config_temp.h.in") - -# setup framework_config_$ using framework_config_temp.h.in as a source -file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/framework_config_$.h" INPUT "${CMAKE_CURRENT_BINARY_DIR}/framework_config_temp.h.in") - -# Add a compiler definition for the path to framework_config.h with the correct config -target_compile_definitions(testing_framework_util PUBLIC FRAMEWORK_CONFIG_HEADER="framework_config_$.h") - add_library(testing_dependencies STATIC test_environment.cpp test_environment.h) target_link_libraries(testing_dependencies PUBLIC gtest Vulkan::Headers testing_framework_util shim-library) target_include_directories(testing_dependencies PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) -if (APPLE_STATIC_LOADER) - target_compile_definitions(testing_dependencies PUBLIC "APPLE_STATIC_LOADER=1") - target_link_libraries(testing_dependencies PUBLIC vulkan) -endif() + diff --git a/tests/framework/README.md b/tests/framework/README.md index 1dc5b73a4..20e1209ae 100644 --- a/tests/framework/README.md +++ b/tests/framework/README.md @@ -136,8 +136,7 @@ There are many utilities that the test framework and tests have access to. These * Environment Variable Wrapper: `EnvVarWrapper` for creating, setting, getting, and removing environment variables in a RAII manner * Windows API error handling helpers * filesystem abstractions: - * `create_folder`/`delete_folder` - * `FolderManager` + * `Folder` * Creates a new folder with the given name at construction time. * Allows writing manifests and files (eg, icd or layer binaries) * Automatically destroys the folder and all contained files at destruction diff --git a/tests/framework/framework_config.h.in b/tests/framework/framework_config.h.in index b70e42b11..8c0c1f78e 100644 --- a/tests/framework/framework_config.h.in +++ b/tests/framework/framework_config.h.in @@ -26,7 +26,7 @@ */ #pragma once -#define FRAMEWORK_BUILD_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" +#define TEST_EXECUTION_DIRECTORY "${TEST_EXECUTION_DIRECTORY}" #define FRAMEWORK_VULKAN_LIBRARY_PATH "$" diff --git a/tests/framework/icd/test_icd.cpp b/tests/framework/icd/test_icd.cpp index 7e09c6d61..b657672b7 100644 --- a/tests/framework/icd/test_icd.cpp +++ b/tests/framework/icd/test_icd.cpp @@ -29,6 +29,13 @@ #include "test_icd.h" +#include +#include + +#include + +#include "equality_helpers.h" + // export vk_icdGetInstanceProcAddr #if !defined(TEST_ICD_EXPORT_ICD_GIPA) #define TEST_ICD_EXPORT_ICD_GIPA 0 diff --git a/tests/framework/icd/test_icd.h b/tests/framework/icd/test_icd.h index 33b1dc714..57ee27d4b 100644 --- a/tests/framework/icd/test_icd.h +++ b/tests/framework/icd/test_icd.h @@ -27,7 +27,13 @@ #pragma once -#include "test_util.h" +#include +#include +#include + +#include "util/dispatchable_handle.h" +#include "util/platform_wsi.h" +#include "util/functions.h" #include "layer/layer_util.h" @@ -70,6 +76,9 @@ inline std::ostream& operator<<(std::ostream& os, const InterfaceVersionCheck& r } return os << static_cast(result); } + +using VulkanUUID = std::array; + // clang-format on // Move only type because it holds a DispatchableHandle diff --git a/tests/framework/json_writer.h b/tests/framework/json_writer.h deleted file mode 100644 index 4a4cd02a0..000000000 --- a/tests/framework/json_writer.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2023 The Khronos Group Inc. - * Copyright (c) 2023 Valve Corporation - * Copyright (c) 2023 LunarG, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and/or associated documentation files (the "Materials"), to - * deal in the Materials without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Materials, and to permit persons to whom the Materials are - * furnished to do so, subject to the following conditions: - * - * The above copyright notice(s) and this permission notice shall be included in - * all copies or substantial portions of the Materials. - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE - * USE OR OTHER DEALINGS IN THE MATERIALS. - * - * Author: Charles Giessen - */ - -#pragma once - -#include -#include -#include - -// Utility class to simplify the writing of JSON manifest files - -struct JsonWriter { - std::string output; - - // the bool represents whether an object has been written & a comma is needed - std::stack stack; - - void StartObject() { - CommaAndNewLine(); - Indent(); - output += "{"; - stack.push(false); - } - void StartKeyedObject(std::string const& key) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": {"; - stack.push(false); - } - void EndObject() { - stack.pop(); - output += "\n"; - Indent(); - output += "}"; - } - void StartKeyedArray(std::string const& key) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": ["; - stack.push(false); - } - void StartArray() { - CommaAndNewLine(); - Indent(); - output += "["; - stack.push(false); - } - void EndArray() { - stack.pop(); - output += "\n"; - Indent(); - output += "]"; - } - - void AddKeyedString(std::string const& key, std::string const& value) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": \"" + escape(value) + "\""; - } - void AddString(std::string const& value) { - CommaAndNewLine(); - Indent(); - output += "\"" + escape(value) + "\""; - } -#if defined(WIN32) - void AddKeyedString(std::string const& key, std::wstring const& value) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": \"" + escape(narrow(value)) + "\""; - } - void AddString(std::wstring const& value) { - CommaAndNewLine(); - Indent(); - output += "\"" + escape(narrow(value)) + "\""; - } -#endif - - void AddKeyedBool(std::string const& key, bool value) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": " + (value ? "true" : "false"); - } - void AddBool(bool value) { - CommaAndNewLine(); - Indent(); - output += std::string(value ? "true" : "false"); - } - - void AddKeyedNumber(std::string const& key, double number) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": " + std::to_string(number); - } - void AddNumber(double number) { - CommaAndNewLine(); - Indent(); - output += std::to_string(number); - } - - void AddKeyedInteger(std::string const& key, int64_t number) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": " + std::to_string(number); - } - void AddInteger(int64_t number) { - CommaAndNewLine(); - Indent(); - output += std::to_string(number); - } - - // Json doesn't allow `\` in strings, it must be escaped. Thus we have to convert '\\' to '\\\\' in strings - static std::string escape(std::string const& in_path) { - std::string out; - for (auto& c : in_path) { - if (c == '\\') - out += "\\\\"; - else - out += c; - } - return out; - } - static std::string escape(std::filesystem::path const& in_path) { return escape(narrow(in_path.native())); } - - private: - void CommaAndNewLine() { - if (stack.size() > 0) { - if (stack.top() == false) { - stack.top() = true; - } else { - output += ","; - } - output += "\n"; - } - } - void Indent() { - for (uint32_t i = 0; i < stack.size(); i++) { - output += '\t'; - } - } -}; diff --git a/tests/framework/layer/generated/vk_dispatch_table_helper.h b/tests/framework/layer/generated/vk_dispatch_table_helper.h index 498352781..4b54ba2f7 100644 --- a/tests/framework/layer/generated/vk_dispatch_table_helper.h +++ b/tests/framework/layer/generated/vk_dispatch_table_helper.h @@ -28,7 +28,7 @@ #include #include #include -#include "vk_layer_dispatch_table.h" +#include "loader/generated/vk_layer_dispatch_table.h" // clang-format off static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDispatchTable *table, PFN_vkGetDeviceProcAddr gpa) { diff --git a/tests/framework/layer/layer_util.h b/tests/framework/layer/layer_util.h index 3f4bc755a..7107b4642 100644 --- a/tests/framework/layer/layer_util.h +++ b/tests/framework/layer/layer_util.h @@ -27,7 +27,9 @@ #pragma once -#include "test_util.h" +#include "builder_defines.h" + +#include "vulkan_object_wrappers.h" struct LayerDefinition { BUILDER_VALUE(std::string, layerName) @@ -38,10 +40,10 @@ struct LayerDefinition { VkLayerProperties get() const noexcept { VkLayerProperties props{}; - copy_string_to_char_array(layerName, &props.layerName[0], VK_MAX_EXTENSION_NAME_SIZE); + layerName.copy(props.layerName, VK_MAX_EXTENSION_NAME_SIZE); props.specVersion = specVersion; props.implementationVersion = implementationVersion; - copy_string_to_char_array(description, &props.description[0], VK_MAX_DESCRIPTION_SIZE); + description.copy(props.description, VK_MAX_DESCRIPTION_SIZE); return props; } }; diff --git a/tests/framework/layer/test_layer.cpp b/tests/framework/layer/test_layer.cpp index f891927ad..b98f543f4 100644 --- a/tests/framework/layer/test_layer.cpp +++ b/tests/framework/layer/test_layer.cpp @@ -27,6 +27,13 @@ #include "test_layer.h" +#include + +#include + +#include "util/test_defines.h" +#include "util/equality_helpers.h" + #include "generated/vk_dispatch_table_helper.h" // export the enumeration functions instance|device+layer|extension @@ -243,7 +250,7 @@ VKAPI_ATTR VkResult VKAPI_CALL test_vkCreateInstance(const VkInstanceCreateInfo* VkLayerInstanceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; - PFN_vk_icdGetPhysicalDeviceProcAddr fpGetPhysicalDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetPhysicalDeviceProcAddr; + PFN_GetPhysicalDeviceProcAddr fpGetPhysicalDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetPhysicalDeviceProcAddr; PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); if (fpCreateInstance == NULL) { return VK_ERROR_INITIALIZATION_FAILED; diff --git a/tests/framework/layer/test_layer.h b/tests/framework/layer/test_layer.h index 14e60bcc8..085be617d 100644 --- a/tests/framework/layer/test_layer.h +++ b/tests/framework/layer/test_layer.h @@ -27,14 +27,18 @@ #pragma once -#include "test_util.h" - #include +#include -#include "layer/layer_util.h" +#include +#include #include "loader/generated/vk_layer_dispatch_table.h" +#include "util/functions.h" + +#include "layer_util.h" + /* Interface Version 0 */ diff --git a/tests/framework/shim/shim.h b/tests/framework/shim/shim.h index bc47c3987..4dbd472c6 100644 --- a/tests/framework/shim/shim.h +++ b/tests/framework/shim/shim.h @@ -27,13 +27,19 @@ #pragma once -#include "test_util.h" +#include +#include +#include +#include #include #include -#include +#include -#if defined(WIN32) +#if defined(_WIN32) + +#include +#include #include #include #include @@ -46,10 +52,18 @@ #include #endif -enum class ManifestCategory { implicit_layer, explicit_layer, icd, settings }; +#include "util/test_defines.h" + +#if TESTING_COMMON_UNIX_PLATFORMS +#include +#endif + +#include "util/folder_manager.h" +#include "util/functions.h" + enum class GpuType { unspecified, integrated, discrete, external }; -#if defined(WIN32) +#if defined(_WIN32) #define VK_VARIANT_REG_STR "" #define VK_VARIANT_REG_STR_W L"" @@ -117,15 +131,15 @@ struct D3DKMT_Adapter { D3DKMT_Adapter& add_path(std::filesystem::path src, std::vector& dest); }; -#elif COMMON_UNIX_PLATFORMS +#elif TESTING_COMMON_UNIX_PLATFORMS struct DirEntry { DIR* directory = nullptr; - std::string folder_path; + fs::Folder* folder_represented; std::vector contents; // the current item being read by an app (incremented by readdir, reset to zero by opendir & closedir) size_t current_index = 0; - bool is_fake_path = false; // true when this entry is for folder redirection + // bool is_fake_path = false; // true when this entry is for folder redirection }; #endif @@ -136,12 +150,9 @@ struct FrameworkEnvironment; // forward declaration // defined in the .cpp wont be found by the rest of the application struct PlatformShim { PlatformShim() { fputs_stderr_log.reserve(65536); } - PlatformShim(GetFoldersFunc get_folders_by_name_function) : get_folders_by_name_function(get_folders_by_name_function) { - fputs_stderr_log.reserve(65536); - } // Used to get info about which drivers & layers have been added to folders - GetFoldersFunc get_folders_by_name_function; + fs::FileSystemManager* file_system_manager; // Captures the output to stderr from fputs & fputc - aka the output of loader_log() std::string fputs_stderr_log; @@ -149,28 +160,25 @@ struct PlatformShim { // Test Framework interface void reset(); - void redirect_all_paths(std::filesystem::path const& path); - void redirect_category(std::filesystem::path const& new_path, ManifestCategory category); - // fake paths are paths that the loader normally looks in but actually point to locations inside the test framework - void set_fake_path(ManifestCategory category, std::filesystem::path const& path); + // void set_fake_path(ManifestCategory category, std::filesystem::path const& path); // known paths are real paths but since the test framework guarantee's the order files are found in, files in these paths // need to be ordered correctly - void add_known_path(std::filesystem::path const& path); - - void add_manifest(ManifestCategory category, std::filesystem::path const& path); - void add_unsecured_manifest(ManifestCategory category, std::filesystem::path const& path); + // void add_known_path(std::filesystem::path const& path); void clear_logs() { fputs_stderr_log.clear(); } bool find_in_log(std::string const& search_text) const { return fputs_stderr_log.find(search_text) != std::string::npos; } // platform specific shim interface -#if defined(WIN32) +#if defined(_WIN32) // Control Platform Elevation Level void set_elevated_privilege(bool elev) { elevation_level = (elev) ? SECURITY_MANDATORY_HIGH_RID : SECURITY_MANDATORY_LOW_RID; } unsigned long elevation_level = SECURITY_MANDATORY_LOW_RID; + void add_manifest_to_registry(ManifestCategory category, std::filesystem::path const& path); + void add_unsecured_manifest_to_registry(ManifestCategory category, std::filesystem::path const& path); + void add_dxgi_adapter(GpuType gpu_preference, DXGI_ADAPTER_DESC1 desc1); void add_d3dkmt_adapter(D3DKMT_Adapter const& adapter); void set_app_package_path(std::filesystem::path const& path); @@ -202,27 +210,18 @@ struct PlatformShim { size_t created_key_count = 0; std::vector created_keys; -#elif COMMON_UNIX_PLATFORMS - bool is_fake_path(std::filesystem::path const& path); - std::filesystem::path const& get_real_path_from_fake_path(std::filesystem::path const& path); +#elif TESTING_COMMON_UNIX_PLATFORMS + // Add a filename that dlopen can "find" with no absolute or relative path given. + // EG, dlopen("libfoobar.so") gets turned into dlopen(actual_path) + void add_system_library(std::string const& filename, std::filesystem::path const& actual_path); - void redirect_path(std::filesystem::path const& path, std::filesystem::path const& new_path); - void remove_redirect(std::filesystem::path const& path); + // Returns the real path of the system library "filename", if it exists. Else returns empty path + std::filesystem::path get_system_library(std::string const& filename); - bool is_known_path(std::filesystem::path const& path); - void remove_known_path(std::filesystem::path const& path); - - void redirect_dlopen_name(std::filesystem::path const& filename, std::filesystem::path const& actual_path); - bool is_dlopen_redirect_name(std::filesystem::path const& filename); - - std::filesystem::path query_default_redirect_path(ManifestCategory category); + std::unordered_map system_library_redirection_map; void set_app_package_path(std::filesystem::path const& path); - std::unordered_map redirection_map; - std::unordered_map dlopen_redirection_map; - std::unordered_set known_path_set; - void set_elevated_privilege(bool elev) { use_fake_elevation = elev; } bool use_fake_elevation = false; @@ -232,6 +231,7 @@ struct PlatformShim { std::string bundle_contents; #endif #endif + bool is_finished_setup = false; bool is_during_destruction = false; }; @@ -240,12 +240,12 @@ std::string category_path_name(ManifestCategory category); extern "C" { // dynamically link on windows and macos -#if defined(WIN32) || defined(__APPLE__) -using PFN_get_platform_shim = PlatformShim* (*)(GetFoldersFunc get_folders_by_name_function); +#if defined(_WIN32) || defined(__APPLE__) +using PFN_get_platform_shim = PlatformShim* (*)(); #define GET_PLATFORM_SHIM_STR "get_platform_shim" #elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) || defined(__QNX__) // statically link on linux -PlatformShim* get_platform_shim(GetFoldersFunc get_folders_by_name_function); +PlatformShim* get_platform_shim(); #endif } diff --git a/tests/framework/shim/shim_common.cpp b/tests/framework/shim/shim_common.cpp index a21902ceb..f3d021fb6 100644 --- a/tests/framework/shim/shim_common.cpp +++ b/tests/framework/shim/shim_common.cpp @@ -27,20 +27,16 @@ #include "shim.h" -void PlatformShim::redirect_all_paths(std::filesystem::path const& path) { - redirect_category(path, ManifestCategory::implicit_layer); - redirect_category(path, ManifestCategory::explicit_layer); - redirect_category(path, ManifestCategory::icd); -} +#include "env_var_wrapper.h" std::vector parse_env_var_list(std::string const& var) { std::vector items; size_t start = 0; size_t len = 0; for (size_t i = 0; i < var.size(); i++) { -#if defined(WIN32) +#if defined(_WIN32) if (var[i] == ';') { -#elif COMMON_UNIX_PLATFORMS +#elif TESTING_COMMON_UNIX_PLATFORMS if (var[i] == ':') { #endif if (len != 0) { @@ -58,7 +54,7 @@ std::vector parse_env_var_list(std::string const& var) { return items; } -#if defined(WIN32) +#if defined(_WIN32) D3DKMT_Adapter& D3DKMT_Adapter::add_driver_manifest_path(std::filesystem::path const& src) { return add_path(src, driver_paths); } D3DKMT_Adapter& D3DKMT_Adapter::add_implicit_layer_manifest_path(std::filesystem::path const& src) { @@ -91,10 +87,7 @@ void PlatformShim::reset() { hkey_current_user_settings.clear(); } -void PlatformShim::set_fake_path([[maybe_unused]] ManifestCategory category, [[maybe_unused]] std::filesystem::path const& path) {} -void PlatformShim::add_known_path([[maybe_unused]] std::filesystem::path const& path) {} - -void PlatformShim::add_manifest(ManifestCategory category, std::filesystem::path const& path) { +void PlatformShim::add_manifest_to_registry(ManifestCategory category, std::filesystem::path const& path) { if (category == ManifestCategory::settings) { hkey_local_machine_settings.emplace_back(path); } else if (category == ManifestCategory::implicit_layer) { @@ -106,7 +99,7 @@ void PlatformShim::add_manifest(ManifestCategory category, std::filesystem::path } } -void PlatformShim::add_unsecured_manifest(ManifestCategory category, std::filesystem::path const& path) { +void PlatformShim::add_unsecured_manifest_to_registry(ManifestCategory category, std::filesystem::path const& path) { if (category == ManifestCategory::settings) { hkey_current_user_settings.emplace_back(path); } else if (category == ManifestCategory::implicit_layer) { @@ -146,9 +139,7 @@ void PlatformShim::add_CM_Device_ID([[maybe_unused]] std::wstring const& id, [[m // // add_key_value_string(id_key, "VulkanLayerName", layer_path.c_str()); } -void PlatformShim::redirect_category(std::filesystem::path const&, ManifestCategory) {} - -#elif COMMON_UNIX_PLATFORMS +#elif TESTING_COMMON_UNIX_PLATFORMS #include #include @@ -162,93 +153,12 @@ std::string category_path_name(ManifestCategory category) { return "icd.d"; } -void PlatformShim::reset() { redirection_map.clear(); } - -bool PlatformShim::is_fake_path(std::filesystem::path const& path) { return redirection_map.count(path) > 0; } -std::filesystem::path const& PlatformShim::get_real_path_from_fake_path(std::filesystem::path const& path) { - return redirection_map.at(path); -} -void PlatformShim::redirect_path(std::filesystem::path const& path, std::filesystem::path const& new_path) { - redirection_map[path] = new_path; -} -void PlatformShim::remove_redirect(std::filesystem::path const& path) { redirection_map.erase(path); } - -bool PlatformShim::is_known_path(std::filesystem::path const& path) { return known_path_set.count(path) > 0; } -void PlatformShim::add_known_path(std::filesystem::path const& path) { known_path_set.insert(path); } -void PlatformShim::remove_known_path(std::filesystem::path const& path) { known_path_set.erase(path); } - -void PlatformShim::add_manifest([[maybe_unused]] ManifestCategory category, [[maybe_unused]] std::filesystem::path const& path) {} -void PlatformShim::add_unsecured_manifest([[maybe_unused]] ManifestCategory category, - [[maybe_unused]] std::filesystem::path const& path) {} - -void PlatformShim::set_app_package_path(std::filesystem::path const& path) {} - -void parse_and_add_env_var_override(std::vector& paths, std::string env_var_contents) { - auto parsed_paths = parse_env_var_list(env_var_contents); - paths.insert(paths.end(), parsed_paths.begin(), parsed_paths.end()); -} - -void PlatformShim::redirect_category(std::filesystem::path const& new_path, ManifestCategory category) { - std::vector paths; - auto home = std::filesystem::path(get_env_var("HOME")); - if (category == ManifestCategory::settings) { - redirect_path(home / ".local/share/vulkan" / category_path_name(category), new_path); - return; - } - - if (!home.empty()) { - paths.push_back((home / ".config")); - paths.push_back((home / ".local/share")); - } - // Don't report errors on apple - these env-vars are not suppose to be defined - bool report_errors = true; -#if defined(__APPLE__) - report_errors = false; -#endif - parse_and_add_env_var_override(paths, get_env_var("XDG_CONFIG_HOME", report_errors)); - if (category == ManifestCategory::explicit_layer) { - parse_and_add_env_var_override(paths, get_env_var("VK_LAYER_PATH", false)); // don't report failure - } - if (category == ManifestCategory::implicit_layer) { - parse_and_add_env_var_override(paths, get_env_var("VK_IMPLICIT_LAYER_PATH", false)); // don't report failure - } - parse_and_add_env_var_override(paths, FALLBACK_DATA_DIRS); - parse_and_add_env_var_override(paths, FALLBACK_CONFIG_DIRS); - - auto sys_conf_dir = std::string(SYSCONFDIR); - if (!sys_conf_dir.empty()) { - paths.push_back(sys_conf_dir); - } -#if defined(EXTRASYSCONFDIR) - // EXTRASYSCONFDIR default is /etc, if SYSCONFDIR wasn't defined, it will have /etc put - // as its default. Don't want to double add it - auto extra_sys_conf_dir = std::string(EXTRASYSCONFDIR); - if (!extra_sys_conf_dir.empty() && sys_conf_dir != extra_sys_conf_dir) { - paths.push_back(extra_sys_conf_dir); - } -#endif - - for (auto& path : paths) { - if (!path.empty()) { - redirect_path(std::filesystem::path(path) / "vulkan" / category_path_name(category), new_path); - } - } -} - -void PlatformShim::set_fake_path(ManifestCategory category, std::filesystem::path const& path) { - // use /etc as the 'redirection path' by default since its always searched - redirect_path(std::filesystem::path(SYSCONFDIR) / "vulkan" / category_path_name(category), path); -} - -void PlatformShim::redirect_dlopen_name(std::filesystem::path const& filename, std::filesystem::path const& actual_path) { - dlopen_redirection_map[filename] = actual_path; -} - -bool PlatformShim::is_dlopen_redirect_name(std::filesystem::path const& filename) { - return dlopen_redirection_map.count(filename) == 1; +void PlatformShim::add_system_library(std::string const& filename, std::filesystem::path const& actual_path) { + system_library_redirection_map[filename] = actual_path; } -std::filesystem::path PlatformShim::query_default_redirect_path(ManifestCategory category) { - return std::filesystem::path(SYSCONFDIR) / "vulkan" / category_path_name(category); +std::filesystem::path PlatformShim::get_system_library(std::string const& filename) { + return system_library_redirection_map.count(filename) == 1 ? system_library_redirection_map.at(filename) + : std::filesystem::path{}; } #endif diff --git a/tests/framework/shim/unix_shim.cpp b/tests/framework/shim/unix_shim.cpp index a3abba5a8..da3fd151a 100644 --- a/tests/framework/shim/unix_shim.cpp +++ b/tests/framework/shim/unix_shim.cpp @@ -28,21 +28,26 @@ #include "shim.h" #include +#include #if defined(__APPLE__) #include #endif +#include + +#include "util/folder_manager.h" + PlatformShim platform_shim; extern "C" { #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) || defined(__QNX__) -PlatformShim* get_platform_shim(GetFoldersFunc get_folders_by_name_function) { - platform_shim = PlatformShim(get_folders_by_name_function); +PlatformShim* get_platform_shim() { + platform_shim = PlatformShim(); return &platform_shim; } #elif defined(__APPLE__) -FRAMEWORK_EXPORT PlatformShim* get_platform_shim(GetFoldersFunc get_folders_by_name_function) { - platform_shim = PlatformShim(get_folders_by_name_function); +FRAMEWORK_EXPORT PlatformShim* get_platform_shim() { + platform_shim = PlatformShim(); return &platform_shim; } #endif @@ -141,20 +146,16 @@ FRAMEWORK_EXPORT DIR* OPENDIR_FUNC_NAME(const char* path_name) { #if !defined(__APPLE__) if (!real_opendir) real_opendir = (PFN_OPENDIR)dlsym(RTLD_NEXT, "opendir"); #endif - if (platform_shim.is_during_destruction) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_opendir(path_name); } - DIR* dir; - if (platform_shim.is_fake_path(path_name)) { - auto real_path_name = platform_shim.get_real_path_from_fake_path(std::filesystem::path(path_name)); - dir = real_opendir(real_path_name.c_str()); - platform_shim.dir_entries.push_back(DirEntry{dir, std::string(path_name), {}, 0, true}); - } else if (platform_shim.is_known_path(path_name)) { - dir = real_opendir(path_name); - platform_shim.dir_entries.push_back(DirEntry{dir, std::string(path_name), {}, 0, false}); - } else { - dir = real_opendir(path_name); + DIR* dir = nullptr; + + if (auto* folder = platform_shim.file_system_manager->get_folder_for_given_path(path_name); folder != nullptr) { + dir = real_opendir(folder->location().c_str()); + platform_shim.dir_entries.push_back(DirEntry{dir, folder, {}, 0}); } + // Dont pass through as this allows non-test paths to escape containment return dir; } @@ -171,18 +172,19 @@ FRAMEWORK_EXPORT struct dirent* READDIR_FUNC_NAME(DIR* dir_stream) { } #endif - if (platform_shim.is_during_destruction) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_readdir(dir_stream); } auto it = std::find_if(platform_shim.dir_entries.begin(), platform_shim.dir_entries.end(), [dir_stream](DirEntry const& entry) { return entry.directory == dir_stream; }); + // Folder has not been opened with opendir previously, probably not a loader call so we should ignore it if (it == platform_shim.dir_entries.end()) { return real_readdir(dir_stream); } // Folder was found but this is the first file to be read from it if (it->current_index == 0) { - std::vector folder_contents; + std::vector readdir_contents; std::vector dirent_filenames; while (true) { errno = 0; @@ -198,21 +200,17 @@ FRAMEWORK_EXPORT struct dirent* READDIR_FUNC_NAME(DIR* dir_stream) { (dir_entry->d_name[0] == '.' && dir_entry->d_name[1] == '\0')) { continue; } - folder_contents.push_back(dir_entry); + readdir_contents.push_back(dir_entry); dirent_filenames.push_back(&dir_entry->d_name[0]); } - auto real_path = it->folder_path; - if (it->is_fake_path) { - real_path = platform_shim.redirection_map.at(it->folder_path); - } - auto filenames = platform_shim.get_folders_by_name_function(real_path.c_str()); - // Add the dirent structures in the order they appear in the FolderManager - // Ignore anything which wasn't in the FolderManager - for (auto const& file : filenames) { + // Add the dirent structures in the order they appear in the list of files provided by the Folder + // Ignore anything which wasn't in the Folder + auto folder_contents = it->folder_represented->get_files(); + for (auto const& file : folder_contents) { for (size_t i = 0; i < dirent_filenames.size(); i++) { if (file == dirent_filenames.at(i)) { - it->contents.push_back(folder_contents.at(i)); + it->contents.push_back(readdir_contents.at(i)); break; } } @@ -226,7 +224,7 @@ FRAMEWORK_EXPORT int CLOSEDIR_FUNC_NAME(DIR* dir_stream) { #if !defined(__APPLE__) if (!real_closedir) real_closedir = (PFN_CLOSEDIR)dlsym(RTLD_NEXT, "closedir"); #endif - if (platform_shim.is_during_destruction) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_closedir(dir_stream); } auto it = std::find_if(platform_shim.dir_entries.begin(), platform_shim.dir_entries.end(), @@ -243,48 +241,62 @@ FRAMEWORK_EXPORT int ACCESS_FUNC_NAME(const char* in_pathname, int mode) { #if !defined(__APPLE__) if (!real_access) real_access = (PFN_ACCESS)dlsym(RTLD_NEXT, "access"); #endif - std::filesystem::path path{in_pathname}; - if (!path.has_parent_path()) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_access(in_pathname, mode); } - if (platform_shim.is_fake_path(path.parent_path())) { - std::filesystem::path real_path = platform_shim.get_real_path_from_fake_path(path.parent_path()); + std::filesystem::path path{in_pathname}; + if (!path.has_parent_path()) { + return real_access(in_pathname, mode); + } else if (auto real_path = platform_shim.file_system_manager->get_real_path_of_redirected_path(path.parent_path()); + !real_path.empty()) { real_path /= path.filename(); return real_access(real_path.c_str(), mode); + } else if (platform_shim.file_system_manager->is_folder_path(path.parent_path())) { + return real_access(in_pathname, mode); + } else { + return -1; } - return real_access(in_pathname, mode); } FRAMEWORK_EXPORT FILE* FOPEN_FUNC_NAME(const char* in_filename, const char* mode) { #if !defined(__APPLE__) if (!real_fopen) real_fopen = (PFN_FOPEN)dlsym(RTLD_NEXT, "fopen"); #endif - std::filesystem::path path{in_filename}; - if (!path.has_parent_path()) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_fopen(in_filename, mode); } - FILE* f_ptr; - if (platform_shim.is_fake_path(path.parent_path())) { - auto real_path = platform_shim.get_real_path_from_fake_path(path.parent_path()) / path.filename(); - f_ptr = real_fopen(real_path.c_str(), mode); + std::filesystem::path path{in_filename}; + if (!path.has_parent_path()) { + return real_fopen(in_filename, mode); + } else if (auto real_path = platform_shim.file_system_manager->get_real_path_of_redirected_path(path.parent_path()); + !real_path.empty()) { + real_path /= path.filename(); + return real_fopen(real_path.c_str(), mode); } else { - f_ptr = real_fopen(in_filename, mode); + return real_fopen(in_filename, mode); } - - return f_ptr; } FRAMEWORK_EXPORT void* DLOPEN_FUNC_NAME(const char* in_filename, int flags) { #if !defined(__APPLE__) if (!real_dlopen) real_dlopen = (PFN_DLOPEN)dlsym(RTLD_NEXT, "dlopen"); #endif + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { + return real_dlopen(in_filename, flags); + } - if (platform_shim.is_dlopen_redirect_name(in_filename)) { - return real_dlopen(platform_shim.dlopen_redirection_map[in_filename].c_str(), flags); + std::filesystem::path path{in_filename}; + if (auto real_path = platform_shim.get_system_library(path); !real_path.empty()) { + return real_dlopen(real_path.c_str(), flags); + } else if (auto real_path = platform_shim.file_system_manager->get_real_path_of_redirected_path(path.parent_path()); + !real_path.empty()) { + real_path /= path.filename(); + return real_dlopen(real_path.c_str(), flags); + } else { + return real_dlopen(in_filename, flags); } - return real_dlopen(in_filename, flags); } FRAMEWORK_EXPORT uid_t GETEUID_FUNC_NAME(void) { diff --git a/tests/framework/shim/windows_shim.cpp b/tests/framework/shim/windows_shim.cpp index 1ca1a68e2..1c6bc0b9f 100644 --- a/tests/framework/shim/windows_shim.cpp +++ b/tests/framework/shim/windows_shim.cpp @@ -31,6 +31,10 @@ #include #endif +#include + +#include + #include #include @@ -38,6 +42,9 @@ #include "detours.h" +#include "util/dynamic_library_wrapper.h" +#include "util/wide_char_handling.h" + static PlatformShim platform_shim; extern "C" { @@ -512,8 +519,5 @@ BOOL WINAPI DllMain([[maybe_unused]] HINSTANCE hinst, DWORD dwReason, [[maybe_un } return TRUE; } -FRAMEWORK_EXPORT PlatformShim *get_platform_shim(GetFoldersFunc get_folders_by_name_function) { - platform_shim = PlatformShim(get_folders_by_name_function); - return &platform_shim; -} +FRAMEWORK_EXPORT PlatformShim *get_platform_shim() { return &platform_shim; } } diff --git a/tests/framework/test_environment.cpp b/tests/framework/test_environment.cpp index 0dea3f794..261d21149 100644 --- a/tests/framework/test_environment.cpp +++ b/tests/framework/test_environment.cpp @@ -27,151 +27,11 @@ #include "test_environment.h" +#include #include +#include -std::filesystem::path get_loader_path() { - auto loader_path = std::filesystem::path(FRAMEWORK_VULKAN_LIBRARY_PATH); - auto env_var_res = get_env_var("VK_LOADER_TEST_LOADER_PATH", false); - if (!env_var_res.empty()) { - loader_path = std::filesystem::path(env_var_res); - } - return loader_path; -} - -void init_vulkan_functions(VulkanFunctions& funcs) { -#if defined(APPLE_STATIC_LOADER) -#define GPA(name) name -#else -#define GPA(name) funcs.loader.get_symbol(#name) -#endif - - // clang-format off - funcs.vkGetInstanceProcAddr = GPA(vkGetInstanceProcAddr); - funcs.vkEnumerateInstanceExtensionProperties = GPA(vkEnumerateInstanceExtensionProperties); - funcs.vkEnumerateInstanceLayerProperties = GPA(vkEnumerateInstanceLayerProperties); - funcs.vkEnumerateInstanceVersion = GPA(vkEnumerateInstanceVersion); - funcs.vkCreateInstance = GPA(vkCreateInstance); - funcs.vkDestroyInstance = GPA(vkDestroyInstance); - funcs.vkEnumeratePhysicalDevices = GPA(vkEnumeratePhysicalDevices); - funcs.vkEnumeratePhysicalDeviceGroups = GPA(vkEnumeratePhysicalDeviceGroups); - funcs.vkGetPhysicalDeviceFeatures = GPA(vkGetPhysicalDeviceFeatures); - funcs.vkGetPhysicalDeviceFeatures2 = GPA(vkGetPhysicalDeviceFeatures2); - funcs.vkGetPhysicalDeviceFormatProperties = GPA(vkGetPhysicalDeviceFormatProperties); - funcs.vkGetPhysicalDeviceFormatProperties2 = GPA(vkGetPhysicalDeviceFormatProperties2); - funcs.vkGetPhysicalDeviceImageFormatProperties = GPA(vkGetPhysicalDeviceImageFormatProperties); - funcs.vkGetPhysicalDeviceImageFormatProperties2 = GPA(vkGetPhysicalDeviceImageFormatProperties2); - funcs.vkGetPhysicalDeviceSparseImageFormatProperties = GPA(vkGetPhysicalDeviceSparseImageFormatProperties); - funcs.vkGetPhysicalDeviceSparseImageFormatProperties2 = GPA(vkGetPhysicalDeviceSparseImageFormatProperties2); - funcs.vkGetPhysicalDeviceProperties = GPA(vkGetPhysicalDeviceProperties); - funcs.vkGetPhysicalDeviceProperties2 = GPA(vkGetPhysicalDeviceProperties2); - funcs.vkGetPhysicalDeviceQueueFamilyProperties = GPA(vkGetPhysicalDeviceQueueFamilyProperties); - funcs.vkGetPhysicalDeviceQueueFamilyProperties2 = GPA(vkGetPhysicalDeviceQueueFamilyProperties2); - funcs.vkGetPhysicalDeviceMemoryProperties = GPA(vkGetPhysicalDeviceMemoryProperties); - funcs.vkGetPhysicalDeviceMemoryProperties2 = GPA(vkGetPhysicalDeviceMemoryProperties2); - funcs.vkGetPhysicalDeviceSurfaceSupportKHR = GPA(vkGetPhysicalDeviceSurfaceSupportKHR); - funcs.vkGetPhysicalDeviceSurfaceFormatsKHR = GPA(vkGetPhysicalDeviceSurfaceFormatsKHR); - funcs.vkGetPhysicalDeviceSurfacePresentModesKHR = GPA(vkGetPhysicalDeviceSurfacePresentModesKHR); - funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR = GPA(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); - funcs.vkEnumerateDeviceExtensionProperties = GPA(vkEnumerateDeviceExtensionProperties); - funcs.vkEnumerateDeviceLayerProperties = GPA(vkEnumerateDeviceLayerProperties); - funcs.vkGetPhysicalDeviceExternalBufferProperties = GPA(vkGetPhysicalDeviceExternalBufferProperties); - funcs.vkGetPhysicalDeviceExternalFenceProperties = GPA(vkGetPhysicalDeviceExternalFenceProperties); - funcs.vkGetPhysicalDeviceExternalSemaphoreProperties = GPA(vkGetPhysicalDeviceExternalSemaphoreProperties); - - funcs.vkDestroySurfaceKHR = GPA(vkDestroySurfaceKHR); - funcs.vkGetDeviceProcAddr = GPA(vkGetDeviceProcAddr); - funcs.vkCreateDevice = GPA(vkCreateDevice); - - funcs.vkCreateHeadlessSurfaceEXT = GPA(vkCreateHeadlessSurfaceEXT); - funcs.vkCreateDisplayPlaneSurfaceKHR = GPA(vkCreateDisplayPlaneSurfaceKHR); - funcs.vkGetPhysicalDeviceDisplayPropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPropertiesKHR); - funcs.vkGetPhysicalDeviceDisplayPlanePropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPlanePropertiesKHR); - funcs.vkGetDisplayPlaneSupportedDisplaysKHR = GPA(vkGetDisplayPlaneSupportedDisplaysKHR); - funcs.vkGetDisplayModePropertiesKHR = GPA(vkGetDisplayModePropertiesKHR); - funcs.vkCreateDisplayModeKHR = GPA(vkCreateDisplayModeKHR); - funcs.vkGetDisplayPlaneCapabilitiesKHR = GPA(vkGetDisplayPlaneCapabilitiesKHR); - funcs.vkGetPhysicalDevicePresentRectanglesKHR = GPA(vkGetPhysicalDevicePresentRectanglesKHR); - funcs.vkGetPhysicalDeviceDisplayProperties2KHR = GPA(vkGetPhysicalDeviceDisplayProperties2KHR); - funcs.vkGetPhysicalDeviceDisplayPlaneProperties2KHR = GPA(vkGetPhysicalDeviceDisplayPlaneProperties2KHR); - funcs.vkGetDisplayModeProperties2KHR = GPA(vkGetDisplayModeProperties2KHR); - funcs.vkGetDisplayPlaneCapabilities2KHR = GPA(vkGetDisplayPlaneCapabilities2KHR); - funcs.vkGetPhysicalDeviceSurfaceCapabilities2KHR = GPA(vkGetPhysicalDeviceSurfaceCapabilities2KHR); - funcs.vkGetPhysicalDeviceSurfaceFormats2KHR = GPA(vkGetPhysicalDeviceSurfaceFormats2KHR); - -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - funcs.vkCreateAndroidSurfaceKHR = GPA(vkCreateAndroidSurfaceKHR); -#endif // VK_USE_PLATFORM_ANDROID_KHR -#if defined(VK_USE_PLATFORM_DIRECTFB_EXT) - funcs.vkCreateDirectFBSurfaceEXT = GPA(vkCreateDirectFBSurfaceEXT); - funcs.vkGetPhysicalDeviceDirectFBPresentationSupportEXT = GPA(vkGetPhysicalDeviceDirectFBPresentationSupportEXT); -#endif // VK_USE_PLATFORM_DIRECTFB_EXT -#if defined(VK_USE_PLATFORM_FUCHSIA) - funcs.vkCreateImagePipeSurfaceFUCHSIA = GPA(vkCreateImagePipeSurfaceFUCHSIA); -#endif // VK_USE_PLATFORM_FUCHSIA -#if defined(VK_USE_PLATFORM_GGP) - funcs.vkCreateStreamDescriptorSurfaceGGP = GPA(vkCreateStreamDescriptorSurfaceGGP); -#endif // VK_USE_PLATFORM_GGP -#if defined(VK_USE_PLATFORM_IOS_MVK) - funcs.vkCreateIOSSurfaceMVK = GPA(vkCreateIOSSurfaceMVK); -#endif // VK_USE_PLATFORM_IOS_MVK -#if defined(VK_USE_PLATFORM_MACOS_MVK) - funcs.vkCreateMacOSSurfaceMVK = GPA(vkCreateMacOSSurfaceMVK); -#endif // VK_USE_PLATFORM_MACOS_MVK -#if defined(VK_USE_PLATFORM_METAL_EXT) - funcs.vkCreateMetalSurfaceEXT = GPA(vkCreateMetalSurfaceEXT); -#endif // VK_USE_PLATFORM_METAL_EXT -#if defined(VK_USE_PLATFORM_SCREEN_QNX) - funcs.vkCreateScreenSurfaceQNX = GPA(vkCreateScreenSurfaceQNX); - funcs.vkGetPhysicalDeviceScreenPresentationSupportQNX = GPA(vkGetPhysicalDeviceScreenPresentationSupportQNX); -#endif // VK_USE_PLATFORM_SCREEN_QNX -#if defined(VK_USE_PLATFORM_WAYLAND_KHR) - funcs.vkCreateWaylandSurfaceKHR = GPA(vkCreateWaylandSurfaceKHR); - funcs.vkGetPhysicalDeviceWaylandPresentationSupportKHR = GPA(vkGetPhysicalDeviceWaylandPresentationSupportKHR); -#endif // VK_USE_PLATFORM_WAYLAND_KHR -#if defined(VK_USE_PLATFORM_XCB_KHR) - funcs.vkCreateXcbSurfaceKHR = GPA(vkCreateXcbSurfaceKHR); - funcs.vkGetPhysicalDeviceXcbPresentationSupportKHR = GPA(vkGetPhysicalDeviceXcbPresentationSupportKHR); -#endif // VK_USE_PLATFORM_XCB_KHR -#if defined(VK_USE_PLATFORM_XLIB_KHR) - funcs.vkCreateXlibSurfaceKHR = GPA(vkCreateXlibSurfaceKHR); - funcs.vkGetPhysicalDeviceXlibPresentationSupportKHR = GPA(vkGetPhysicalDeviceXlibPresentationSupportKHR); -#endif // VK_USE_PLATFORM_XLIB_KHR -#if defined(VK_USE_PLATFORM_WIN32_KHR) - funcs.vkCreateWin32SurfaceKHR = GPA(vkCreateWin32SurfaceKHR); - funcs.vkGetPhysicalDeviceWin32PresentationSupportKHR = GPA(vkGetPhysicalDeviceWin32PresentationSupportKHR); -#endif // VK_USE_PLATFORM_WIN32_KHR - funcs.vkDestroyDevice = GPA(vkDestroyDevice); - funcs.vkGetDeviceQueue = GPA(vkGetDeviceQueue); -#undef GPA - // clang-format on -} - -#if defined(APPLE_STATIC_LOADER) -VulkanFunctions::VulkanFunctions() { -#else -VulkanFunctions::VulkanFunctions() : loader(get_loader_path()) { -#endif - init_vulkan_functions(*this); -} - -void VulkanFunctions::load_instance_functions(VkInstance instance) { - vkCreateDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); - vkDestroyDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); - vkCreateDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); - vkDestroyDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); -} - -DeviceFunctions::DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device) { - vkGetDeviceProcAddr = vulkan_functions.vkGetDeviceProcAddr; - vkDestroyDevice = load(device, "vkDestroyDevice"); - vkGetDeviceQueue = load(device, "vkGetDeviceQueue"); - vkCreateCommandPool = load(device, "vkCreateCommandPool"); - vkAllocateCommandBuffers = load(device, "vkAllocateCommandBuffers"); - vkDestroyCommandPool = load(device, "vkDestroyCommandPool"); - vkCreateSwapchainKHR = load(device, "vkCreateSwapchainKHR"); - vkGetSwapchainImagesKHR = load(device, "vkGetSwapchainImagesKHR"); - vkDestroySwapchainKHR = load(device, "vkDestroySwapchainKHR"); -} +#include "json_writer.h" InstWrapper::InstWrapper(VulkanFunctions& functions, VkAllocationCallbacks* callbacks) noexcept : functions(&functions), callbacks(callbacks) {} @@ -341,139 +201,23 @@ bool FindPrefixPostfixStringOnLine(DebugUtilsLogger const& env_log, const char* return env_log.find_prefix_then_postfix(prefix, postfix); } -namespace fs { -FolderManager::FolderManager(std::filesystem::path root_path, std::string name) noexcept : folder(root_path / name) { - clear(); - // Don't actually create the folder yet, as we will do it on demand -} -FolderManager::~FolderManager() noexcept { clear(); } -FolderManager::FolderManager(FolderManager&& other) noexcept : actually_created(other.actually_created), folder(other.folder) { - other.folder.clear(); -} -FolderManager& FolderManager::operator=(FolderManager&& other) noexcept { - folder = other.folder; - actually_created = other.actually_created; - other.folder.clear(); - return *this; -} - -void FolderManager::check_if_first_use() { - if (!actually_created) { - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - if (!std::filesystem::create_directories(folder, err)) { - std::cerr << "Failed to create folder " << folder << " because " << err.message() << "\n"; - } - } - actually_created = true; - } -} - -std::filesystem::path FolderManager::write_manifest(std::filesystem::path const& name, std::string const& contents) { - check_if_first_use(); - std::filesystem::path out_path = folder / name; - if (!::testing::internal::InDeathTestChild()) { - auto file = std::ofstream(out_path, std::ios_base::trunc | std::ios_base::out); - if (!file) { - std::cerr << "Failed to create manifest " << name << " at " << out_path << "\n"; - return out_path; - } - file << contents << std::endl; - } - insert_file_to_tracking(name); - return out_path; -} - -// close file handle, delete file, remove `name` from managed file list. -void FolderManager::remove(std::filesystem::path const& name) { - check_if_first_use(); - std::filesystem::path out_path = folder / name; - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - if (!std::filesystem::remove(out_path, err)) { - std::cerr << "Failed to remove file " << name << " at " << out_path << " because " << err.message() << "\n"; - } - } - - auto found = std::find(added_files.begin(), added_files.end(), name); - if (found != added_files.end()) { - added_files.erase(found); - } else { - std::cout << "File " << name << " not in tracked files of folder " << folder << ".\n"; - } -} - -// copy file into this folder -std::filesystem::path FolderManager::copy_file(std::filesystem::path const& file, std::filesystem::path const& new_name) { - check_if_first_use(); - insert_file_to_tracking(new_name); - - auto new_filepath = folder / new_name; - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - if (!std::filesystem::copy_file(file, new_filepath, err)) { - std::cerr << "Failed to copy file " << file << " to " << new_filepath << " because " << err.message() << "\n"; - } - } - return new_filepath; -} - -std::vector FolderManager::get_files() const { return added_files; } - -std::filesystem::path FolderManager::add_symlink(std::filesystem::path const& target, std::filesystem::path const& link_name) { - check_if_first_use(); - - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - std::filesystem::create_symlink(target, folder / link_name, err); - if (err.value() != 0) { - std::cerr << "Failed to create symlink with target" << target << " with name " << folder / link_name << " because " - << err.message() << "\n"; - } - } - insert_file_to_tracking(link_name); - return folder / link_name; -} -void FolderManager::insert_file_to_tracking(std::filesystem::path const& name) { - auto found = std::find(added_files.begin(), added_files.end(), name); - if (found != added_files.end()) { - std::cout << "Overwriting manifest " << name << ". Was this intended?\n"; - } else { - added_files.emplace_back(name); - } -} - -void FolderManager::clear() const noexcept { - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - std::filesystem::remove_all(folder, err); - if (err.value() != 0) { - std::cerr << "Failed to remove folder " << folder << " because " << err.message() << "\n"; - } - } -} - -} // namespace fs - -PlatformShimWrapper::PlatformShimWrapper(GetFoldersFunc get_folders_by_name_function, const char* log_filter) noexcept +PlatformShimWrapper::PlatformShimWrapper(fs::FileSystemManager& file_system_manager, const char* log_filter) noexcept : loader_logging{"VK_LOADER_DEBUG"} { #if defined(WIN32) || defined(__APPLE__) shim_library = LibraryWrapper(SHIM_LIBRARY_NAME); - PFN_get_platform_shim get_platform_shim_func = shim_library.get_symbol(GET_PLATFORM_SHIM_STR); - assert(get_platform_shim_func != NULL && "Must be able to get \"platform_shim\""); - platform_shim = get_platform_shim_func(get_folders_by_name_function); + PFN_get_platform_shim get_platform_shim = shim_library.get_symbol(GET_PLATFORM_SHIM_STR); + assert(get_platform_shim != NULL && "Must be able to get \"platform_shim\""); + platform_shim = get_platform_shim(); #elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) - platform_shim = get_platform_shim(get_folders_by_name_function); + platform_shim = get_platform_shim(); #endif - platform_shim->reset(); + platform_shim->file_system_manager = &file_system_manager; if (log_filter) { loader_logging.set_new_value(log_filter); } } -PlatformShimWrapper::~PlatformShimWrapper() noexcept { platform_shim->reset(); } - TestICDHandle::TestICDHandle() noexcept {} TestICDHandle::TestICDHandle(std::filesystem::path const& icd_path) noexcept : icd_library(icd_path) { proc_addr_get_test_icd = icd_library.get_symbol(GET_TEST_ICD_FUNC_STR); @@ -487,7 +231,7 @@ TestICD& TestICDHandle::reset_icd() noexcept { assert(proc_addr_reset_icd != NULL && "symbol must be loaded before use"); return *proc_addr_reset_icd(); } -std::filesystem::path TestICDHandle::get_icd_full_path() noexcept { return icd_library.lib_path; } +std::filesystem::path TestICDHandle::get_icd_full_path() noexcept { return icd_library.get_path(); } std::filesystem::path TestICDHandle::get_icd_manifest_path() noexcept { return manifest_path; } std::filesystem::path TestICDHandle::get_shimmed_manifest_path() noexcept { return shimmed_manifest_path; } @@ -504,78 +248,101 @@ TestLayer& TestLayerHandle::reset_layer() noexcept { assert(proc_addr_reset_layer != NULL && "symbol must be loaded before use"); return *proc_addr_reset_layer(); } -std::filesystem::path TestLayerHandle::get_layer_full_path() noexcept { return layer_library.lib_path; } +std::filesystem::path TestLayerHandle::get_layer_full_path() noexcept { return layer_library.get_path(); } std::filesystem::path TestLayerHandle::get_layer_manifest_path() noexcept { return manifest_path; } std::filesystem::path TestLayerHandle::get_shimmed_manifest_path() noexcept { return shimmed_manifest_path; } FrameworkEnvironment::FrameworkEnvironment() noexcept : FrameworkEnvironment(FrameworkSettings{}) {} FrameworkEnvironment::FrameworkEnvironment(FrameworkSettings const& settings) noexcept - : settings(settings), - test_folder(std::filesystem::path(FRAMEWORK_BUILD_DIRECTORY), - std::string(::testing::UnitTest::GetInstance()->GetInstance()->current_test_suite()->name()) + "_" + - ::testing::UnitTest::GetInstance()->current_test_info()->name()), - platform_shim( - [this](const char* folder_name) -> std::vector { - for (auto& folder : folders) { - if (folder.location() == folder_name) { - return folder.get_files(); - } - } - return {}; - }, - settings.log_filter) { - // Clean out test folder in case a previous run's files are still around - test_folder.clear(); - - // This order is important, it matches the enum ManifestLocation, used to index the folders vector - folders.emplace_back(test_folder.location(), std::string("null_dir")); - folders.emplace_back(test_folder.location(), std::string("icd_manifests")); - folders.emplace_back(test_folder.location(), std::string("icd_env_vars_manifests")); - folders.emplace_back(test_folder.location(), std::string("explicit_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("explicit_env_var_layer_folder")); - folders.emplace_back(test_folder.location(), std::string("explicit_add_env_var_layer_folder")); - folders.emplace_back(test_folder.location(), std::string("implicit_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("implicit_env_var_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("implicit_add_env_var_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("override_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("app_package_manifests")); - folders.emplace_back(test_folder.location(), std::string("macos_bundle")); - folders.emplace_back(test_folder.location(), std::string("unsecured_location")); - folders.emplace_back(test_folder.location(), std::string("settings_location")); - - platform_shim->redirect_all_paths(get_folder(ManifestLocation::null).location()); - if (settings.enable_default_search_paths) { - platform_shim->set_fake_path(ManifestCategory::icd, get_folder(ManifestLocation::driver).location()); - platform_shim->set_fake_path(ManifestCategory::explicit_layer, get_folder(ManifestLocation::explicit_layer).location()); - platform_shim->set_fake_path(ManifestCategory::implicit_layer, get_folder(ManifestLocation::implicit_layer).location()); -#if COMMON_UNIX_PLATFORMS - auto home = get_env_var("HOME"); - auto unsecured_location = get_folder(ManifestLocation::unsecured_location).location(); - platform_shim->redirect_path(home + "/.local/share/vulkan/icd.d", unsecured_location); - platform_shim->redirect_path(home + "/.local/share/vulkan/implicit_layer.d", unsecured_location); - platform_shim->redirect_path(home + "/.local/share/vulkan/explicit_layer.d", unsecured_location); + : settings(settings), platform_shim(file_system_manager, settings.log_filter) { + // Setup default path redirections + + platform_shim->set_elevated_privilege(settings.run_as_if_with_elevated_privleges); + +#if TESTING_COMMON_UNIX_PLATFORMS + if (!settings.home_env_var.empty()) env_var_home.set_new_value(settings.home_env_var); +#if !defined(__APPLE__) + if (!settings.xdg_config_home_env_var.empty()) env_var_xdg_config_home.set_new_value(settings.xdg_config_home_env_var); + if (!settings.xdg_config_dirs_env_var.empty()) env_var_xdg_config_dirs.set_new_value(settings.xdg_config_dirs_env_var); + if (!settings.xdg_data_home_env_var.empty()) env_var_xdg_data_home.set_new_value(settings.xdg_data_home_env_var); + if (!settings.xdg_data_dirs_env_var.empty()) env_var_xdg_data_dirs.set_new_value(settings.xdg_data_dirs_env_var); #endif +#endif + + const std::array, 4> secured_redirection_map = { + std::pair{"icd.d", ManifestLocation::driver}, + std::pair{"implicit_layer.d", ManifestLocation::implicit_layer}, + std::pair{"explicit_layer.d", ManifestLocation::explicit_layer}, + std::pair{"loader_settings.d", ManifestLocation::settings_location}, + }; + + const std::array, 4> unsecured_redirection_map = { + std::pair{"icd.d", ManifestLocation::unsecured_driver}, + std::pair{"implicit_layer.d", ManifestLocation::unsecured_implicit_layer}, + std::pair{"explicit_layer.d", ManifestLocation::unsecured_explicit_layer}, + std::pair{"loader_settings.d", ManifestLocation::unsecured_settings}, + }; + +#if TESTING_COMMON_UNIX_PLATFORMS && !defined(__APPLE__) + // Always are searching SYSCONFDIR on unix (but not apple, which is handled separately) + secure_manifest_base_location = SYSCONFDIR; + + for (auto const& [redirect, location] : secured_redirection_map) { + file_system_manager.add_path_redirect(secure_manifest_base_location + "/vulkan/" + redirect, location); } -#if COMMON_UNIX_PLATFORMS - if (settings.secure_loader_settings) { - platform_shim->redirect_path("/etc/vulkan/loader_settings.d", get_folder(ManifestLocation::settings_location).location()); - } else { - platform_shim->redirect_path(get_env_var("HOME") + "/.local/share/vulkan/loader_settings.d", - get_folder(ManifestLocation::settings_location).location()); + + // Determines which unsecure path should be used + if (!settings.run_as_if_with_elevated_privleges) { + if (!settings.xdg_config_home_env_var.empty()) { + auto env_var_list = split_env_var_as_list(settings.xdg_config_home_env_var); + unsecure_manifest_base_location = env_var_list.at(0); + if (!env_var_list.empty()) { + for (auto const& [redirect, location] : unsecured_redirection_map) { + file_system_manager.add_path_redirect(env_var_list.at(0) + "/vulkan/" + redirect, location); + } + } + } else if (!settings.xdg_data_home_env_var.empty()) { + auto env_var_list = split_env_var_as_list(settings.xdg_data_home_env_var); + if (!env_var_list.empty()) { + unsecure_manifest_base_location = env_var_list.at(0); + for (auto const& [redirect, location] : unsecured_redirection_map) { + file_system_manager.add_path_redirect(env_var_list.at(0) + "/vulkan/" + redirect, location); + } + } + } else { + std::string home = settings.home_env_var; + for (auto const& [redirect, location] : unsecured_redirection_map) { + unsecure_manifest_base_location = home + "/.config"; + file_system_manager.add_path_redirect(home + "/.config/vulkan/" + redirect, location); + } + } } #endif #if defined(__APPLE__) + // Since XDG env-var shouldn't ever be defined on apple, FALLBACK_DATA_DIRS takes over as the 'global' location to search, eg + // /usr/local/share + auto env_var_list = split_env_var_as_list(FALLBACK_DATA_DIRS); + assert(env_var_list.size() > 0 && "FALLBACK_DATA_DIRS was set to an empty path"); + secure_manifest_base_location = env_var_list.at(0); + for (auto const& [redirect, location] : secured_redirection_map) { + file_system_manager.add_path_redirect(secure_manifest_base_location + "/vulkan/" + redirect, location); + } + + std::string home = settings.home_env_var; + unsecure_manifest_base_location = home + "/.config"; + for (auto const& [redirect, location] : unsecured_redirection_map) { + file_system_manager.add_path_redirect(home + "/.config/vulkan/" + redirect, location); + } + // Necessary since bundles look in sub folders for manifests, not the test framework folder itself auto bundle_location = get_folder(ManifestLocation::macos_bundle).location(); - platform_shim->redirect_path(bundle_location / "vulkan/icd.d", bundle_location); - platform_shim->redirect_path(bundle_location / "vulkan/explicit_layer.d", bundle_location); - platform_shim->redirect_path(bundle_location / "vulkan/implicit_layer.d", bundle_location); + file_system_manager.add_path_redirect(bundle_location / "vulkan/icd.d", ManifestLocation::macos_bundle); + file_system_manager.add_path_redirect(bundle_location / "vulkan/explicit_layer.d", ManifestLocation::macos_bundle); + file_system_manager.add_path_redirect(bundle_location / "vulkan/implicit_layer.d", ManifestLocation::macos_bundle); #endif - // only set the settings file if there are elements in the app_specific_settings vector - if (!settings.loader_settings.app_specific_settings.empty()) { - update_loader_settings(settings.loader_settings); - } + + platform_shim->is_finished_setup = true; } FrameworkEnvironment::~FrameworkEnvironment() { @@ -588,23 +355,27 @@ FrameworkEnvironment::~FrameworkEnvironment() { TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { size_t cur_icd_index = icds.size(); - fs::FolderManager* fs_ptr = &get_folder(ManifestLocation::driver); + fs::Folder* fs_ptr = &get_folder(ManifestLocation::driver); switch (icd_details.discovery_type) { case (ManifestDiscoveryType::env_var): case (ManifestDiscoveryType::add_env_var): fs_ptr = &get_folder(ManifestLocation::driver_env_var); break; +#if defined(WIN32) case (ManifestDiscoveryType::windows_app_package): fs_ptr = &get_folder(ManifestLocation::windows_app_package); break; +#endif case (ManifestDiscoveryType::override_folder): fs_ptr = &get_folder(ManifestLocation::override_layer); break; +#if defined(__APPLE__) case (ManifestDiscoveryType::macos_bundle): fs_ptr = &get_folder(ManifestLocation::macos_bundle); break; +#endif case (ManifestDiscoveryType::unsecured_generic): - fs_ptr = &get_folder(ManifestLocation::unsecured_location); + fs_ptr = &get_folder(ManifestLocation::unsecured_driver); break; case (ManifestDiscoveryType::null_dir): case (ManifestDiscoveryType::none): @@ -623,12 +394,9 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { new_lib_name += icd_details.icd_manifest.lib_path.extension(); auto new_driver_location = folder.copy_file(icd_details.icd_manifest.lib_path, new_lib_name); -#if COMMON_UNIX_PLATFORMS +#if TESTING_COMMON_UNIX_PLATFORMS if (icd_details.library_path_type == LibraryPathType::default_search_paths) { - platform_shim->redirect_dlopen_name(new_lib_name, new_driver_location); - } else if (icd_details.library_path_type == LibraryPathType::relative) { - platform_shim->redirect_dlopen_name(std::filesystem::path(SYSCONFDIR) / "vulkan" / "icd.d" / "." / new_lib_name, - new_driver_location); + platform_shim->add_system_library(new_lib_name, new_driver_location); } #endif #if defined(WIN32) @@ -658,10 +426,20 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { icds.back().shimmed_manifest_path = icds.back().manifest_path; switch (icd_details.discovery_type) { case (ManifestDiscoveryType::generic): - platform_shim->add_manifest(ManifestCategory::icd, icds.back().manifest_path); -#if COMMON_UNIX_PLATFORMS +#if defined(WIN32) + platform_shim->add_manifest_to_registry(ManifestCategory::icd, icds.back().manifest_path); +#elif TESTING_COMMON_UNIX_PLATFORMS icds.back().shimmed_manifest_path = - platform_shim->query_default_redirect_path(ManifestCategory::icd) / new_manifest_path; + file_system_manager.get_path_redirect_by_manifest_location(ManifestLocation::driver) / new_manifest_path; +#endif + break; + case (ManifestDiscoveryType::unsecured_generic): +#if defined(WIN32) + platform_shim->add_unsecured_manifest_to_registry(ManifestCategory::icd, icds.back().manifest_path); +#elif TESTING_COMMON_UNIX_PLATFORMS + icds.back().shimmed_manifest_path = + file_system_manager.get_path_redirect_by_manifest_location(ManifestLocation::unsecured_driver) / + new_manifest_path; #endif break; case (ManifestDiscoveryType::env_var): @@ -670,7 +448,6 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { } else { env_var_vk_icd_filenames.add_to_list(folder.location() / new_manifest_path); } - platform_shim->add_known_path(folder.location()); break; case (ManifestDiscoveryType::add_env_var): if (icd_details.is_dir) { @@ -678,18 +455,17 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { } else { add_env_var_vk_icd_filenames.add_to_list(folder.location() / new_manifest_path); } - platform_shim->add_known_path(folder.location()); - break; - case (ManifestDiscoveryType::macos_bundle): - platform_shim->add_manifest(ManifestCategory::icd, icds.back().manifest_path); break; - case (ManifestDiscoveryType::unsecured_generic): - platform_shim->add_unsecured_manifest(ManifestCategory::icd, icds.back().manifest_path); break; + +#if defined(WIN32) case (ManifestDiscoveryType::windows_app_package): platform_shim->set_app_package_path(folder.location()); break; - +#endif +#if defined(__APPLE__) + case (ManifestDiscoveryType::macos_bundle): // macos_bundle contents are always discoverable by the loader +#endif case (ManifestDiscoveryType::override_folder): // should be found through override layer/settings file, not 'normal' // search paths case (ManifestDiscoveryType::null_dir): @@ -720,7 +496,8 @@ void FrameworkEnvironment::add_explicit_layer(TestLayerDetails layer_details) no } void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, ManifestCategory category) { - fs::FolderManager* fs_ptr = &get_folder(ManifestLocation::explicit_layer); + fs::Folder* fs_ptr = &get_folder(ManifestLocation::explicit_layer); + EnvVarWrapper* env_var_to_use = nullptr; switch (layer_details.discovery_type) { case (ManifestDiscoveryType::generic): if (category == ManifestCategory::implicit_layer) fs_ptr = &get_folder(ManifestLocation::implicit_layer); @@ -728,53 +505,48 @@ void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, Manife case (ManifestDiscoveryType::env_var): if (category == ManifestCategory::explicit_layer) { fs_ptr = &get_folder(ManifestLocation::explicit_layer_env_var); - if (layer_details.is_dir) { - env_var_vk_layer_paths.add_to_list(fs_ptr->location()); - } else { - env_var_vk_layer_paths.add_to_list(fs_ptr->location() / layer_details.json_name); - } - } - if (category == ManifestCategory::implicit_layer) { + env_var_to_use = &env_var_vk_layer_paths; + } else if (category == ManifestCategory::implicit_layer) { fs_ptr = &get_folder(ManifestLocation::implicit_layer_env_var); - if (layer_details.is_dir) { - env_var_vk_implicit_layer_paths.add_to_list(fs_ptr->location()); - } else { - env_var_vk_implicit_layer_paths.add_to_list(fs_ptr->location() / layer_details.json_name); - } + env_var_to_use = &env_var_vk_implicit_layer_paths; + } + if (layer_details.is_dir) { + env_var_to_use->add_to_list(fs_ptr->location()); + } else { + env_var_to_use->add_to_list(fs_ptr->location() / layer_details.json_name); } - platform_shim->add_known_path(fs_ptr->location()); break; case (ManifestDiscoveryType::add_env_var): if (category == ManifestCategory::explicit_layer) { fs_ptr = &get_folder(ManifestLocation::explicit_layer_add_env_var); - if (layer_details.is_dir) { - add_env_var_vk_layer_paths.add_to_list(fs_ptr->location()); - } else { - add_env_var_vk_layer_paths.add_to_list(fs_ptr->location() / layer_details.json_name); - } - } - if (category == ManifestCategory::implicit_layer) { + env_var_to_use = &add_env_var_vk_layer_paths; + } else if (category == ManifestCategory::implicit_layer) { fs_ptr = &get_folder(ManifestLocation::implicit_layer_add_env_var); - if (layer_details.is_dir) { - add_env_var_vk_implicit_layer_paths.add_to_list(fs_ptr->location()); - } else { - add_env_var_vk_implicit_layer_paths.add_to_list(fs_ptr->location() / layer_details.json_name); - } + env_var_to_use = &add_env_var_vk_implicit_layer_paths; + } + if (layer_details.is_dir) { + env_var_to_use->add_to_list(fs_ptr->location()); + } else { + env_var_to_use->add_to_list(fs_ptr->location() / layer_details.json_name); } - platform_shim->add_known_path(fs_ptr->location()); break; case (ManifestDiscoveryType::override_folder): fs_ptr = &get_folder(ManifestLocation::override_layer); break; +#if defined(__APPLE__) case (ManifestDiscoveryType::macos_bundle): fs_ptr = &(get_folder(ManifestLocation::macos_bundle)); break; +#endif case (ManifestDiscoveryType::unsecured_generic): - fs_ptr = &(get_folder(ManifestLocation::unsecured_location)); + fs_ptr = &(get_folder(category == ManifestCategory::implicit_layer ? ManifestLocation::unsecured_implicit_layer + : ManifestLocation::unsecured_explicit_layer)); break; +#if defined(WIN32) case (ManifestDiscoveryType::windows_app_package): fs_ptr = &(get_folder(ManifestLocation::windows_app_package)); break; +#endif case (ManifestDiscoveryType::none): case (ManifestDiscoveryType::null_dir): fs_ptr = &(get_folder(ManifestLocation::null)); @@ -791,14 +563,9 @@ void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, Manife auto new_layer_location = folder.copy_file(layer.lib_path, new_lib_path); -#if COMMON_UNIX_PLATFORMS +#if TESTING_COMMON_UNIX_PLATFORMS if (layer_details.library_path_type == LibraryPathType::default_search_paths) { - platform_shim->redirect_dlopen_name(new_lib_path, new_layer_location); - } - if (layer_details.library_path_type == LibraryPathType::relative) { - platform_shim->redirect_dlopen_name( - std::filesystem::path(SYSCONFDIR) / "vulkan" / category_path_name(category) / "." / new_lib_path, - new_layer_location); + platform_shim->add_system_library(new_lib_path, new_layer_location); } #endif #if defined(WIN32) @@ -828,22 +595,28 @@ void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, Manife if (layer_details.discovery_type != ManifestDiscoveryType::none) { // Write a manifest file to a folder as long as the discovery type isn't none auto layer_manifest_loc = folder.write_manifest(layer_details.json_name, layer_details.layer_manifest.get_manifest_str()); +#if defined(WIN32) // only add the manifest to the registry if its a generic location (as if it was installed) - both system and user local if (layer_details.discovery_type == ManifestDiscoveryType::generic) { - platform_shim->add_manifest(category, layer_manifest_loc); + platform_shim->add_manifest_to_registry(category, layer_manifest_loc); } if (layer_details.discovery_type == ManifestDiscoveryType::unsecured_generic) { - platform_shim->add_unsecured_manifest(category, layer_manifest_loc); + platform_shim->add_unsecured_manifest_to_registry(category, layer_manifest_loc); } if (layer_details.discovery_type == ManifestDiscoveryType::windows_app_package) { platform_shim->set_app_package_path(folder.location()); } +#endif for (size_t i = new_layers_start; i < layers.size(); i++) { layers.at(i).manifest_path = layer_manifest_loc; layers.at(i).shimmed_manifest_path = layer_manifest_loc; -#if COMMON_UNIX_PLATFORMS +#if TESTING_COMMON_UNIX_PLATFORMS if (layer_details.discovery_type == ManifestDiscoveryType::generic) { - layers.at(i).shimmed_manifest_path = platform_shim->query_default_redirect_path(category) / layer_details.json_name; + layers.at(i).shimmed_manifest_path = + ((category == ManifestCategory::implicit_layer) + ? file_system_manager.get_path_redirect_by_manifest_location(ManifestLocation::implicit_layer) + : file_system_manager.get_path_redirect_by_manifest_location(ManifestLocation::explicit_layer)) / + layer_details.json_name; } #endif } @@ -944,36 +717,41 @@ std::string get_loader_settings_file_contents(const LoaderSettings& loader_setti writer.EndObject(); return writer.output; } -void FrameworkEnvironment::write_settings_file(std::string const& file_contents) { - auto out_path = get_folder(ManifestLocation::settings_location).write_manifest("vk_loader_settings.json", file_contents); +void FrameworkEnvironment::write_settings_file(std::string const& file_contents, bool write_to_secure_location) { + auto location = write_to_secure_location ? ManifestLocation::settings_location : ManifestLocation::unsecured_settings; + auto out_path = get_folder(location).write_manifest("vk_loader_settings.json", file_contents); #if defined(WIN32) - platform_shim->hkey_current_user_settings.clear(); - platform_shim->hkey_local_machine_settings.clear(); + if (write_to_secure_location) { + platform_shim->hkey_local_machine_settings.clear(); + platform_shim->add_manifest_to_registry(ManifestCategory::settings, out_path); + } else { + platform_shim->hkey_current_user_settings.clear(); + platform_shim->add_unsecured_manifest_to_registry(ManifestCategory::settings, out_path); + } #endif - if (settings.secure_loader_settings) - platform_shim->add_manifest(ManifestCategory::settings, out_path); - else - platform_shim->add_unsecured_manifest(ManifestCategory::settings, out_path); } -void FrameworkEnvironment::update_loader_settings(const LoaderSettings& settings) noexcept { - write_settings_file(get_loader_settings_file_contents(settings)); +void FrameworkEnvironment::update_loader_settings(const LoaderSettings& settings, bool write_to_secure_location) noexcept { + write_settings_file(get_loader_settings_file_contents(settings), write_to_secure_location); } void FrameworkEnvironment::remove_loader_settings() { get_folder(ManifestLocation::settings_location).remove("vk_loader_settings.json"); } +void FrameworkEnvironment::write_file_from_string(std::string const& source_string, ManifestCategory category, + ManifestLocation location, std::string const& file_name) { + auto out_path = get_folder(location).write_manifest(file_name, source_string); + +#if defined(WIN32) + // Only writes to the hkey_local_machine registries, doesn't support hkey_current_user registries + platform_shim->add_manifest_to_registry(category, out_path); +#endif +} void FrameworkEnvironment::write_file_from_source(const char* source_file, ManifestCategory category, ManifestLocation location, std::string const& file_name) { std::fstream file{source_file, std::ios_base::in}; ASSERT_TRUE(file.is_open()); std::stringstream file_stream; file_stream << file.rdbuf(); - - auto out_path = get_folder(location).write_manifest(file_name, file_stream.str()); - - if (settings.secure_loader_settings) - platform_shim->add_manifest(category, out_path); - else - platform_shim->add_unsecured_manifest(category, out_path); + write_file_from_string(file_stream.str(), category, location, file_name); } TestICD& FrameworkEnvironment::get_test_icd(size_t index) noexcept { return icds[index].get_test_icd(); } @@ -998,19 +776,24 @@ std::filesystem::path FrameworkEnvironment::get_shimmed_layer_manifest_path(size return layers[index].get_shimmed_manifest_path(); } -fs::FolderManager& FrameworkEnvironment::get_folder(ManifestLocation location) noexcept { - // index it directly using the enum location since they will always be in that order - return folders.at(static_cast(location)); +fs::Folder& FrameworkEnvironment::get_folder(ManifestLocation location) noexcept { + return file_system_manager.get_folder(location); } -fs::FolderManager const& FrameworkEnvironment::get_folder(ManifestLocation location) const noexcept { - return folders.at(static_cast(location)); +fs::Folder const& FrameworkEnvironment::get_folder(ManifestLocation location) const noexcept { + return file_system_manager.get_folder(location); } #if defined(__APPLE__) void FrameworkEnvironment::setup_macos_bundle() noexcept { - platform_shim->bundle_contents = get_folder(ManifestLocation::macos_bundle).location(); + platform_shim->bundle_contents = file_system_manager.get_folder(ManifestLocation::macos_bundle).location(); } #endif +void FrameworkEnvironment::add_symlink(ManifestLocation location, std::filesystem::path const& target, + std::filesystem::path const& link_name) { + auto symlinked_path = get_folder(location).add_symlink(target, link_name); + file_system_manager.add_path_redirect(symlinked_path, location); +} + std::vector FrameworkEnvironment::GetInstanceExtensions(uint32_t expected_count, const char* layer_name) { uint32_t count = 0; VkResult res = vulkan_functions.vkEnumerateInstanceExtensionProperties(layer_name, &count, nullptr); diff --git a/tests/framework/test_environment.h b/tests/framework/test_environment.h index 98ba141d1..0a1f89e0d 100644 --- a/tests/framework/test_environment.h +++ b/tests/framework/test_environment.h @@ -46,10 +46,18 @@ #endif #endif -// Use the NDK's header on Android #include "gtest/gtest.h" -#include "test_util.h" +#include "util/builder_defines.h" +#include "util/dispatch_table.h" +#include "util/dynamic_library_wrapper.h" +#include "util/env_var_wrapper.h" +#include "util/equality_helpers.h" +#include "util/folder_manager.h" +#include "util/functions.h" +#include "util/manifest_builders.h" +#include "util/test_defines.h" +#include "util/vulkan_object_wrappers.h" #include "shim/shim.h" @@ -59,15 +67,6 @@ #include "generated/vk_result_to_string_helper.h" -#include FRAMEWORK_CONFIG_HEADER - -// Useful defines -#if COMMON_UNIX_PLATFORMS -#define HOME_DIR "/home/fake_home" -#define USER_LOCAL_SHARE_DIR HOME_DIR "/.local/share" -#define ETC_DIR "/etc" -#endif - // handle checking template void handle_assert_has_value(T const& handle) { @@ -113,152 +112,26 @@ void handle_assert_equal(size_t count, T left[], T right[]) { } } -// VulkanFunctions - loads vulkan functions for tests to use - -struct VulkanFunctions { -#if !defined(APPLE_STATIC_LOADER) - LibraryWrapper loader; -#endif - // Pre-Instance - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; - PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties = nullptr; - PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties = nullptr; - PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion = nullptr; - PFN_vkCreateInstance vkCreateInstance = nullptr; - - // Instance - PFN_vkDestroyInstance vkDestroyInstance = nullptr; - PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = nullptr; - PFN_vkEnumeratePhysicalDeviceGroups vkEnumeratePhysicalDeviceGroups = nullptr; - PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures = nullptr; - PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2 = nullptr; - PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties = nullptr; - PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2 = nullptr; - PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties = nullptr; - PFN_vkGetPhysicalDeviceImageFormatProperties2 vkGetPhysicalDeviceImageFormatProperties2 = nullptr; - PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties = nullptr; - PFN_vkGetPhysicalDeviceSparseImageFormatProperties2 vkGetPhysicalDeviceSparseImageFormatProperties2 = nullptr; - PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = nullptr; - PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2 = nullptr; - PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties = nullptr; - PFN_vkGetPhysicalDeviceQueueFamilyProperties2 vkGetPhysicalDeviceQueueFamilyProperties2 = nullptr; - PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties = nullptr; - PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2 = nullptr; - PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR = nullptr; - PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR = nullptr; - PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR = nullptr; - PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr; - PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties = nullptr; - PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties = nullptr; - PFN_vkGetPhysicalDeviceExternalBufferProperties vkGetPhysicalDeviceExternalBufferProperties = nullptr; - PFN_vkGetPhysicalDeviceExternalFenceProperties vkGetPhysicalDeviceExternalFenceProperties = nullptr; - PFN_vkGetPhysicalDeviceExternalSemaphoreProperties vkGetPhysicalDeviceExternalSemaphoreProperties = nullptr; - - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = nullptr; - PFN_vkCreateDevice vkCreateDevice = nullptr; - - // WSI - PFN_vkCreateHeadlessSurfaceEXT vkCreateHeadlessSurfaceEXT = nullptr; - PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR = nullptr; - PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR = nullptr; - PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR = nullptr; - PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR = nullptr; - PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR = nullptr; - PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR = nullptr; - PFN_vkGetPhysicalDevicePresentRectanglesKHR vkGetPhysicalDevicePresentRectanglesKHR = nullptr; - PFN_vkGetPhysicalDeviceDisplayProperties2KHR vkGetPhysicalDeviceDisplayProperties2KHR = nullptr; - PFN_vkGetPhysicalDeviceDisplayPlaneProperties2KHR vkGetPhysicalDeviceDisplayPlaneProperties2KHR = nullptr; - PFN_vkGetDisplayModeProperties2KHR vkGetDisplayModeProperties2KHR = nullptr; - PFN_vkGetDisplayPlaneCapabilities2KHR vkGetDisplayPlaneCapabilities2KHR = nullptr; - PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR vkGetPhysicalDeviceSurfaceCapabilities2KHR = nullptr; - PFN_vkGetPhysicalDeviceSurfaceFormats2KHR vkGetPhysicalDeviceSurfaceFormats2KHR = nullptr; - -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR = nullptr; -#endif // VK_USE_PLATFORM_ANDROID_KHR -#if defined(VK_USE_PLATFORM_DIRECTFB_EXT) - PFN_vkCreateDirectFBSurfaceEXT vkCreateDirectFBSurfaceEXT = nullptr; - PFN_vkGetPhysicalDeviceDirectFBPresentationSupportEXT vkGetPhysicalDeviceDirectFBPresentationSupportEXT = nullptr; -#endif // VK_USE_PLATFORM_DIRECTFB_EXT -#if defined(VK_USE_PLATFORM_FUCHSIA) - PFN_vkCreateImagePipeSurfaceFUCHSIA vkCreateImagePipeSurfaceFUCHSIA = nullptr; -#endif // VK_USE_PLATFORM_FUCHSIA -#if defined(VK_USE_PLATFORM_GGP) - PFN_vkCreateStreamDescriptorSurfaceGGP vkCreateStreamDescriptorSurfaceGGP = nullptr; -#endif // VK_USE_PLATFORM_GGP -#if defined(VK_USE_PLATFORM_IOS_MVK) - PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK = nullptr; -#endif // VK_USE_PLATFORM_IOS_MVK -#if defined(VK_USE_PLATFORM_MACOS_MVK) - PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK = nullptr; -#endif // VK_USE_PLATFORM_MACOS_MVK -#if defined(VK_USE_PLATFORM_METAL_EXT) - PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT = nullptr; -#endif // VK_USE_PLATFORM_METAL_EXT -#if defined(VK_USE_PLATFORM_SCREEN_QNX) - PFN_vkCreateScreenSurfaceQNX vkCreateScreenSurfaceQNX = nullptr; - PFN_vkGetPhysicalDeviceScreenPresentationSupportQNX vkGetPhysicalDeviceScreenPresentationSupportQNX = nullptr; -#endif // VK_USE_PLATFORM_SCREEN_QNX -#if defined(VK_USE_PLATFORM_WAYLAND_KHR) - PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR = nullptr; -#endif // VK_USE_PLATFORM_WAYLAND_KHR -#if defined(VK_USE_PLATFORM_XCB_KHR) - PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR = nullptr; -#endif // VK_USE_PLATFORM_XCB_KHR -#if defined(VK_USE_PLATFORM_XLIB_KHR) - PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR = nullptr; -#endif // VK_USE_PLATFORM_XLIB_KHR -#if defined(VK_USE_PLATFORM_WIN32_KHR) - PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR = nullptr; -#endif // VK_USE_PLATFORM_WIN32_KHR - PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = nullptr; - - // instance extensions functions (can only be loaded with a valid instance) - PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = nullptr; // Null unless the extension is enabled - PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT = nullptr; // Null unless the extension is enabled - PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = nullptr; // Null unless the extension is enabled - PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT = nullptr; // Null unless the extension is enabled - - // device functions - PFN_vkDestroyDevice vkDestroyDevice = nullptr; - PFN_vkGetDeviceQueue vkGetDeviceQueue = nullptr; - - VulkanFunctions(); - - void load_instance_functions(VkInstance instance); - - FromVoidStarFunc load(VkInstance inst, const char* func_name) const { - return FromVoidStarFunc(vkGetInstanceProcAddr(inst, func_name)); +template +bool check_permutation(std::initializer_list expected, std::array const& returned) { + if (expected.size() != returned.size()) return false; + for (uint32_t i = 0; i < expected.size(); i++) { + auto found = std::find_if(std::begin(returned), std::end(returned), + [&](T elem) { return string_eq(*(expected.begin() + i), elem.layerName); }); + if (found == std::end(returned)) return false; } - - FromVoidStarFunc load(VkDevice device, const char* func_name) const { - return FromVoidStarFunc(vkGetDeviceProcAddr(device, func_name)); - } -}; - -struct DeviceFunctions { - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = nullptr; - PFN_vkDestroyDevice vkDestroyDevice = nullptr; - PFN_vkGetDeviceQueue vkGetDeviceQueue = nullptr; - PFN_vkCreateCommandPool vkCreateCommandPool = nullptr; - PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = nullptr; - PFN_vkDestroyCommandPool vkDestroyCommandPool = nullptr; - PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = nullptr; - PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR = nullptr; - PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR = nullptr; - - DeviceFunctions() = default; - DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device); - - FromVoidStarFunc load(VkDevice device, const char* func_name) const { - return FromVoidStarFunc(vkGetDeviceProcAddr(device, func_name)); + return true; +} +template +bool check_permutation(std::initializer_list expected, std::vector const& returned) { + if (expected.size() != returned.size()) return false; + for (uint32_t i = 0; i < expected.size(); i++) { + auto found = std::find_if(std::begin(returned), std::end(returned), + [&](T elem) { return string_eq(*(expected.begin() + i), elem.layerName); }); + if (found == std::end(returned)) return false; } -}; + return true; +} // InstWrapper & DeviceWrapper - used to make creating instances & devices easier when writing tests struct InstWrapper { @@ -465,50 +338,6 @@ VkResult CreateDebugUtilsMessenger(DebugUtilsWrapper& debug_utils); void FillDebugUtilsCreateDetails(InstanceCreateInfo& create_info, DebugUtilsLogger& logger); void FillDebugUtilsCreateDetails(InstanceCreateInfo& create_info, DebugUtilsWrapper& wrapper); -namespace fs { - -int create_folder(std::filesystem::path const& path); -int delete_folder(std::filesystem::path const& folder); - -class FolderManager { - public: - explicit FolderManager(std::filesystem::path root_path, std::string name) noexcept; - ~FolderManager() noexcept; - FolderManager(FolderManager const&) = delete; - FolderManager& operator=(FolderManager const&) = delete; - FolderManager(FolderManager&& other) noexcept; - FolderManager& operator=(FolderManager&& other) noexcept; - - // Add a manifest to the folder - std::filesystem::path write_manifest(std::filesystem::path const& name, std::string const& contents); - - // close file handle, delete file, remove `name` from managed file list. - void remove(std::filesystem::path const& name); - - // Remove all contents in the path - void clear() const noexcept; - - // copy file into this folder with name `new_name`. Returns the full path of the file that was copied - std::filesystem::path copy_file(std::filesystem::path const& file, std::filesystem::path const& new_name); - - // location of the managed folder - std::filesystem::path location() const { return folder; } - - std::vector get_files() const; - - // Create a symlink in this folder to target with the filename set to link_name - std::filesystem::path add_symlink(std::filesystem::path const& target, std::filesystem::path const& link_name); - - private: - bool actually_created = false; - std::filesystem::path folder; - std::vector added_files; - - void insert_file_to_tracking(std::filesystem::path const& name); - void check_if_first_use(); -}; -} // namespace fs - struct LoaderSettingsLayerConfiguration { BUILDER_VALUE(std::string, name) BUILDER_VALUE(std::filesystem::path, path) @@ -532,6 +361,8 @@ struct LoaderSettingsDriverConfiguration { BUILDER_VALUE(std::filesystem::path, path) }; +using VulkanUUID = std::array; + struct LoaderSettingsDeviceConfiguration { BUILDER_VALUE(VulkanUUID, deviceUUID) BUILDER_VALUE(std::string, deviceName) @@ -560,8 +391,7 @@ struct LoaderSettings { struct FrameworkEnvironment; // forward declaration struct PlatformShimWrapper { - PlatformShimWrapper(GetFoldersFunc get_folders_by_name_function, const char* log_filter) noexcept; - ~PlatformShimWrapper() noexcept; + PlatformShimWrapper(fs::FileSystemManager& file_system_manager, const char* log_filter) noexcept; PlatformShimWrapper(PlatformShimWrapper const&) = delete; PlatformShimWrapper& operator=(PlatformShimWrapper const&) = delete; @@ -610,25 +440,6 @@ struct TestLayerHandle { shimmed_manifest_path; // path to where the loader will find the manifest file (eg /usr/local/share/vulkan/<...>) }; -// Controls whether to create a manifest and where to put it -enum class ManifestDiscoveryType { - generic, // put the manifest in the regular locations - unsecured_generic, // put the manifest in a user folder rather than system - none, // Do not write the manifest anywhere (for Direct Driver Loading) - null_dir, // put the manifest in the 'null_dir' which the loader does not search in (D3DKMT for instance) - env_var, // use the corresponding env-var for it - add_env_var, // use the corresponding add-env-var for it - override_folder, // add to a special folder for the override layer to use - windows_app_package, // let the app package search find it - macos_bundle, // place it in a location only accessible to macos bundles -}; - -enum class LibraryPathType { - absolute, // default for testing - the exact path of the binary - relative, // Relative to the manifest file - default_search_paths, // Dont add any path information to the library_path - force the use of the default search paths -}; - struct TestICDDetails { TestICDDetails(ManifestICD icd_manifest) noexcept : icd_manifest(icd_manifest) {} TestICDDetails(std::filesystem::path icd_binary_path, uint32_t api_version = VK_API_VERSION_1_0) noexcept { @@ -657,30 +468,19 @@ struct TestLayerDetails { BUILDER_VALUE_WITH_DEFAULT(LibraryPathType, library_path_type, LibraryPathType::absolute); }; -// Locations manifests can go in the test framework -// If this enum is added to - the contructor of FrameworkEnvironment also needs to be updated with the new enum value -enum class ManifestLocation { - null = 0, - driver = 1, - driver_env_var = 2, - explicit_layer = 3, - explicit_layer_env_var = 4, - explicit_layer_add_env_var = 5, - implicit_layer = 6, - implicit_layer_env_var = 7, - implicit_layer_add_env_var = 8, - override_layer = 9, - windows_app_package = 10, - macos_bundle = 11, - unsecured_location = 12, - settings_location = 13, -}; - struct FrameworkSettings { BUILDER_VALUE_WITH_DEFAULT(const char*, log_filter, "all"); - BUILDER_VALUE_WITH_DEFAULT(bool, enable_default_search_paths, true); - BUILDER_VALUE(LoaderSettings, loader_settings); - BUILDER_VALUE(bool, secure_loader_settings); + BUILDER_VALUE_WITH_DEFAULT(bool, run_as_if_with_elevated_privleges, false); + +#if TESTING_COMMON_UNIX_PLATFORMS + BUILDER_VALUE_WITH_DEFAULT(std::string, home_env_var, "/home/fake_home"); +#if !defined(__APPLE__) + BUILDER_VALUE(std::string, xdg_config_home_env_var); + BUILDER_VALUE(std::string, xdg_config_dirs_env_var); + BUILDER_VALUE(std::string, xdg_data_home_env_var); + BUILDER_VALUE(std::string, xdg_data_dirs_env_var); +#endif +#endif }; struct FrameworkEnvironment { @@ -699,12 +499,20 @@ struct FrameworkEnvironment { void add_fake_implicit_layer(ManifestLayer layer_manifest, const std::string& json_name) noexcept; void add_fake_explicit_layer(ManifestLayer layer_manifest, const std::string& json_name) noexcept; - // resets the current settings with the values contained in loader_settings - void write_settings_file(std::string const& file_contents); - // apply any changes made to FrameworkEnvironment's loader_settings member - void update_loader_settings(const LoaderSettings& loader_settings) noexcept; + // Resets the current settings with the values contained in loader_settings. + // Write_to_secure_location determines whether to write to the secure or unsecure settings folder. + void write_settings_file(std::string const& file_contents, bool write_to_secure_location); + + // Apply any changes made to FrameworkEnvironment's loader_settings member. + // By default writes to the secure settings location + void update_loader_settings(const LoaderSettings& loader_settings, bool write_to_secure_location = true) noexcept; + void remove_loader_settings(); + // Creates a file called `file_name` for the given `category` in the given `location` with `source_string` as the contents + void write_file_from_string(std::string const& source_string, ManifestCategory category, ManifestLocation location, + std::string const& file_name); + // Creates a file called `file_name` for the given `category` in the given `location` with contents copied from `source_file` void write_file_from_source(const char* source_file, ManifestCategory category, ManifestLocation location, std::string const& file_name); @@ -720,16 +528,18 @@ struct FrameworkEnvironment { std::filesystem::path get_layer_manifest_path(size_t index = 0) noexcept; std::filesystem::path get_shimmed_layer_manifest_path(size_t index = 0) noexcept; - fs::FolderManager& get_folder(ManifestLocation location) noexcept; - fs::FolderManager const& get_folder(ManifestLocation location) const noexcept; + fs::Folder& get_folder(ManifestLocation location) noexcept; + fs::Folder const& get_folder(ManifestLocation location) const noexcept; #if defined(__APPLE__) // Set the path of the app bundle to the appropriate test framework bundle void setup_macos_bundle() noexcept; #endif + void add_symlink(ManifestLocation location, std::filesystem::path const& target, std::filesystem::path const& link_name); + FrameworkSettings settings; - fs::FolderManager test_folder; + fs::FileSystemManager file_system_manager; // Query the global extensions // Optional: use layer_name to query the extensions of a specific layer @@ -738,7 +548,6 @@ struct FrameworkEnvironment { std::vector GetLayerProperties(uint32_t count); PlatformShimWrapper platform_shim; - std::vector folders; std::vector icds; std::vector layers; @@ -753,6 +562,18 @@ struct FrameworkEnvironment { EnvVarWrapper env_var_vk_implicit_layer_paths{"VK_IMPLICIT_LAYER_PATH"}; EnvVarWrapper add_env_var_vk_implicit_layer_paths{"VK_ADD_IMPLICIT_LAYER_PATH"}; +#if TESTING_COMMON_UNIX_PLATFORMS + EnvVarWrapper env_var_home{"HOME", "/home/fake_home"}; +#if !defined(__APPLE__) + EnvVarWrapper env_var_xdg_config_home{"XDG_CONFIG_HOME"}; + EnvVarWrapper env_var_xdg_config_dirs{"XDG_CONFIG_DIRS"}; + EnvVarWrapper env_var_xdg_data_home{"XDG_DATA_HOME"}; + EnvVarWrapper env_var_xdg_data_dirs{"XDG_DATA_DIRS"}; +#endif + std::string secure_manifest_base_location; + std::string unsecure_manifest_base_location; +#endif + LoaderSettings loader_settings; // the current settings written to disk private: void add_layer_impl(TestLayerDetails layer_details, ManifestCategory category); diff --git a/tests/framework/test_util.cpp b/tests/framework/test_util.cpp deleted file mode 100644 index 9e45b4143..000000000 --- a/tests/framework/test_util.cpp +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright (c) 2021-2023 The Khronos Group Inc. - * Copyright (c) 2021-2023 Valve Corporation - * Copyright (c) 2021-2023 LunarG, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and/or associated documentation files (the "Materials"), to - * deal in the Materials without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Materials, and to permit persons to whom the Materials are - * furnished to do so, subject to the following conditions: - * - * The above copyright notice(s) and this permission notice shall be included in - * all copies or substantial portions of the Materials. - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE - * USE OR OTHER DEALINGS IN THE MATERIALS. - * - * Author: Charles Giessen - */ - -#include "test_util.h" - -#include - -#if defined(WIN32) -#include -#include -const char* win_api_error_str(LSTATUS status) { - if (status == ERROR_INVALID_FUNCTION) return "ERROR_INVALID_FUNCTION"; - if (status == ERROR_FILE_NOT_FOUND) return "ERROR_FILE_NOT_FOUND"; - if (status == ERROR_PATH_NOT_FOUND) return "ERROR_PATH_NOT_FOUND"; - if (status == ERROR_TOO_MANY_OPEN_FILES) return "ERROR_TOO_MANY_OPEN_FILES"; - if (status == ERROR_ACCESS_DENIED) return "ERROR_ACCESS_DENIED"; - if (status == ERROR_INVALID_HANDLE) return "ERROR_INVALID_HANDLE"; - if (status == ERROR_ENVVAR_NOT_FOUND) return "ERROR_ENVVAR_NOT_FOUND"; - if (status == ERROR_SETENV_FAILED) return "ERROR_SETENV_FAILED"; - return "UNKNOWN ERROR"; -} - -void print_error_message(LSTATUS status, const char* function_name, std::string optional_message) { - LPVOID lpMsgBuf; - DWORD dw = GetLastError(); - - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&lpMsgBuf), 0, nullptr); - - std::cerr << function_name << " failed with " << win_api_error_str(status) << ": " - << std::string(reinterpret_cast(lpMsgBuf)); - if (optional_message != "") { - std::cerr << " | " << optional_message; - } - std::cerr << "\n"; - LocalFree(lpMsgBuf); -} - -void EnvVarWrapper::set_env_var() { - BOOL ret = SetEnvironmentVariableW(widen(name).c_str(), widen(cur_value).c_str()); - if (ret == 0) { - print_error_message(ERROR_SETENV_FAILED, "SetEnvironmentVariableW"); - } -} -void EnvVarWrapper::remove_env_var() const { SetEnvironmentVariableW(widen(name).c_str(), nullptr); } -std::string get_env_var(std::string const& name, bool report_failure) { - std::wstring name_utf16 = widen(name); - DWORD value_size = GetEnvironmentVariableW(name_utf16.c_str(), nullptr, 0); - if (0 == value_size) { - if (report_failure) print_error_message(ERROR_ENVVAR_NOT_FOUND, "GetEnvironmentVariableW"); - return {}; - } - std::wstring value(value_size, L'\0'); - if (GetEnvironmentVariableW(name_utf16.c_str(), &value[0], value_size) != value_size - 1) { - return {}; - } - return narrow(value); -} -#elif COMMON_UNIX_PLATFORMS - -void EnvVarWrapper::set_env_var() { setenv(name.c_str(), cur_value.c_str(), 1); } -void EnvVarWrapper::remove_env_var() const { unsetenv(name.c_str()); } -std::string get_env_var(std::string const& name, bool report_failure) { - char* ret = getenv(name.c_str()); - if (ret == nullptr) { - if (report_failure) std::cerr << "Failed to get environment variable:" << name << "\n"; - return std::string(); - } - return ret; -} -#endif - -template -void print_object_of_t(JsonWriter& writer, const char* object_name, std::vector const& vec) { - if (vec.size() == 0) return; - writer.StartKeyedObject(object_name); - for (auto& element : vec) { - element.get_manifest_str(writer); - } - writer.EndObject(); -} - -template -void print_array_of_t(JsonWriter& writer, const char* object_name, std::vector const& vec) { - if (vec.size() == 0) return; - writer.StartKeyedArray(object_name); - for (auto& element : vec) { - element.get_manifest_str(writer); - } - writer.EndArray(); -} -void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector const& strings) { - if (strings.size() == 0) return; - writer.StartKeyedArray(object_name); - for (auto const& str : strings) { - writer.AddString(std::filesystem::path(str).native()); - } - writer.EndArray(); -} -void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector const& paths) { - if (paths.size() == 0) return; - writer.StartKeyedArray(object_name); - for (auto const& path : paths) { - writer.AddString(path.native()); - } - writer.EndArray(); -} - -std::string to_text(bool b) { return b ? std::string("true") : std::string("false"); } - -std::string ManifestICD::get_manifest_str() const { - JsonWriter writer; - writer.StartObject(); - writer.AddKeyedString("file_format_version", file_format_version.get_version_str()); - writer.StartKeyedObject("ICD"); - writer.AddKeyedString("library_path", lib_path.native()); - writer.AddKeyedString("api_version", version_to_string(api_version)); - writer.AddKeyedBool("is_portability_driver", is_portability_driver); - if (!library_arch.empty()) writer.AddKeyedString("library_arch", library_arch); - writer.EndObject(); - writer.EndObject(); - return writer.output; -} - -void ManifestLayer::LayerDescription::Extension::get_manifest_str(JsonWriter& writer) const { - writer.StartObject(); - writer.AddKeyedString("name", name); - writer.AddKeyedString("spec_version", std::to_string(spec_version)); - writer.AddKeyedString("spec_version", std::to_string(spec_version)); - print_vector_of_strings(writer, "entrypoints", entrypoints); - writer.EndObject(); -} - -void ManifestLayer::LayerDescription::get_manifest_str(JsonWriter& writer) const { - writer.AddKeyedString("name", name); - writer.AddKeyedString("type", get_type_str(type)); - if (!lib_path.empty()) { - writer.AddKeyedString("library_path", lib_path.native()); - } - writer.AddKeyedString("api_version", version_to_string(api_version)); - writer.AddKeyedString("implementation_version", std::to_string(implementation_version)); - writer.AddKeyedString("description", description); - print_object_of_t(writer, "functions", functions); - print_array_of_t(writer, "instance_extensions", instance_extensions); - print_array_of_t(writer, "device_extensions", device_extensions); - if (!enable_environment.empty()) { - writer.StartKeyedObject("enable_environment"); - writer.AddKeyedString(enable_environment, "1"); - writer.EndObject(); - } - if (!disable_environment.empty()) { - writer.StartKeyedObject("disable_environment"); - writer.AddKeyedString(disable_environment, "1"); - writer.EndObject(); - } - print_vector_of_strings(writer, "component_layers", component_layers); - print_vector_of_strings(writer, "blacklisted_layers", blacklisted_layers); - print_vector_of_strings(writer, "override_paths", override_paths); - print_vector_of_strings(writer, "app_keys", app_keys); - print_object_of_t(writer, "pre_instance_functions", pre_instance_functions); - if (!library_arch.empty()) { - writer.AddKeyedString("library_arch", library_arch); - } -} - -VkLayerProperties ManifestLayer::LayerDescription::get_layer_properties() const { - VkLayerProperties properties{}; - copy_string_to_char_array(name, properties.layerName, VK_MAX_EXTENSION_NAME_SIZE); - copy_string_to_char_array(description, properties.description, VK_MAX_EXTENSION_NAME_SIZE); - properties.implementationVersion = implementation_version; - properties.specVersion = api_version; - return properties; -} - -std::string ManifestLayer::get_manifest_str() const { - JsonWriter writer; - writer.StartObject(); - writer.AddKeyedString("file_format_version", file_format_version.get_version_str()); - if (layers.size() == 1) { - writer.StartKeyedObject("layer"); - layers.at(0).get_manifest_str(writer); - writer.EndObject(); - } else { - writer.StartKeyedArray("layers"); - for (size_t i = 0; i < layers.size(); i++) { - writer.StartObject(); - layers.at(i).get_manifest_str(writer); - writer.EndObject(); - } - writer.EndArray(); - } - writer.EndObject(); - return writer.output; -} - -const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection) { -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - return "VK_KHR_android_surface"; -#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) - return "VK_EXT_directfb_surface"; -#elif defined(VK_USE_PLATFORM_FUCHSIA) - return "VK_FUCHSIA_imagepipe_surface"; -#elif defined(VK_USE_PLATFORM_GGP) - return "VK_GGP_stream_descriptor_surface"; -#elif defined(VK_USE_PLATFORM_IOS_MVK) - return "VK_MVK_ios_surface"; -#elif defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT) -#if defined(VK_USE_PLATFORM_MACOS_MVK) - if (string_eq(api_selection, "VK_USE_PLATFORM_MACOS_MVK")) return "VK_MVK_macos_surface"; -#endif -#if defined(VK_USE_PLATFORM_METAL_EXT) - if (string_eq(api_selection, "VK_USE_PLATFORM_METAL_EXT")) return "VK_EXT_metal_surface"; - return "VK_EXT_metal_surface"; -#endif -#elif defined(VK_USE_PLATFORM_SCREEN_QNX) - return "VK_QNX_screen_surface"; -#elif defined(VK_USE_PLATFORM_VI_NN) - return "VK_NN_vi_surface"; -#elif defined(VK_USE_PLATFORM_XCB_KHR) || defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) -#if defined(VK_USE_PLATFORM_XCB_KHR) - if (string_eq(api_selection, "VK_USE_PLATFORM_XCB_KHR")) return "VK_KHR_xcb_surface"; -#endif -#if defined(VK_USE_PLATFORM_XLIB_KHR) - if (string_eq(api_selection, "VK_USE_PLATFORM_XLIB_KHR")) return "VK_KHR_xlib_surface"; -#endif -#if defined(VK_USE_PLATFORM_WAYLAND_KHR) - if (string_eq(api_selection, "VK_USE_PLATFORM_WAYLAND_KHR")) return "VK_KHR_wayland_surface"; -#endif -#if defined(VK_USE_PLATFORM_XCB_KHR) - return "VK_KHR_xcb_surface"; -#endif -#elif defined(VK_USE_PLATFORM_WIN32_KHR) - return "VK_KHR_win32_surface"; -#else - return "VK_KHR_display"; -#endif -} - -bool string_eq(const char* a, const char* b) noexcept { return a && b && strcmp(a, b) == 0; } -bool string_eq(const char* a, const char* b, size_t len) noexcept { return a && b && strncmp(a, b, len) == 0; } - -InstanceCreateInfo::InstanceCreateInfo() { - instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; -} - -VkInstanceCreateInfo* InstanceCreateInfo::get() noexcept { - if (fill_in_application_info) { - application_info.pApplicationName = app_name.c_str(); - application_info.pEngineName = engine_name.c_str(); - application_info.applicationVersion = app_version; - application_info.engineVersion = engine_version; - application_info.apiVersion = api_version; - instance_info.pApplicationInfo = &application_info; - } - instance_info.flags = flags; - instance_info.enabledLayerCount = static_cast(enabled_layers.size()); - instance_info.ppEnabledLayerNames = enabled_layers.data(); - instance_info.enabledExtensionCount = static_cast(enabled_extensions.size()); - instance_info.ppEnabledExtensionNames = enabled_extensions.data(); - return &instance_info; -} -InstanceCreateInfo& InstanceCreateInfo::set_api_version(uint32_t major, uint32_t minor, uint32_t patch) { - this->api_version = VK_MAKE_API_VERSION(0, major, minor, patch); - return *this; -} -InstanceCreateInfo& InstanceCreateInfo::setup_WSI(const char* api_selection) { - add_extensions({"VK_KHR_surface", get_platform_wsi_extension(api_selection)}); - return *this; -} - -DeviceQueueCreateInfo::DeviceQueueCreateInfo() { queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; } -DeviceQueueCreateInfo::DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info) { - queue_create_info = *create_info; - for (uint32_t i = 0; i < create_info->queueCount; i++) { - priorities.push_back(create_info->pQueuePriorities[i]); - } -} - -VkDeviceQueueCreateInfo DeviceQueueCreateInfo::get() noexcept { - queue_create_info.pQueuePriorities = priorities.data(); - queue_create_info.queueCount = 1; - queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - return queue_create_info; -} - -DeviceCreateInfo::DeviceCreateInfo(const VkDeviceCreateInfo* create_info) { - dev = *create_info; - for (uint32_t i = 0; i < create_info->enabledExtensionCount; i++) { - enabled_extensions.push_back(create_info->ppEnabledExtensionNames[i]); - } - for (uint32_t i = 0; i < create_info->enabledLayerCount; i++) { - enabled_layers.push_back(create_info->ppEnabledLayerNames[i]); - } - for (uint32_t i = 0; i < create_info->queueCreateInfoCount; i++) { - device_queue_infos.push_back(create_info->pQueueCreateInfos[i]); - } -} - -VkDeviceCreateInfo* DeviceCreateInfo::get() noexcept { - dev.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - dev.enabledLayerCount = static_cast(enabled_layers.size()); - dev.ppEnabledLayerNames = enabled_layers.data(); - dev.enabledExtensionCount = static_cast(enabled_extensions.size()); - dev.ppEnabledExtensionNames = enabled_extensions.data(); - uint32_t index = 0; - for (auto& queue : queue_info_details) { - queue.queue_create_info.queueFamilyIndex = index++; - queue.queue_create_info.queueCount = 1; - device_queue_infos.push_back(queue.get()); - } - - dev.queueCreateInfoCount = static_cast(device_queue_infos.size()); - dev.pQueueCreateInfos = device_queue_infos.data(); - return &dev; -} - -#if defined(WIN32) -std::string narrow(const std::wstring& utf16) { - if (utf16.empty()) { - return {}; - } - int size = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), nullptr, 0, nullptr, nullptr); - if (size <= 0) { - return {}; - } - std::string utf8(size, '\0'); - if (WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), &utf8[0], size, nullptr, nullptr) != size) { - return {}; - } - return utf8; -} - -std::wstring widen(const std::string& utf8) { - if (utf8.empty()) { - return {}; - } - int size = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), nullptr, 0); - if (size <= 0) { - return {}; - } - std::wstring utf16(size, L'\0'); - if (MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), &utf16[0], size) != size) { - return {}; - } - return utf16; -} -#else -std::string narrow(const std::string& utf16) { return utf16; } -std::string widen(const std::string& utf8) { return utf8; } -#endif diff --git a/tests/framework/test_util.h b/tests/framework/test_util.h deleted file mode 100644 index 2f4b641f0..000000000 --- a/tests/framework/test_util.h +++ /dev/null @@ -1,861 +0,0 @@ -/* - * Copyright (c) 2021-2023 The Khronos Group Inc. - * Copyright (c) 2021-2023 Valve Corporation - * Copyright (c) 2021-2023 LunarG, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and/or associated documentation files (the "Materials"), to - * deal in the Materials without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Materials, and to permit persons to whom the Materials are - * furnished to do so, subject to the following conditions: - * - * The above copyright notice(s) and this permission notice shall be included in - * all copies or substantial portions of the Materials. - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE - * USE OR OTHER DEALINGS IN THE MATERIALS. - * - * Author: Charles Giessen - */ - -/* - * Contains all the utilities needed to make the framework and tests work. - * Contains: - * All the standard library includes and main platform specific includes - * Dll export macro - * Manifest ICD & Layer structs - * per-platform library loading - mirrors the vk_loader_platform - * LibraryWrapper - RAII wrapper for a library - * DispatchableHandle - RAII wrapper for vulkan dispatchable handle objects - * ostream overload for VkResult - prettifies googletest output - * Instance & Device create info helpers - * operator == overloads for many vulkan structs - more concise tests - */ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -// Set of platforms with a common set of functionality which is queried throughout the program -#if defined(__linux__) || defined(__APPLE__) || defined(__Fuchsia__) || defined(__QNX__) || defined(__FreeBSD__) || \ - defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__GNU__) -#define COMMON_UNIX_PLATFORMS 1 -#else -#define COMMON_UNIX_PLATFORMS 0 -#endif - -#if defined(WIN32) -#include -#include -#include -#elif COMMON_UNIX_PLATFORMS -#include -#include -#include -#include -#include - -// Prevent macro collisions from -#undef major -#undef minor - -#endif - -#include -#include -#include - -#include FRAMEWORK_CONFIG_HEADER - -#if defined(__GNUC__) && __GNUC__ >= 4 -#define FRAMEWORK_EXPORT __attribute__((visibility("default"))) -#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) -#define FRAMEWORK_EXPORT __attribute__((visibility("default"))) -#elif defined(WIN32) -#define FRAMEWORK_EXPORT __declspec(dllexport) -#else -#define FRAMEWORK_EXPORT -#endif - -// Define it here so that json_writer.h has access to these functions -#if defined(WIN32) -// Convert an UTF-16 wstring to an UTF-8 string -std::string narrow(const std::wstring& utf16); -// Convert an UTF-8 string to an UTF-16 wstring -std::wstring widen(const std::string& utf8); -#else -// Do nothing passthrough for the sake of Windows & UTF-16 -std::string narrow(const std::string& utf16); -// Do nothing passthrough for the sake of Windows & UTF-16 -std::string widen(const std::string& utf8); -#endif - -#include "json_writer.h" - -using GetFoldersFunc = std::function(const char*)>; - -// get_env_var() - returns a std::string of `name`. if report_failure is true, then it will log to stderr that it didn't find the -// env-var -// NOTE: This is only intended for test framework code, all test code MUST use EnvVarWrapper -std::string get_env_var(std::string const& name, bool report_failure = true); - -/* - * Wrapper around Environment Variables with common operations - * Since Environment Variables leak between tests, there needs to be RAII code to remove them during test cleanup - - */ - -// Wrapper to set & remove env-vars automatically -struct EnvVarWrapper { - // Constructor which unsets the env-var - EnvVarWrapper(std::string const& name) noexcept : name(name) { - initial_value = get_env_var(name, false); - remove_env_var(); - } - // Constructor which set the env-var to the specified value - EnvVarWrapper(std::string const& name, std::string const& value) noexcept : name(name), cur_value(value) { - initial_value = get_env_var(name, false); - set_env_var(); - } - ~EnvVarWrapper() noexcept { - remove_env_var(); - if (!initial_value.empty()) { - set_new_value(initial_value); - } - } - - // delete copy operators - EnvVarWrapper(const EnvVarWrapper&) = delete; - EnvVarWrapper& operator=(const EnvVarWrapper&) = delete; - - void set_new_value(std::string const& value) { - cur_value = value; - set_env_var(); - } - void add_to_list(std::string const& list_item) { - if (!cur_value.empty()) { - cur_value += OS_ENV_VAR_LIST_SEPARATOR; - } - cur_value += list_item; - set_env_var(); - } -#if defined(WIN32) - void add_to_list(std::wstring const& list_item) { - if (!cur_value.empty()) { - cur_value += OS_ENV_VAR_LIST_SEPARATOR; - } - cur_value += narrow(list_item); - set_env_var(); - } -#endif - void remove_value() const { remove_env_var(); } - const char* get() const { return name.c_str(); } - const char* value() const { return cur_value.c_str(); } - - private: - std::string name; - std::string cur_value; - std::string initial_value; - - void set_env_var(); - void remove_env_var() const; -#if defined(WIN32) - // Environment variable list separator - not for filesystem paths - const char OS_ENV_VAR_LIST_SEPARATOR = ';'; -#elif COMMON_UNIX_PLATFORMS - // Environment variable list separator - not for filesystem paths - const char OS_ENV_VAR_LIST_SEPARATOR = ':'; -#endif -}; - -// Windows specific error handling logic -#if defined(WIN32) -const long ERROR_SETENV_FAILED = 10543; // chosen at random, attempts to not conflict -const long ERROR_REMOVEDIRECTORY_FAILED = 10544; // chosen at random, attempts to not conflict -const char* win_api_error_str(LSTATUS status); -void print_error_message(LSTATUS status, const char* function_name, std::string optional_message = ""); -#endif - -// copy the contents of a std::string into a char array and add a null terminator at the end -// src - std::string to read from -// dst - char array to write to -// size_dst - number of characters in the dst array -inline void copy_string_to_char_array(std::string const& src, char* dst, size_t size_dst) { dst[src.copy(dst, size_dst - 1)] = 0; } - -#if defined(WIN32) -typedef HMODULE test_platform_dl_handle; -inline test_platform_dl_handle test_platform_open_library(const wchar_t* lib_path) { - // Try loading the library the original way first. - test_platform_dl_handle lib_handle = LoadLibraryW(lib_path); - if (lib_handle == nullptr && GetLastError() == ERROR_MOD_NOT_FOUND) { - // If that failed, then try loading it with broader search folders. - lib_handle = LoadLibraryExW(lib_path, nullptr, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); - } - return lib_handle; -} -inline void test_platform_open_library_print_error(std::filesystem::path const& libPath) { - std::wcerr << L"Unable to open library: " << libPath << L" due to: " << std::to_wstring(GetLastError()) << L"\n"; -} -inline void test_platform_close_library(test_platform_dl_handle library) { FreeLibrary(library); } -inline void* test_platform_get_proc_address(test_platform_dl_handle library, const char* name) { - assert(library); - assert(name); - return reinterpret_cast(GetProcAddress(library, name)); -} -inline char* test_platform_get_proc_address_error(const char* name) { - static char errorMsg[120]; - snprintf(errorMsg, 119, "Failed to find function \"%s\" in dynamic library", name); - return errorMsg; -} - -#elif COMMON_UNIX_PLATFORMS - -typedef void* test_platform_dl_handle; -inline test_platform_dl_handle test_platform_open_library(const char* libPath) { return dlopen(libPath, RTLD_LAZY | RTLD_LOCAL); } -inline void test_platform_open_library_print_error(std::filesystem::path const& libPath) { - std::wcerr << "Unable to open library: " << libPath << " due to: " << dlerror() << "\n"; -} -inline void test_platform_close_library(test_platform_dl_handle library) { - char* loader_disable_dynamic_library_unloading_env_var = getenv("VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING"); - if (NULL == loader_disable_dynamic_library_unloading_env_var || - 0 != strncmp(loader_disable_dynamic_library_unloading_env_var, "1", 2)) { - dlclose(library); - } -} -inline void* test_platform_get_proc_address(test_platform_dl_handle library, const char* name) { - assert(library); - assert(name); - return dlsym(library, name); -} -inline const char* test_platform_get_proc_address_error([[maybe_unused]] const char* name) { return dlerror(); } -#endif - -class FromVoidStarFunc { - private: - void* function; - - public: - FromVoidStarFunc(void* function) : function(function) {} - FromVoidStarFunc(PFN_vkVoidFunction function) : function(reinterpret_cast(function)) {} - - template - operator T() { - return reinterpret_cast(function); - } -}; - -struct LibraryWrapper { - explicit LibraryWrapper() noexcept {} - explicit LibraryWrapper(std::filesystem::path const& lib_path) noexcept : lib_path(lib_path) { - lib_handle = test_platform_open_library(lib_path.native().c_str()); - if (lib_handle == nullptr) { - test_platform_open_library_print_error(lib_path); - assert(lib_handle != nullptr && "Must be able to open library"); - } - } - ~LibraryWrapper() noexcept { - if (lib_handle != nullptr) { - test_platform_close_library(lib_handle); - lib_handle = nullptr; - } - } - LibraryWrapper(LibraryWrapper const& wrapper) = delete; - LibraryWrapper& operator=(LibraryWrapper const& wrapper) = delete; - LibraryWrapper(LibraryWrapper&& wrapper) noexcept : lib_handle(wrapper.lib_handle), lib_path(wrapper.lib_path) { - wrapper.lib_handle = nullptr; - } - LibraryWrapper& operator=(LibraryWrapper&& wrapper) noexcept { - if (this != &wrapper) { - if (lib_handle != nullptr) { - test_platform_close_library(lib_handle); - } - lib_handle = wrapper.lib_handle; - lib_path = wrapper.lib_path; - wrapper.lib_handle = nullptr; - } - return *this; - } - FromVoidStarFunc get_symbol(const char* symbol_name) const { - assert(lib_handle != nullptr && "Cannot get symbol with null library handle"); - void* symbol = test_platform_get_proc_address(lib_handle, symbol_name); - if (symbol == nullptr) { - fprintf(stderr, "Unable to open symbol %s: %s\n", symbol_name, test_platform_get_proc_address_error(symbol_name)); - assert(symbol != nullptr && "Must be able to get symbol"); - } - return FromVoidStarFunc(symbol); - } - - explicit operator bool() const noexcept { return lib_handle != nullptr; } - - test_platform_dl_handle lib_handle = nullptr; - std::filesystem::path lib_path; -}; - -template -PFN_vkVoidFunction to_vkVoidFunction(T func) { - return reinterpret_cast(func); -} -template -struct FRAMEWORK_EXPORT DispatchableHandle { - DispatchableHandle() { - auto ptr_handle = new VK_LOADER_DATA; - set_loader_magic_value(ptr_handle); - handle = reinterpret_cast(ptr_handle); - } - ~DispatchableHandle() { - if (handle) { - delete reinterpret_cast(handle); - } - handle = nullptr; - } - DispatchableHandle(DispatchableHandle const&) = delete; - DispatchableHandle& operator=(DispatchableHandle const&) = delete; - DispatchableHandle(DispatchableHandle&& other) noexcept : handle(other.handle) { other.handle = nullptr; } - DispatchableHandle& operator=(DispatchableHandle&& other) noexcept { - if (handle) { - delete reinterpret_cast(handle); - } - handle = other.handle; - other.handle = nullptr; - return *this; - } - bool operator==(T base_handle) { return base_handle == handle; } - bool operator!=(T base_handle) { return base_handle != handle; } - - T handle = nullptr; -}; - -const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection); - -bool string_eq(const char* a, const char* b) noexcept; -bool string_eq(const char* a, const char* b, size_t len) noexcept; - -inline std::string version_to_string(uint32_t version) { - std::string out = std::to_string(VK_API_VERSION_MAJOR(version)) + "." + std::to_string(VK_API_VERSION_MINOR(version)) + "." + - std::to_string(VK_API_VERSION_PATCH(version)); - if (VK_API_VERSION_VARIANT(version) != 0) out += std::to_string(VK_API_VERSION_VARIANT(version)) + "." + out; - return out; -} - -// Macro to ease the definition of variables with builder member functions -// type = type of the variable -// name = name of the variable -// default_value = value to default initialize, use {} if nothing else makes sense -#define BUILDER_VALUE_WITH_DEFAULT(type, name, default_value) \ - type name = default_value; \ - auto set_##name(type const& name)->decltype(*this) { \ - this->name = name; \ - return *this; \ - } - -#define BUILDER_VALUE(type, name) BUILDER_VALUE_WITH_DEFAULT(type, name, {}) - -// Macro to ease the definition of vectors with builder member functions -// type = type of the variable -// name = name of the variable -// singular_name = used for the `add_singular_name` member function -#define BUILDER_VECTOR(type, name, singular_name) \ - std::vector name; \ - auto add_##singular_name(type const& singular_name)->decltype(*this) { \ - this->name.push_back(singular_name); \ - return *this; \ - } \ - auto add_##singular_name##s(std::vector const& singular_name)->decltype(*this) { \ - for (auto& elem : singular_name) this->name.push_back(elem); \ - return *this; \ - } -// Like BUILDER_VECTOR but for move only types - where passing in means giving up ownership -#define BUILDER_VECTOR_MOVE_ONLY(type, name, singular_name) \ - std::vector name; \ - auto add_##singular_name(type&& singular_name)->decltype(*this) { \ - this->name.push_back(std::move(singular_name)); \ - return *this; \ - } - -struct ManifestVersion { - BUILDER_VALUE_WITH_DEFAULT(uint32_t, major, 1) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, minor, 0) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, patch, 0) - - std::string get_version_str() const noexcept { - return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch); - } -}; - -// ManifestICD builder -struct ManifestICD { - BUILDER_VALUE(ManifestVersion, file_format_version) - BUILDER_VALUE(uint32_t, api_version) - BUILDER_VALUE(std::filesystem::path, lib_path) - BUILDER_VALUE(bool, is_portability_driver) - BUILDER_VALUE(std::string, library_arch) - std::string get_manifest_str() const; -}; - -// ManifestLayer builder -struct ManifestLayer { - struct LayerDescription { - enum class Type { INSTANCE, GLOBAL, DEVICE }; - std::string get_type_str(Type layer_type) const { - if (layer_type == Type::GLOBAL) - return "GLOBAL"; - else if (layer_type == Type::DEVICE) - return "DEVICE"; - else // default - return "INSTANCE"; - } - struct FunctionOverride { - BUILDER_VALUE(std::string, vk_func) - BUILDER_VALUE(std::string, override_name) - - void get_manifest_str(JsonWriter& writer) const { writer.AddKeyedString(vk_func, override_name); } - }; - struct Extension { - Extension() noexcept {} - Extension(std::string name, uint32_t spec_version = 0, std::vector entrypoints = {}) noexcept - : name(name), spec_version(spec_version), entrypoints(entrypoints) {} - std::string name; - uint32_t spec_version = 0; - std::vector entrypoints; - void get_manifest_str(JsonWriter& writer) const; - }; - BUILDER_VALUE(std::string, name) - BUILDER_VALUE_WITH_DEFAULT(Type, type, Type::INSTANCE) - BUILDER_VALUE(std::filesystem::path, lib_path) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, api_version, VK_API_VERSION_1_0) - BUILDER_VALUE(uint32_t, implementation_version) - BUILDER_VALUE(std::string, description) - BUILDER_VECTOR(FunctionOverride, functions, function) - BUILDER_VECTOR(Extension, instance_extensions, instance_extension) - BUILDER_VECTOR(Extension, device_extensions, device_extension) - BUILDER_VALUE(std::string, enable_environment) - BUILDER_VALUE(std::string, disable_environment) - BUILDER_VECTOR(std::string, component_layers, component_layer) - BUILDER_VECTOR(std::string, blacklisted_layers, blacklisted_layer) - BUILDER_VECTOR(std::filesystem::path, override_paths, override_path) - BUILDER_VECTOR(FunctionOverride, pre_instance_functions, pre_instance_function) - BUILDER_VECTOR(std::string, app_keys, app_key) - BUILDER_VALUE(std::string, library_arch) - - void get_manifest_str(JsonWriter& writer) const; - VkLayerProperties get_layer_properties() const; - }; - BUILDER_VALUE(ManifestVersion, file_format_version) - BUILDER_VECTOR(LayerDescription, layers, layer) - - std::string get_manifest_str() const; -}; - -struct Extension { - BUILDER_VALUE(std::string, extensionName) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, specVersion, VK_API_VERSION_1_0) - - Extension(const char* name, uint32_t specVersion = VK_API_VERSION_1_0) noexcept - : extensionName(name), specVersion(specVersion) {} - Extension(std::string extensionName, uint32_t specVersion = VK_API_VERSION_1_0) noexcept - : extensionName(extensionName), specVersion(specVersion) {} - - VkExtensionProperties get() const noexcept { - VkExtensionProperties props{}; - copy_string_to_char_array(extensionName, &props.extensionName[0], VK_MAX_EXTENSION_NAME_SIZE); - props.specVersion = specVersion; - return props; - } -}; - -struct MockQueueFamilyProperties { - BUILDER_VALUE(VkQueueFamilyProperties, properties) - BUILDER_VALUE(bool, support_present) - - VkQueueFamilyProperties get() const noexcept { return properties; } -}; - -struct InstanceCreateInfo { - BUILDER_VALUE(VkInstanceCreateInfo, instance_info) - BUILDER_VALUE(VkApplicationInfo, application_info) - BUILDER_VALUE(std::string, app_name) - BUILDER_VALUE(std::string, engine_name) - BUILDER_VALUE(uint32_t, flags) - BUILDER_VALUE(uint32_t, app_version) - BUILDER_VALUE(uint32_t, engine_version) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, api_version, VK_API_VERSION_1_0) - BUILDER_VECTOR(const char*, enabled_layers, layer) - BUILDER_VECTOR(const char*, enabled_extensions, extension) - // tell the get() function to not provide `application_info` - BUILDER_VALUE_WITH_DEFAULT(bool, fill_in_application_info, true) - - InstanceCreateInfo(); - - VkInstanceCreateInfo* get() noexcept; - - InstanceCreateInfo& set_api_version(uint32_t major, uint32_t minor, uint32_t patch); - - InstanceCreateInfo& setup_WSI(const char* api_selection = nullptr); -}; - -struct DeviceQueueCreateInfo { - DeviceQueueCreateInfo(); - DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info); - - BUILDER_VALUE(VkDeviceQueueCreateInfo, queue_create_info) - BUILDER_VECTOR(float, priorities, priority) - - VkDeviceQueueCreateInfo get() noexcept; -}; - -struct DeviceCreateInfo { - DeviceCreateInfo() = default; - DeviceCreateInfo(const VkDeviceCreateInfo* create_info); - - BUILDER_VALUE(VkDeviceCreateInfo, dev) - BUILDER_VECTOR(const char*, enabled_extensions, extension) - BUILDER_VECTOR(const char*, enabled_layers, layer) - BUILDER_VECTOR(DeviceQueueCreateInfo, queue_info_details, device_queue) - - VkDeviceCreateInfo* get() noexcept; - - private: - std::vector device_queue_infos; -}; - -inline bool operator==(const VkExtent3D& a, const VkExtent3D& b) { - return a.width == b.width && a.height == b.height && a.depth == b.depth; -} -inline bool operator!=(const VkExtent3D& a, const VkExtent3D& b) { return !(a == b); } - -inline bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b) { - return a.minImageTransferGranularity == b.minImageTransferGranularity && a.queueCount == b.queueCount && - a.queueFlags == b.queueFlags && a.timestampValidBits == b.timestampValidBits; -} -inline bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b) { return !(a == b); } - -inline bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b) { return a == b.queueFamilyProperties; } -inline bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b) { return a != b.queueFamilyProperties; } - -inline bool operator==(const VkLayerProperties& a, const VkLayerProperties& b) { - return string_eq(a.layerName, b.layerName, 256) && string_eq(a.description, b.description, 256) && - a.implementationVersion == b.implementationVersion && a.specVersion == b.specVersion; -} -inline bool operator!=(const VkLayerProperties& a, const VkLayerProperties& b) { return !(a == b); } - -inline bool operator==(const VkExtensionProperties& a, const VkExtensionProperties& b) { - return string_eq(a.extensionName, b.extensionName, 256) && a.specVersion == b.specVersion; -} -inline bool operator!=(const VkExtensionProperties& a, const VkExtensionProperties& b) { return !(a == b); } - -inline bool operator==(const VkPhysicalDeviceFeatures& feats1, const VkPhysicalDeviceFeatures2& feats2) { - return feats1.robustBufferAccess == feats2.features.robustBufferAccess && - feats1.fullDrawIndexUint32 == feats2.features.fullDrawIndexUint32 && - feats1.imageCubeArray == feats2.features.imageCubeArray && feats1.independentBlend == feats2.features.independentBlend && - feats1.geometryShader == feats2.features.geometryShader && - feats1.tessellationShader == feats2.features.tessellationShader && - feats1.sampleRateShading == feats2.features.sampleRateShading && feats1.dualSrcBlend == feats2.features.dualSrcBlend && - feats1.logicOp == feats2.features.logicOp && feats1.multiDrawIndirect == feats2.features.multiDrawIndirect && - feats1.drawIndirectFirstInstance == feats2.features.drawIndirectFirstInstance && - feats1.depthClamp == feats2.features.depthClamp && feats1.depthBiasClamp == feats2.features.depthBiasClamp && - feats1.fillModeNonSolid == feats2.features.fillModeNonSolid && feats1.depthBounds == feats2.features.depthBounds && - feats1.wideLines == feats2.features.wideLines && feats1.largePoints == feats2.features.largePoints && - feats1.alphaToOne == feats2.features.alphaToOne && feats1.multiViewport == feats2.features.multiViewport && - feats1.samplerAnisotropy == feats2.features.samplerAnisotropy && - feats1.textureCompressionETC2 == feats2.features.textureCompressionETC2 && - feats1.textureCompressionASTC_LDR == feats2.features.textureCompressionASTC_LDR && - feats1.textureCompressionBC == feats2.features.textureCompressionBC && - feats1.occlusionQueryPrecise == feats2.features.occlusionQueryPrecise && - feats1.pipelineStatisticsQuery == feats2.features.pipelineStatisticsQuery && - feats1.vertexPipelineStoresAndAtomics == feats2.features.vertexPipelineStoresAndAtomics && - feats1.fragmentStoresAndAtomics == feats2.features.fragmentStoresAndAtomics && - feats1.shaderTessellationAndGeometryPointSize == feats2.features.shaderTessellationAndGeometryPointSize && - feats1.shaderImageGatherExtended == feats2.features.shaderImageGatherExtended && - feats1.shaderStorageImageExtendedFormats == feats2.features.shaderStorageImageExtendedFormats && - feats1.shaderStorageImageMultisample == feats2.features.shaderStorageImageMultisample && - feats1.shaderStorageImageReadWithoutFormat == feats2.features.shaderStorageImageReadWithoutFormat && - feats1.shaderStorageImageWriteWithoutFormat == feats2.features.shaderStorageImageWriteWithoutFormat && - feats1.shaderUniformBufferArrayDynamicIndexing == feats2.features.shaderUniformBufferArrayDynamicIndexing && - feats1.shaderSampledImageArrayDynamicIndexing == feats2.features.shaderSampledImageArrayDynamicIndexing && - feats1.shaderStorageBufferArrayDynamicIndexing == feats2.features.shaderStorageBufferArrayDynamicIndexing && - feats1.shaderStorageImageArrayDynamicIndexing == feats2.features.shaderStorageImageArrayDynamicIndexing && - feats1.shaderClipDistance == feats2.features.shaderClipDistance && - feats1.shaderCullDistance == feats2.features.shaderCullDistance && - feats1.shaderFloat64 == feats2.features.shaderFloat64 && feats1.shaderInt64 == feats2.features.shaderInt64 && - feats1.shaderInt16 == feats2.features.shaderInt16 && - feats1.shaderResourceResidency == feats2.features.shaderResourceResidency && - feats1.shaderResourceMinLod == feats2.features.shaderResourceMinLod && - feats1.sparseBinding == feats2.features.sparseBinding && - feats1.sparseResidencyBuffer == feats2.features.sparseResidencyBuffer && - feats1.sparseResidencyImage2D == feats2.features.sparseResidencyImage2D && - feats1.sparseResidencyImage3D == feats2.features.sparseResidencyImage3D && - feats1.sparseResidency2Samples == feats2.features.sparseResidency2Samples && - feats1.sparseResidency4Samples == feats2.features.sparseResidency4Samples && - feats1.sparseResidency8Samples == feats2.features.sparseResidency8Samples && - feats1.sparseResidency16Samples == feats2.features.sparseResidency16Samples && - feats1.sparseResidencyAliased == feats2.features.sparseResidencyAliased && - feats1.variableMultisampleRate == feats2.features.variableMultisampleRate && - feats1.inheritedQueries == feats2.features.inheritedQueries; -} - -inline bool operator==(const VkPhysicalDeviceMemoryProperties& props1, const VkPhysicalDeviceMemoryProperties2& props2) { - bool equal = true; - equal = equal && props1.memoryTypeCount == props2.memoryProperties.memoryTypeCount; - equal = equal && props1.memoryHeapCount == props2.memoryProperties.memoryHeapCount; - for (uint32_t i = 0; i < props1.memoryHeapCount; ++i) { - equal = equal && props1.memoryHeaps[i].size == props2.memoryProperties.memoryHeaps[i].size; - equal = equal && props1.memoryHeaps[i].flags == props2.memoryProperties.memoryHeaps[i].flags; - } - for (uint32_t i = 0; i < props1.memoryTypeCount; ++i) { - equal = equal && props1.memoryTypes[i].propertyFlags == props2.memoryProperties.memoryTypes[i].propertyFlags; - equal = equal && props1.memoryTypes[i].heapIndex == props2.memoryProperties.memoryTypes[i].heapIndex; - } - return equal; -} -inline bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties& props2) { - return props1.aspectMask == props2.aspectMask && props1.imageGranularity.width == props2.imageGranularity.width && - props1.imageGranularity.height == props2.imageGranularity.height && - props1.imageGranularity.depth == props2.imageGranularity.depth && props1.flags == props2.flags; -} -inline bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties2& props2) { - return props1 == props2.properties; -} -inline bool operator==(const VkExternalMemoryProperties& props1, const VkExternalMemoryProperties& props2) { - return props1.externalMemoryFeatures == props2.externalMemoryFeatures && - props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && - props1.compatibleHandleTypes == props2.compatibleHandleTypes; -} -inline bool operator==(const VkExternalSemaphoreProperties& props1, const VkExternalSemaphoreProperties& props2) { - return props1.externalSemaphoreFeatures == props2.externalSemaphoreFeatures && - props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && - props1.compatibleHandleTypes == props2.compatibleHandleTypes; -} -inline bool operator==(const VkExternalFenceProperties& props1, const VkExternalFenceProperties& props2) { - return props1.externalFenceFeatures == props2.externalFenceFeatures && - props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && - props1.compatibleHandleTypes == props2.compatibleHandleTypes; -} -inline bool operator==(const VkSurfaceCapabilitiesKHR& props1, const VkSurfaceCapabilitiesKHR& props2) { - return props1.minImageCount == props2.minImageCount && props1.maxImageCount == props2.maxImageCount && - props1.currentExtent.width == props2.currentExtent.width && props1.currentExtent.height == props2.currentExtent.height && - props1.minImageExtent.width == props2.minImageExtent.width && - props1.minImageExtent.height == props2.minImageExtent.height && - props1.maxImageExtent.width == props2.maxImageExtent.width && - props1.maxImageExtent.height == props2.maxImageExtent.height && - props1.maxImageArrayLayers == props2.maxImageArrayLayers && props1.supportedTransforms == props2.supportedTransforms && - props1.currentTransform == props2.currentTransform && props1.supportedCompositeAlpha == props2.supportedCompositeAlpha && - props1.supportedUsageFlags == props2.supportedUsageFlags; -} -inline bool operator==(const VkSurfacePresentScalingCapabilitiesEXT& caps1, const VkSurfacePresentScalingCapabilitiesEXT& caps2) { - return caps1.supportedPresentScaling == caps2.supportedPresentScaling && - caps1.supportedPresentGravityX == caps2.supportedPresentGravityX && - caps1.supportedPresentGravityY == caps2.supportedPresentGravityY && - caps1.minScaledImageExtent.width == caps2.minScaledImageExtent.width && - caps1.minScaledImageExtent.height == caps2.minScaledImageExtent.height && - caps1.maxScaledImageExtent.width == caps2.maxScaledImageExtent.width && - caps1.maxScaledImageExtent.height == caps2.maxScaledImageExtent.height; -} -inline bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormatKHR& format2) { - return format1.format == format2.format && format1.colorSpace == format2.colorSpace; -} -inline bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormat2KHR& format2) { - return format1 == format2.surfaceFormat; -} -inline bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayPropertiesKHR& props2) { - return props1.display == props2.display && props1.physicalDimensions.width == props2.physicalDimensions.width && - props1.physicalDimensions.height == props2.physicalDimensions.height && - props1.physicalResolution.width == props2.physicalResolution.width && - props1.physicalResolution.height == props2.physicalResolution.height && - props1.supportedTransforms == props2.supportedTransforms && props1.planeReorderPossible == props2.planeReorderPossible && - props1.persistentContent == props2.persistentContent; -} -inline bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayProperties2KHR& props2) { - return props1 == props2.displayProperties; -} -inline bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModePropertiesKHR& disp2) { - return disp1.displayMode == disp2.displayMode && disp1.parameters.visibleRegion.width == disp2.parameters.visibleRegion.width && - disp1.parameters.visibleRegion.height == disp2.parameters.visibleRegion.height && - disp1.parameters.refreshRate == disp2.parameters.refreshRate; -} - -inline bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModeProperties2KHR& disp2) { - return disp1 == disp2.displayModeProperties; -} -inline bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilitiesKHR& caps2) { - return caps1.supportedAlpha == caps2.supportedAlpha && caps1.minSrcPosition.x == caps2.minSrcPosition.x && - caps1.minSrcPosition.y == caps2.minSrcPosition.y && caps1.maxSrcPosition.x == caps2.maxSrcPosition.x && - caps1.maxSrcPosition.y == caps2.maxSrcPosition.y && caps1.minSrcExtent.width == caps2.minSrcExtent.width && - caps1.minSrcExtent.height == caps2.minSrcExtent.height && caps1.maxSrcExtent.width == caps2.maxSrcExtent.width && - caps1.maxSrcExtent.height == caps2.maxSrcExtent.height && caps1.minDstPosition.x == caps2.minDstPosition.x && - caps1.minDstPosition.y == caps2.minDstPosition.y && caps1.maxDstPosition.x == caps2.maxDstPosition.x && - caps1.maxDstPosition.y == caps2.maxDstPosition.y && caps1.minDstExtent.width == caps2.minDstExtent.width && - caps1.minDstExtent.height == caps2.minDstExtent.height && caps1.maxDstExtent.width == caps2.maxDstExtent.width && - caps1.maxDstExtent.height == caps2.maxDstExtent.height; -} - -inline bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilities2KHR& caps2) { - return caps1 == caps2.capabilities; -} -inline bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlanePropertiesKHR& props2) { - return props1.currentDisplay == props2.currentDisplay && props1.currentStackIndex == props2.currentStackIndex; -} -inline bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlaneProperties2KHR& props2) { - return props1 == props2.displayPlaneProperties; -} -inline bool operator==(const VkExtent2D& ext1, const VkExtent2D& ext2) { - return ext1.height == ext2.height && ext1.width == ext2.width; -} -// Allow comparison of vectors of different types as long as their elements are comparable (just has to make sure to only apply when -// T != U) -template >> -bool operator==(const std::vector& a, const std::vector& b) { - return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](const auto& left, const auto& right) { return left == right; }); -} - -struct VulkanFunction { - std::string name; - PFN_vkVoidFunction function = nullptr; -}; - -using VulkanUUID = std::array; - -template -bool check_permutation(std::initializer_list expected, std::array const& returned) { - if (expected.size() != returned.size()) return false; - for (uint32_t i = 0; i < expected.size(); i++) { - auto found = std::find_if(std::begin(returned), std::end(returned), - [&](T elem) { return string_eq(*(expected.begin() + i), elem.layerName); }); - if (found == std::end(returned)) return false; - } - return true; -} -template -bool check_permutation(std::initializer_list expected, std::vector const& returned) { - if (expected.size() != returned.size()) return false; - for (uint32_t i = 0; i < expected.size(); i++) { - auto found = std::find_if(std::begin(returned), std::end(returned), - [&](T elem) { return string_eq(*(expected.begin() + i), elem.layerName); }); - if (found == std::end(returned)) return false; - } - return true; -} - -inline bool contains(std::vector const& vec, const char* name) { - return std::any_of(std::begin(vec), std::end(vec), - [name](VkExtensionProperties const& elem) { return string_eq(name, elem.extensionName); }); -} -inline bool contains(std::vector const& vec, const char* name) { - return std::any_of(std::begin(vec), std::end(vec), - [name](VkLayerProperties const& elem) { return string_eq(name, elem.layerName); }); -} - -#if defined(__linux__) || defined(__GNU__) - -// find application path + name. Path cannot be longer than 1024, returns NULL if it is greater than that. -inline std::string test_platform_executable_path() { - std::string buffer; - buffer.resize(1024); - ssize_t count = readlink("/proc/self/exe", &buffer[0], buffer.size()); - if (count == -1) return NULL; - if (count == 0) return NULL; - buffer[count] = '\0'; - buffer.resize(count); - return buffer; -} -#elif defined(__APPLE__) -#include -inline std::string test_platform_executable_path() { - std::string buffer; - buffer.resize(1024); - pid_t pid = getpid(); - int ret = proc_pidpath(pid, &buffer[0], static_cast(buffer.size())); - if (ret <= 0) return NULL; - buffer[ret] = '\0'; - buffer.resize(ret); - return buffer; -} -#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) -#include -inline std::string test_platform_executable_path() { - int mib[] = { - CTL_KERN, -#if defined(__NetBSD__) - KERN_PROC_ARGS, - -1, - KERN_PROC_PATHNAME, -#else - KERN_PROC, - KERN_PROC_PATHNAME, - -1, -#endif - }; - std::string buffer; - buffer.resize(1024); - size_t size = buffer.size(); - if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), &buffer[0], &size, NULL, 0) < 0) { - return NULL; - } - buffer.resize(size); - - return buffer; -} -#elif defined(__Fuchsia__) || defined(__OpenBSD__) -inline std::string test_platform_executable_path() { return {}; } -#elif defined(__QNX__) - -#ifndef SYSCONFDIR -#define SYSCONFDIR "/etc" -#endif - -#include -#include - -inline std::string test_platform_executable_path() { - std::string buffer; - buffer.resize(1024); - int fd = open("/proc/self/exefile", O_RDONLY); - ssize_t rdsize; - - if (fd == -1) { - return NULL; - } - - rdsize = read(fd, &buffer[0], buffer.size()); - if (rdsize < 0) { - return NULL; - } - buffer[rdsize] = 0x00; - close(fd); - buffer.resize(rdsize); - - return buffer; -} -#endif // defined (__QNX__) -#if defined(WIN32) -inline std::string test_platform_executable_path() { - std::string buffer; - buffer.resize(1024); - DWORD ret = GetModuleFileName(NULL, static_cast(&buffer[0]), (DWORD)buffer.size()); - if (ret == 0) return NULL; - if (ret > buffer.size()) return NULL; - buffer.resize(ret); - buffer[ret] = '\0'; - return narrow(std::filesystem::path(buffer).native()); -} - -#endif diff --git a/tests/framework/util/CMakeLists.txt b/tests/framework/util/CMakeLists.txt new file mode 100644 index 000000000..3860f9932 --- /dev/null +++ b/tests/framework/util/CMakeLists.txt @@ -0,0 +1,85 @@ +# ~~~ +# Copyright (c) 2021 Valve Corporation +# Copyright (c) 2021 LunarG, 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(testing_framework_util STATIC + builder_defines.h + dispatch_table.cpp + dispatch_table.h + dispatchable_handle.h + dynamic_library_wrapper.cpp + dynamic_library_wrapper.h + env_var_wrapper.cpp + env_var_wrapper.h + equality_helpers.cpp + equality_helpers.h + folder_manager.cpp + folder_manager.h + functions.h + get_executable_path.cpp + get_executable_path.h + json_writer.cpp + json_writer.h + manifest_builders.cpp + manifest_builders.h + platform_wsi.cpp + platform_wsi.h + test_defines.h + vulkan_object_wrappers.cpp + vulkan_object_wrappers.h + wide_char_handling.cpp + wide_char_handling.h + ) +target_link_libraries(testing_framework_util PUBLIC loader_common_options Vulkan::Headers gtest) + +if(UNIX OR APPLE) + target_link_libraries(testing_framework_util PUBLIC ${CMAKE_DL_LIBS}) +endif() + +if(UNIX) + target_compile_options(testing_framework_util PUBLIC -fPIC) +endif() +# Gives access to all headers in this folder, the framework folder, the framework binary folder, and the loader/generated folder +target_include_directories(testing_framework_util + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/.." "${CMAKE_CURRENT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}") + +if (UNIX) + if (LOADER_ENABLE_ADDRESS_SANITIZER) + target_compile_options(testing_framework_util PUBLIC -fsanitize=address,undefined) + target_link_options(testing_framework_util PUBLIC -fsanitize=address,undefined) + endif() + if (LOADER_ENABLE_THREAD_SANITIZER) + target_compile_options(testing_framework_util PUBLIC -fsanitize=thread) + target_link_options(testing_framework_util PUBLIC -fsanitize=thread) + target_compile_options(gtest PUBLIC -fsanitize=thread) + target_link_options(gtest PUBLIC -fsanitize=thread) + endif() +endif() + +if (MSVC) + # silence hidden class member warnings in test framework + target_compile_options(testing_framework_util PUBLIC /wd4458) + # Make sure exception handling is enabled for the test framework + target_compile_options(testing_framework_util PUBLIC /EHsc) +endif() + +# Add a compiler definition for the path to framework_config.h with the correct config +target_compile_definitions(testing_framework_util PUBLIC FRAMEWORK_CONFIG_HEADER="${FRAMEWORK_CONFIG_HEADER_PATH}") + +if (APPLE_STATIC_LOADER) + target_compile_definitions(testing_framework_util PUBLIC "APPLE_STATIC_LOADER=1") + target_link_libraries(testing_framework_util PRIVATE vulkan) +endif() diff --git a/tests/framework/util/builder_defines.h b/tests/framework/util/builder_defines.h new file mode 100644 index 000000000..c727c81b9 --- /dev/null +++ b/tests/framework/util/builder_defines.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +// Macro to ease the definition of variables with builder member functions +// type = type of the variable +// name = name of the variable +// default_value = value to default initialize, use {} if nothing else makes sense +#define BUILDER_VALUE_WITH_DEFAULT(type, name, default_value) \ + type name = default_value; \ + auto set_##name(type const& name)->decltype(*this) { \ + this->name = name; \ + return *this; \ + } + +#define BUILDER_VALUE(type, name) BUILDER_VALUE_WITH_DEFAULT(type, name, {}) + +// Macro to ease the definition of vectors with builder member functions +// type = type of the variable +// name = name of the variable +// singular_name = used for the `add_singular_name` member function +#define BUILDER_VECTOR(type, name, singular_name) \ + std::vector name; \ + auto add_##singular_name(type const& singular_name)->decltype(*this) { \ + this->name.push_back(singular_name); \ + return *this; \ + } \ + auto add_##singular_name##s(std::vector const& singular_name)->decltype(*this) { \ + for (auto& elem : singular_name) this->name.push_back(elem); \ + return *this; \ + } +// Like BUILDER_VECTOR but for move only types - where passing in means giving up ownership +#define BUILDER_VECTOR_MOVE_ONLY(type, name, singular_name) \ + std::vector name; \ + auto add_##singular_name(type&& singular_name)->decltype(*this) { \ + this->name.push_back(std::move(singular_name)); \ + return *this; \ + } diff --git a/tests/framework/util/dispatch_table.cpp b/tests/framework/util/dispatch_table.cpp new file mode 100644 index 000000000..60901afc1 --- /dev/null +++ b/tests/framework/util/dispatch_table.cpp @@ -0,0 +1,174 @@ +// VulkanFunctions - loads vulkan functions for tests to use +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "dispatch_table.h" + +#include "env_var_wrapper.h" + +std::filesystem::path get_loader_path() { + auto loader_path = std::filesystem::path(FRAMEWORK_VULKAN_LIBRARY_PATH); + auto env_var_res = get_env_var("VK_LOADER_TEST_LOADER_PATH", false); + if (!env_var_res.empty()) { + loader_path = std::filesystem::path(env_var_res); + } + return loader_path; +} + +void init_vulkan_functions(VulkanFunctions& funcs) { +#if defined(APPLE_STATIC_LOADER) +#define GPA(name) name +#else +#define GPA(name) funcs.loader.get_symbol(#name) +#endif + + // clang-format off + funcs.vkGetInstanceProcAddr = GPA(vkGetInstanceProcAddr); + funcs.vkEnumerateInstanceExtensionProperties = GPA(vkEnumerateInstanceExtensionProperties); + funcs.vkEnumerateInstanceLayerProperties = GPA(vkEnumerateInstanceLayerProperties); + funcs.vkEnumerateInstanceVersion = GPA(vkEnumerateInstanceVersion); + funcs.vkCreateInstance = GPA(vkCreateInstance); + funcs.vkDestroyInstance = GPA(vkDestroyInstance); + funcs.vkEnumeratePhysicalDevices = GPA(vkEnumeratePhysicalDevices); + funcs.vkEnumeratePhysicalDeviceGroups = GPA(vkEnumeratePhysicalDeviceGroups); + funcs.vkGetPhysicalDeviceFeatures = GPA(vkGetPhysicalDeviceFeatures); + funcs.vkGetPhysicalDeviceFeatures2 = GPA(vkGetPhysicalDeviceFeatures2); + funcs.vkGetPhysicalDeviceFormatProperties = GPA(vkGetPhysicalDeviceFormatProperties); + funcs.vkGetPhysicalDeviceFormatProperties2 = GPA(vkGetPhysicalDeviceFormatProperties2); + funcs.vkGetPhysicalDeviceImageFormatProperties = GPA(vkGetPhysicalDeviceImageFormatProperties); + funcs.vkGetPhysicalDeviceImageFormatProperties2 = GPA(vkGetPhysicalDeviceImageFormatProperties2); + funcs.vkGetPhysicalDeviceSparseImageFormatProperties = GPA(vkGetPhysicalDeviceSparseImageFormatProperties); + funcs.vkGetPhysicalDeviceSparseImageFormatProperties2 = GPA(vkGetPhysicalDeviceSparseImageFormatProperties2); + funcs.vkGetPhysicalDeviceProperties = GPA(vkGetPhysicalDeviceProperties); + funcs.vkGetPhysicalDeviceProperties2 = GPA(vkGetPhysicalDeviceProperties2); + funcs.vkGetPhysicalDeviceQueueFamilyProperties = GPA(vkGetPhysicalDeviceQueueFamilyProperties); + funcs.vkGetPhysicalDeviceQueueFamilyProperties2 = GPA(vkGetPhysicalDeviceQueueFamilyProperties2); + funcs.vkGetPhysicalDeviceMemoryProperties = GPA(vkGetPhysicalDeviceMemoryProperties); + funcs.vkGetPhysicalDeviceMemoryProperties2 = GPA(vkGetPhysicalDeviceMemoryProperties2); + funcs.vkGetPhysicalDeviceSurfaceSupportKHR = GPA(vkGetPhysicalDeviceSurfaceSupportKHR); + funcs.vkGetPhysicalDeviceSurfaceFormatsKHR = GPA(vkGetPhysicalDeviceSurfaceFormatsKHR); + funcs.vkGetPhysicalDeviceSurfacePresentModesKHR = GPA(vkGetPhysicalDeviceSurfacePresentModesKHR); + funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR = GPA(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); + funcs.vkEnumerateDeviceExtensionProperties = GPA(vkEnumerateDeviceExtensionProperties); + funcs.vkEnumerateDeviceLayerProperties = GPA(vkEnumerateDeviceLayerProperties); + funcs.vkGetPhysicalDeviceExternalBufferProperties = GPA(vkGetPhysicalDeviceExternalBufferProperties); + funcs.vkGetPhysicalDeviceExternalFenceProperties = GPA(vkGetPhysicalDeviceExternalFenceProperties); + funcs.vkGetPhysicalDeviceExternalSemaphoreProperties = GPA(vkGetPhysicalDeviceExternalSemaphoreProperties); + + funcs.vkDestroySurfaceKHR = GPA(vkDestroySurfaceKHR); + funcs.vkGetDeviceProcAddr = GPA(vkGetDeviceProcAddr); + funcs.vkCreateDevice = GPA(vkCreateDevice); + + funcs.vkCreateHeadlessSurfaceEXT = GPA(vkCreateHeadlessSurfaceEXT); + funcs.vkCreateDisplayPlaneSurfaceKHR = GPA(vkCreateDisplayPlaneSurfaceKHR); + funcs.vkGetPhysicalDeviceDisplayPropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPropertiesKHR); + funcs.vkGetPhysicalDeviceDisplayPlanePropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPlanePropertiesKHR); + funcs.vkGetDisplayPlaneSupportedDisplaysKHR = GPA(vkGetDisplayPlaneSupportedDisplaysKHR); + funcs.vkGetDisplayModePropertiesKHR = GPA(vkGetDisplayModePropertiesKHR); + funcs.vkCreateDisplayModeKHR = GPA(vkCreateDisplayModeKHR); + funcs.vkGetDisplayPlaneCapabilitiesKHR = GPA(vkGetDisplayPlaneCapabilitiesKHR); + funcs.vkGetPhysicalDevicePresentRectanglesKHR = GPA(vkGetPhysicalDevicePresentRectanglesKHR); + funcs.vkGetPhysicalDeviceDisplayProperties2KHR = GPA(vkGetPhysicalDeviceDisplayProperties2KHR); + funcs.vkGetPhysicalDeviceDisplayPlaneProperties2KHR = GPA(vkGetPhysicalDeviceDisplayPlaneProperties2KHR); + funcs.vkGetDisplayModeProperties2KHR = GPA(vkGetDisplayModeProperties2KHR); + funcs.vkGetDisplayPlaneCapabilities2KHR = GPA(vkGetDisplayPlaneCapabilities2KHR); + funcs.vkGetPhysicalDeviceSurfaceCapabilities2KHR = GPA(vkGetPhysicalDeviceSurfaceCapabilities2KHR); + funcs.vkGetPhysicalDeviceSurfaceFormats2KHR = GPA(vkGetPhysicalDeviceSurfaceFormats2KHR); + +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + funcs.vkCreateAndroidSurfaceKHR = GPA(vkCreateAndroidSurfaceKHR); +#endif // VK_USE_PLATFORM_ANDROID_KHR +#if defined(VK_USE_PLATFORM_DIRECTFB_EXT) + funcs.vkCreateDirectFBSurfaceEXT = GPA(vkCreateDirectFBSurfaceEXT); + funcs.vkGetPhysicalDeviceDirectFBPresentationSupportEXT = GPA(vkGetPhysicalDeviceDirectFBPresentationSupportEXT); +#endif // VK_USE_PLATFORM_DIRECTFB_EXT +#if defined(VK_USE_PLATFORM_FUCHSIA) + funcs.vkCreateImagePipeSurfaceFUCHSIA = GPA(vkCreateImagePipeSurfaceFUCHSIA); +#endif // VK_USE_PLATFORM_FUCHSIA +#if defined(VK_USE_PLATFORM_GGP) + funcs.vkCreateStreamDescriptorSurfaceGGP = GPA(vkCreateStreamDescriptorSurfaceGGP); +#endif // VK_USE_PLATFORM_GGP +#if defined(VK_USE_PLATFORM_IOS_MVK) + funcs.vkCreateIOSSurfaceMVK = GPA(vkCreateIOSSurfaceMVK); +#endif // VK_USE_PLATFORM_IOS_MVK +#if defined(VK_USE_PLATFORM_MACOS_MVK) + funcs.vkCreateMacOSSurfaceMVK = GPA(vkCreateMacOSSurfaceMVK); +#endif // VK_USE_PLATFORM_MACOS_MVK +#if defined(VK_USE_PLATFORM_METAL_EXT) + funcs.vkCreateMetalSurfaceEXT = GPA(vkCreateMetalSurfaceEXT); +#endif // VK_USE_PLATFORM_METAL_EXT +#if defined(VK_USE_PLATFORM_SCREEN_QNX) + funcs.vkCreateScreenSurfaceQNX = GPA(vkCreateScreenSurfaceQNX); + funcs.vkGetPhysicalDeviceScreenPresentationSupportQNX = GPA(vkGetPhysicalDeviceScreenPresentationSupportQNX); +#endif // VK_USE_PLATFORM_SCREEN_QNX +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + funcs.vkCreateWaylandSurfaceKHR = GPA(vkCreateWaylandSurfaceKHR); + funcs.vkGetPhysicalDeviceWaylandPresentationSupportKHR = GPA(vkGetPhysicalDeviceWaylandPresentationSupportKHR); +#endif // VK_USE_PLATFORM_WAYLAND_KHR +#if defined(VK_USE_PLATFORM_XCB_KHR) + funcs.vkCreateXcbSurfaceKHR = GPA(vkCreateXcbSurfaceKHR); + funcs.vkGetPhysicalDeviceXcbPresentationSupportKHR = GPA(vkGetPhysicalDeviceXcbPresentationSupportKHR); +#endif // VK_USE_PLATFORM_XCB_KHR +#if defined(VK_USE_PLATFORM_XLIB_KHR) + funcs.vkCreateXlibSurfaceKHR = GPA(vkCreateXlibSurfaceKHR); + funcs.vkGetPhysicalDeviceXlibPresentationSupportKHR = GPA(vkGetPhysicalDeviceXlibPresentationSupportKHR); +#endif // VK_USE_PLATFORM_XLIB_KHR +#if defined(VK_USE_PLATFORM_WIN32_KHR) + funcs.vkCreateWin32SurfaceKHR = GPA(vkCreateWin32SurfaceKHR); + funcs.vkGetPhysicalDeviceWin32PresentationSupportKHR = GPA(vkGetPhysicalDeviceWin32PresentationSupportKHR); +#endif // VK_USE_PLATFORM_WIN32_KHR + funcs.vkDestroyDevice = GPA(vkDestroyDevice); + funcs.vkGetDeviceQueue = GPA(vkGetDeviceQueue); +#undef GPA + // clang-format on +} + +#if defined(APPLE_STATIC_LOADER) +VulkanFunctions::VulkanFunctions() { +#else +VulkanFunctions::VulkanFunctions() : loader(get_loader_path()) { +#endif + init_vulkan_functions(*this); +} + +void VulkanFunctions::load_instance_functions(VkInstance instance) { + vkCreateDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); + vkDestroyDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); + vkCreateDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); + vkDestroyDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); +} + +DeviceFunctions::DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device) { + vkGetDeviceProcAddr = vulkan_functions.vkGetDeviceProcAddr; + vkDestroyDevice = load(device, "vkDestroyDevice"); + vkGetDeviceQueue = load(device, "vkGetDeviceQueue"); + vkCreateCommandPool = load(device, "vkCreateCommandPool"); + vkAllocateCommandBuffers = load(device, "vkAllocateCommandBuffers"); + vkDestroyCommandPool = load(device, "vkDestroyCommandPool"); + vkCreateSwapchainKHR = load(device, "vkCreateSwapchainKHR"); + vkGetSwapchainImagesKHR = load(device, "vkGetSwapchainImagesKHR"); + vkDestroySwapchainKHR = load(device, "vkDestroySwapchainKHR"); +} diff --git a/tests/framework/util/dispatch_table.h b/tests/framework/util/dispatch_table.h new file mode 100644 index 000000000..265416532 --- /dev/null +++ b/tests/framework/util/dispatch_table.h @@ -0,0 +1,178 @@ +// VulkanFunctions - loads vulkan functions for tests to use +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include "functions.h" +#include "dynamic_library_wrapper.h" + +struct VulkanFunctions { +#if !defined(APPLE_STATIC_LOADER) + LibraryWrapper loader; +#endif + // Pre-Instance + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; + PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties = nullptr; + PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties = nullptr; + PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion = nullptr; + PFN_vkCreateInstance vkCreateInstance = nullptr; + + // Instance + PFN_vkDestroyInstance vkDestroyInstance = nullptr; + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = nullptr; + PFN_vkEnumeratePhysicalDeviceGroups vkEnumeratePhysicalDeviceGroups = nullptr; + PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures = nullptr; + PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2 = nullptr; + PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties = nullptr; + PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2 = nullptr; + PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties = nullptr; + PFN_vkGetPhysicalDeviceImageFormatProperties2 vkGetPhysicalDeviceImageFormatProperties2 = nullptr; + PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties = nullptr; + PFN_vkGetPhysicalDeviceSparseImageFormatProperties2 vkGetPhysicalDeviceSparseImageFormatProperties2 = nullptr; + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = nullptr; + PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2 = nullptr; + PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties = nullptr; + PFN_vkGetPhysicalDeviceQueueFamilyProperties2 vkGetPhysicalDeviceQueueFamilyProperties2 = nullptr; + PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties = nullptr; + PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2 = nullptr; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR = nullptr; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR = nullptr; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR = nullptr; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr; + PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties = nullptr; + PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties = nullptr; + PFN_vkGetPhysicalDeviceExternalBufferProperties vkGetPhysicalDeviceExternalBufferProperties = nullptr; + PFN_vkGetPhysicalDeviceExternalFenceProperties vkGetPhysicalDeviceExternalFenceProperties = nullptr; + PFN_vkGetPhysicalDeviceExternalSemaphoreProperties vkGetPhysicalDeviceExternalSemaphoreProperties = nullptr; + + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = nullptr; + PFN_vkCreateDevice vkCreateDevice = nullptr; + + // WSI + PFN_vkCreateHeadlessSurfaceEXT vkCreateHeadlessSurfaceEXT = nullptr; + PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR = nullptr; + PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR = nullptr; + PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR = nullptr; + PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR = nullptr; + PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR = nullptr; + PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR = nullptr; + PFN_vkGetPhysicalDevicePresentRectanglesKHR vkGetPhysicalDevicePresentRectanglesKHR = nullptr; + PFN_vkGetPhysicalDeviceDisplayProperties2KHR vkGetPhysicalDeviceDisplayProperties2KHR = nullptr; + PFN_vkGetPhysicalDeviceDisplayPlaneProperties2KHR vkGetPhysicalDeviceDisplayPlaneProperties2KHR = nullptr; + PFN_vkGetDisplayModeProperties2KHR vkGetDisplayModeProperties2KHR = nullptr; + PFN_vkGetDisplayPlaneCapabilities2KHR vkGetDisplayPlaneCapabilities2KHR = nullptr; + PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR vkGetPhysicalDeviceSurfaceCapabilities2KHR = nullptr; + PFN_vkGetPhysicalDeviceSurfaceFormats2KHR vkGetPhysicalDeviceSurfaceFormats2KHR = nullptr; + +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR = nullptr; +#endif // VK_USE_PLATFORM_ANDROID_KHR +#if defined(VK_USE_PLATFORM_DIRECTFB_EXT) + PFN_vkCreateDirectFBSurfaceEXT vkCreateDirectFBSurfaceEXT = nullptr; + PFN_vkGetPhysicalDeviceDirectFBPresentationSupportEXT vkGetPhysicalDeviceDirectFBPresentationSupportEXT = nullptr; +#endif // VK_USE_PLATFORM_DIRECTFB_EXT +#if defined(VK_USE_PLATFORM_FUCHSIA) + PFN_vkCreateImagePipeSurfaceFUCHSIA vkCreateImagePipeSurfaceFUCHSIA = nullptr; +#endif // VK_USE_PLATFORM_FUCHSIA +#if defined(VK_USE_PLATFORM_GGP) + PFN_vkCreateStreamDescriptorSurfaceGGP vkCreateStreamDescriptorSurfaceGGP = nullptr; +#endif // VK_USE_PLATFORM_GGP +#if defined(VK_USE_PLATFORM_IOS_MVK) + PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK = nullptr; +#endif // VK_USE_PLATFORM_IOS_MVK +#if defined(VK_USE_PLATFORM_MACOS_MVK) + PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK = nullptr; +#endif // VK_USE_PLATFORM_MACOS_MVK +#if defined(VK_USE_PLATFORM_METAL_EXT) + PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT = nullptr; +#endif // VK_USE_PLATFORM_METAL_EXT +#if defined(VK_USE_PLATFORM_SCREEN_QNX) + PFN_vkCreateScreenSurfaceQNX vkCreateScreenSurfaceQNX = nullptr; + PFN_vkGetPhysicalDeviceScreenPresentationSupportQNX vkGetPhysicalDeviceScreenPresentationSupportQNX = nullptr; +#endif // VK_USE_PLATFORM_SCREEN_QNX +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR = nullptr; +#endif // VK_USE_PLATFORM_WAYLAND_KHR +#if defined(VK_USE_PLATFORM_XCB_KHR) + PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR = nullptr; +#endif // VK_USE_PLATFORM_XCB_KHR +#if defined(VK_USE_PLATFORM_XLIB_KHR) + PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR = nullptr; +#endif // VK_USE_PLATFORM_XLIB_KHR +#if defined(VK_USE_PLATFORM_WIN32_KHR) + PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR = nullptr; +#endif // VK_USE_PLATFORM_WIN32_KHR + PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = nullptr; + + // instance extensions functions (can only be loaded with a valid instance) + PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = nullptr; // Null unless the extension is enabled + PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT = nullptr; // Null unless the extension is enabled + PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = nullptr; // Null unless the extension is enabled + PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT = nullptr; // Null unless the extension is enabled + + // device functions + PFN_vkDestroyDevice vkDestroyDevice = nullptr; + PFN_vkGetDeviceQueue vkGetDeviceQueue = nullptr; + + VulkanFunctions(); + + void load_instance_functions(VkInstance instance); + + FromVoidStarFunc load(VkInstance inst, const char* func_name) const { + return FromVoidStarFunc(vkGetInstanceProcAddr(inst, func_name)); + } + + FromVoidStarFunc load(VkDevice device, const char* func_name) const { + return FromVoidStarFunc(vkGetDeviceProcAddr(device, func_name)); + } +}; + +struct DeviceFunctions { + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = nullptr; + PFN_vkDestroyDevice vkDestroyDevice = nullptr; + PFN_vkGetDeviceQueue vkGetDeviceQueue = nullptr; + PFN_vkCreateCommandPool vkCreateCommandPool = nullptr; + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = nullptr; + PFN_vkDestroyCommandPool vkDestroyCommandPool = nullptr; + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = nullptr; + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR = nullptr; + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR = nullptr; + + DeviceFunctions() = default; + DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device); + + FromVoidStarFunc load(VkDevice device, const char* func_name) const { + return FromVoidStarFunc(vkGetDeviceProcAddr(device, func_name)); + } +}; diff --git a/tests/framework/util/dispatchable_handle.h b/tests/framework/util/dispatchable_handle.h new file mode 100644 index 000000000..98085bb7d --- /dev/null +++ b/tests/framework/util/dispatchable_handle.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include "test_defines.h" + +template +struct FRAMEWORK_EXPORT DispatchableHandle { + DispatchableHandle() { + auto ptr_handle = new VK_LOADER_DATA; + set_loader_magic_value(ptr_handle); + handle = reinterpret_cast(ptr_handle); + } + ~DispatchableHandle() { + if (handle) { + delete reinterpret_cast(handle); + } + handle = nullptr; + } + DispatchableHandle(DispatchableHandle const&) = delete; + DispatchableHandle& operator=(DispatchableHandle const&) = delete; + DispatchableHandle(DispatchableHandle&& other) noexcept : handle(other.handle) { other.handle = nullptr; } + DispatchableHandle& operator=(DispatchableHandle&& other) noexcept { + if (handle) { + delete reinterpret_cast(handle); + } + handle = other.handle; + other.handle = nullptr; + return *this; + } + bool operator==(T base_handle) { return base_handle == handle; } + bool operator!=(T base_handle) { return base_handle != handle; } + + T handle = nullptr; +}; diff --git a/tests/framework/util/dynamic_library_wrapper.cpp b/tests/framework/util/dynamic_library_wrapper.cpp new file mode 100644 index 000000000..e868d1607 --- /dev/null +++ b/tests/framework/util/dynamic_library_wrapper.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "dynamic_library_wrapper.h" + +#include "test_defines.h" +#include "env_var_wrapper.h" + +#include +#include +#include + +#include + +namespace detail { + +#if defined(_WIN32) +typedef HMODULE test_platform_dl_handle; + +std::string error_code_to_string(DWORD error_code) { + LPSTR lpMsgBuf{}; + size_t MsgBufSize = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpMsgBuf, 0, nullptr); + std::string code_str(lpMsgBuf, MsgBufSize); + LocalFree(lpMsgBuf); + return code_str; +} +std::wstring wide_error_code_to_string(DWORD error_code) { + LPWSTR lpwMsgBuf{}; + size_t MsgBufSize = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpwMsgBuf, 0, nullptr); + std::wstring code_str(lpwMsgBuf, MsgBufSize); + LocalFree(lpwMsgBuf); + return code_str; +} +#elif TESTING_COMMON_UNIX_PLATFORMS +#include + +typedef void* test_platform_dl_handle; +#endif +} // namespace detail + +LibraryWrapper::LibraryWrapper() noexcept {} +LibraryWrapper::LibraryWrapper(std::filesystem::path const& lib_path) noexcept : lib_path(lib_path.lexically_normal()) { +#if defined(_WIN32) + auto wide_path = lib_path.native(); // Get a std::wstring first + // Try loading the library the original way first. + lib_handle = LoadLibraryW(wide_path.c_str()); + DWORD last_error = GetLastError(); + // If that failed because of path limitations, prepend a special sequence to opt into longer path support + if (lib_handle == nullptr && last_error == ERROR_FILENAME_EXCED_RANGE) { + // "\\?\" is a special prefix for filenames that tells winapi to ignore path limits, so we can workaround path limitations + // without relying on system level changes. + wide_path = L"\\\\?\\" + wide_path; + last_error = 0; + lib_handle = LoadLibraryW(wide_path.c_str()); + last_error = GetLastError(); + } + // If that failed, then try loading it with broader search folders. + if (lib_handle == nullptr && last_error == ERROR_MOD_NOT_FOUND) { + last_error = 0; + lib_handle = + LoadLibraryExW(wide_path.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); + last_error = GetLastError(); + } + // If neither of the above worked, print an error message and abort + if (lib_handle == nullptr) { + auto error_msg = detail::wide_error_code_to_string(last_error); + std::wcerr << L"Unable to open library: " << wide_path << L" due to: " << error_msg << L"\n"; + abort(); + } +#elif TESTING_COMMON_UNIX_PLATFORMS + lib_handle = dlopen(lib_path.string().c_str(), RTLD_LAZY | RTLD_LOCAL); + if (lib_handle == nullptr) { + std::wcerr << "Unable to open library: " << lib_path << " due to: " << dlerror() << "\n"; + abort(); + } +#else +#error "Unhandled platform in dynamic_library_wrapper.cpp!" +#endif +} + +LibraryWrapper::~LibraryWrapper() noexcept { close_library(); } + +LibraryWrapper::LibraryWrapper(LibraryWrapper&& wrapper) noexcept : lib_handle(wrapper.lib_handle), lib_path(wrapper.lib_path) { + wrapper.lib_handle = nullptr; +} +LibraryWrapper& LibraryWrapper::operator=(LibraryWrapper&& wrapper) noexcept { + if (this != &wrapper) { + lib_handle = wrapper.lib_handle; + lib_path = wrapper.lib_path; + wrapper.lib_handle = nullptr; + } + return *this; +} + +void LibraryWrapper::close_library() noexcept { + auto loader_disable_dynamic_library_unloading_env_var = get_env_var("VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING", false); + if (loader_disable_dynamic_library_unloading_env_var.empty() || loader_disable_dynamic_library_unloading_env_var != "1") { + if (lib_handle != nullptr) { +#if defined(_WIN32) + FreeLibrary(lib_handle); +#elif TESTING_COMMON_UNIX_PLATFORMS + dlclose(lib_handle); +#else +#error "Unhandled platform in dynamic_library_wrapper.cpp!" +#endif + lib_handle = nullptr; + } + } +} + +FromVoidStarFunc LibraryWrapper::get_symbol(const char* symbol_name) const { + if (symbol_name == nullptr) { + std::cerr << "LibraryWrapper::get_symbol called with a null symbol_name!\n"; + abort(); + } + if (lib_handle == nullptr) { + std::cerr << "LibraryWrapper::get_symbol called when lib_handle is nullptr!\n"; + abort(); + } + +#if defined(_WIN32) + void* symbol = reinterpret_cast(GetProcAddress(lib_handle, symbol_name)); + if (symbol == nullptr) { + auto last_error = detail::error_code_to_string(GetLastError()); + std::cerr << "Unable to get symbol " << symbol_name << " in library " << lib_path.string() << " with to error code " + << last_error << "\n"; + abort(); + } +#elif TESTING_COMMON_UNIX_PLATFORMS + void* symbol = dlsym(lib_handle, symbol_name); + if (symbol == nullptr) { + std::cerr << "Unable to get symbol " << symbol_name << " in library " << lib_path.string() << " due to " << dlerror() + << "\n"; + abort(); + } +#else +#error "Unhandled platform in dynamic_library_wrapper.cpp!" +#endif + return FromVoidStarFunc(symbol); +} diff --git a/tests/framework/util/dynamic_library_wrapper.h b/tests/framework/util/dynamic_library_wrapper.h new file mode 100644 index 000000000..35b72adbe --- /dev/null +++ b/tests/framework/util/dynamic_library_wrapper.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include "test_defines.h" + +#include "functions.h" + +#if defined(_WIN32) +#include +#include +#include +#elif TESTING_COMMON_UNIX_PLATFORMS +#include +#include +#include +#include +#include +#endif + +struct LibraryWrapper { + explicit LibraryWrapper() noexcept; + explicit LibraryWrapper(std::filesystem::path const& lib_path) noexcept; + ~LibraryWrapper() noexcept; + LibraryWrapper(LibraryWrapper const& wrapper) = delete; + LibraryWrapper& operator=(LibraryWrapper const& wrapper) = delete; + LibraryWrapper(LibraryWrapper&& wrapper) noexcept; + LibraryWrapper& operator=(LibraryWrapper&& wrapper) noexcept; + FromVoidStarFunc get_symbol(const char* symbol_name) const; + + std::filesystem::path const& get_path() const { return lib_path; } + explicit operator bool() const noexcept { return lib_handle != nullptr; } + + private: + void close_library() noexcept; + +#if defined(_WIN32) + HMODULE lib_handle = nullptr; +#elif TESTING_COMMON_UNIX_PLATFORMS + void* lib_handle = nullptr; +#else +#error "Unhandled platform in dynamic_library_wrapper.h!" +#endif + std::filesystem::path lib_path; +}; diff --git a/tests/framework/util/env_var_wrapper.cpp b/tests/framework/util/env_var_wrapper.cpp new file mode 100644 index 000000000..f9df38994 --- /dev/null +++ b/tests/framework/util/env_var_wrapper.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "env_var_wrapper.h" + +#include + +#include + +#include "wide_char_handling.h" + +// NOTE: get_env_var() are only intended for test framework code, all test code MUST use +// EnvVarWrapper + +#if defined(_WIN32) +#include +#include +#include + +// Windows specific error handling logic +const long ERROR_SETENV_FAILED = 10543; // chosen at random, attempts to not conflict +const long ERROR_REMOVEDIRECTORY_FAILED = 10544; // chosen at random, attempts to not conflict +const char* win_api_error_str(LSTATUS status) { + if (status == ERROR_INVALID_FUNCTION) return "ERROR_INVALID_FUNCTION"; + if (status == ERROR_FILE_NOT_FOUND) return "ERROR_FILE_NOT_FOUND"; + if (status == ERROR_PATH_NOT_FOUND) return "ERROR_PATH_NOT_FOUND"; + if (status == ERROR_TOO_MANY_OPEN_FILES) return "ERROR_TOO_MANY_OPEN_FILES"; + if (status == ERROR_ACCESS_DENIED) return "ERROR_ACCESS_DENIED"; + if (status == ERROR_INVALID_HANDLE) return "ERROR_INVALID_HANDLE"; + if (status == ERROR_ENVVAR_NOT_FOUND) return "ERROR_ENVVAR_NOT_FOUND"; + if (status == ERROR_SETENV_FAILED) return "ERROR_SETENV_FAILED"; + return "UNKNOWN ERROR"; +} +std::string error_code_to_string(DWORD error_code) { + LPSTR lpMsgBuf{}; + size_t MsgBufSize = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpMsgBuf, 0, nullptr); + std::string code_str(lpMsgBuf, MsgBufSize); + LocalFree(lpMsgBuf); + return code_str; +} + +void print_error_message(LSTATUS status, const char* function_name, std::string optional_message = "") { + DWORD dw = GetLastError(); + + std::cerr << function_name << " failed with " << win_api_error_str(status) << ": " << error_code_to_string(dw); + if (optional_message != "") { + std::cerr << " | " << optional_message; + } + std::cerr << "\n"; +} + +// get_env_var() - returns a std::string of `name`. if report_failure is true, then it will log to stderr that it didn't find the +// env-var +std::string get_env_var(std::string const& name, bool report_failure) { + std::wstring name_utf16 = widen(name); + DWORD value_size = GetEnvironmentVariableW(name_utf16.c_str(), nullptr, 0); + if (0 == value_size) { + if (report_failure) print_error_message(ERROR_ENVVAR_NOT_FOUND, "GetEnvironmentVariableW"); + return {}; + } + std::wstring value(value_size, L'\0'); + if (GetEnvironmentVariableW(name_utf16.c_str(), &value[0], value_size) != value_size - 1) { + return {}; + } + return narrow(value); +} +#elif TESTING_COMMON_UNIX_PLATFORMS + +// get_env_var() - returns a std::string of `name`. if report_failure is true, then it will log to stderr that it didn't find the +// env-var +std::string get_env_var(std::string const& name, bool report_failure) { + char* ret = getenv(name.c_str()); + if (ret == nullptr) { + if (report_failure) std::cerr << "Failed to get environment variable:" << name << "\n"; + return std::string(); + } + return ret; +} +#endif + +std::vector split_env_var_as_list(std::string const& env_var_contents) { + std::vector items; + size_t start = 0; + size_t len = 0; + for (size_t i = 0; i < env_var_contents.size(); i++) { + if (env_var_contents[i] == OS_ENV_VAR_LIST_SEPARATOR) { + if (len != 0) { + // only push back non empty strings + items.push_back(env_var_contents.substr(start, len)); + } + start = i + 1; + len = 0; + } else { + len++; + } + } + items.push_back(env_var_contents.substr(start, len)); + return items; +} + +/* + * Wrapper around Environment Variables with common operations + * Since Environment Variables leak between tests, there needs to be RAII code to remove them during test cleanup + + */ + +EnvVarWrapper::EnvVarWrapper(std::string const& name) noexcept : name(name) { + initial_value = get_env_var(name, false); + remove_env_var(); +} +EnvVarWrapper::EnvVarWrapper(std::string const& name, std::string const& value) noexcept : name(name), cur_value(value) { + initial_value = get_env_var(name, false); + set_env_var(); +} +EnvVarWrapper::~EnvVarWrapper() noexcept { + remove_env_var(); + if (!initial_value.empty()) { + set_new_value(initial_value); + } +} + +void EnvVarWrapper::set_new_value(std::string const& value) { + cur_value = value; + set_env_var(); +} +void EnvVarWrapper::add_to_list(std::string const& list_item) { + if (!cur_value.empty()) { + cur_value += OS_ENV_VAR_LIST_SEPARATOR; + } + cur_value += list_item; + set_env_var(); +} +#if defined(_WIN32) +void EnvVarWrapper::add_to_list(std::wstring const& list_item) { + if (!cur_value.empty()) { + cur_value += OS_ENV_VAR_LIST_SEPARATOR; + } + cur_value += narrow(list_item); + set_env_var(); +} +#endif +void EnvVarWrapper::remove_value() const { remove_env_var(); } +const char* EnvVarWrapper::get() const { return name.c_str(); } +const char* EnvVarWrapper::value() const { return cur_value.c_str(); } + +#if defined(_WIN32) + +void EnvVarWrapper::set_env_var() const { + BOOL ret = SetEnvironmentVariableW(widen(name).c_str(), widen(cur_value).c_str()); + if (ret == 0) { + print_error_message(ERROR_SETENV_FAILED, "SetEnvironmentVariableW"); + } +} +void EnvVarWrapper::remove_env_var() const { SetEnvironmentVariableW(widen(name).c_str(), nullptr); } + +#elif TESTING_COMMON_UNIX_PLATFORMS + +void EnvVarWrapper::set_env_var() const { setenv(name.c_str(), cur_value.c_str(), 1); } +void EnvVarWrapper::remove_env_var() const { unsetenv(name.c_str()); } +#endif diff --git a/tests/framework/util/env_var_wrapper.h b/tests/framework/util/env_var_wrapper.h new file mode 100644 index 000000000..2a0ecca7a --- /dev/null +++ b/tests/framework/util/env_var_wrapper.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include +#include + +#include "test_defines.h" + +// Environment variable list separator - not for filesystem paths +#if defined(_WIN32) +static const char OS_ENV_VAR_LIST_SEPARATOR = ';'; +#elif TESTING_COMMON_UNIX_PLATFORMS +static const char OS_ENV_VAR_LIST_SEPARATOR = ':'; +#else +#error "Unhandled platform in env_var_wrapper.h!" +#endif + +// get_env_var() - returns the contents of env-var `name` as a std::string +// If report_failure is true, then it will log to stderr that it didn't find the env-var +std::string get_env_var(std::string const& name, bool report_failure = true); + +// split_env_var_as_list() - split env_var_contents into separate elements using the platform +// specific env-var list separator. +std::vector split_env_var_as_list(std::string const& env_var_contents); + +/* + * Wrapper around Environment Variables with common operations + * Since Environment Variables leak between tests, there needs to be RAII code to remove them during test cleanup + + */ + +// Wrapper to set & remove env-vars automatically +struct EnvVarWrapper { + // Constructor which unsets the env-var + EnvVarWrapper(std::string const& name) noexcept; + // Constructor which set the env-var to the specified value + EnvVarWrapper(std::string const& name, std::string const& value) noexcept; + ~EnvVarWrapper() noexcept; + + // delete copy operators + EnvVarWrapper(const EnvVarWrapper&) = delete; + EnvVarWrapper& operator=(const EnvVarWrapper&) = delete; + + void set_new_value(std::string const& value); + void add_to_list(std::string const& list_item); +#if defined(_WIN32) + void add_to_list(std::wstring const& list_item); +#endif + void remove_value() const; + const char* get() const; + const char* value() const; + + private: + std::string name; + std::string cur_value; + std::string initial_value; + + void set_env_var() const; + void remove_env_var() const; +}; diff --git a/tests/framework/util/equality_helpers.cpp b/tests/framework/util/equality_helpers.cpp new file mode 100644 index 000000000..4a98599d4 --- /dev/null +++ b/tests/framework/util/equality_helpers.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "equality_helpers.h" + +bool operator==(const VkExtent3D& a, const VkExtent3D& b) { + return a.width == b.width && a.height == b.height && a.depth == b.depth; +} +bool operator!=(const VkExtent3D& a, const VkExtent3D& b) { return !(a == b); } + +bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b) { + return a.minImageTransferGranularity == b.minImageTransferGranularity && a.queueCount == b.queueCount && + a.queueFlags == b.queueFlags && a.timestampValidBits == b.timestampValidBits; +} +bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b) { return !(a == b); } + +bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b) { return a == b.queueFamilyProperties; } +bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b) { return a != b.queueFamilyProperties; } + +bool operator==(const VkLayerProperties& a, const VkLayerProperties& b) { + return string_eq(a.layerName, b.layerName, 256) && string_eq(a.description, b.description, 256) && + a.implementationVersion == b.implementationVersion && a.specVersion == b.specVersion; +} +bool operator!=(const VkLayerProperties& a, const VkLayerProperties& b) { return !(a == b); } + +bool operator==(const VkExtensionProperties& a, const VkExtensionProperties& b) { + return string_eq(a.extensionName, b.extensionName, 256) && a.specVersion == b.specVersion; +} +bool operator!=(const VkExtensionProperties& a, const VkExtensionProperties& b) { return !(a == b); } + +bool operator==(const VkPhysicalDeviceFeatures& feats1, const VkPhysicalDeviceFeatures2& feats2) { + return feats1.robustBufferAccess == feats2.features.robustBufferAccess && + feats1.fullDrawIndexUint32 == feats2.features.fullDrawIndexUint32 && + feats1.imageCubeArray == feats2.features.imageCubeArray && feats1.independentBlend == feats2.features.independentBlend && + feats1.geometryShader == feats2.features.geometryShader && + feats1.tessellationShader == feats2.features.tessellationShader && + feats1.sampleRateShading == feats2.features.sampleRateShading && feats1.dualSrcBlend == feats2.features.dualSrcBlend && + feats1.logicOp == feats2.features.logicOp && feats1.multiDrawIndirect == feats2.features.multiDrawIndirect && + feats1.drawIndirectFirstInstance == feats2.features.drawIndirectFirstInstance && + feats1.depthClamp == feats2.features.depthClamp && feats1.depthBiasClamp == feats2.features.depthBiasClamp && + feats1.fillModeNonSolid == feats2.features.fillModeNonSolid && feats1.depthBounds == feats2.features.depthBounds && + feats1.wideLines == feats2.features.wideLines && feats1.largePoints == feats2.features.largePoints && + feats1.alphaToOne == feats2.features.alphaToOne && feats1.multiViewport == feats2.features.multiViewport && + feats1.samplerAnisotropy == feats2.features.samplerAnisotropy && + feats1.textureCompressionETC2 == feats2.features.textureCompressionETC2 && + feats1.textureCompressionASTC_LDR == feats2.features.textureCompressionASTC_LDR && + feats1.textureCompressionBC == feats2.features.textureCompressionBC && + feats1.occlusionQueryPrecise == feats2.features.occlusionQueryPrecise && + feats1.pipelineStatisticsQuery == feats2.features.pipelineStatisticsQuery && + feats1.vertexPipelineStoresAndAtomics == feats2.features.vertexPipelineStoresAndAtomics && + feats1.fragmentStoresAndAtomics == feats2.features.fragmentStoresAndAtomics && + feats1.shaderTessellationAndGeometryPointSize == feats2.features.shaderTessellationAndGeometryPointSize && + feats1.shaderImageGatherExtended == feats2.features.shaderImageGatherExtended && + feats1.shaderStorageImageExtendedFormats == feats2.features.shaderStorageImageExtendedFormats && + feats1.shaderStorageImageMultisample == feats2.features.shaderStorageImageMultisample && + feats1.shaderStorageImageReadWithoutFormat == feats2.features.shaderStorageImageReadWithoutFormat && + feats1.shaderStorageImageWriteWithoutFormat == feats2.features.shaderStorageImageWriteWithoutFormat && + feats1.shaderUniformBufferArrayDynamicIndexing == feats2.features.shaderUniformBufferArrayDynamicIndexing && + feats1.shaderSampledImageArrayDynamicIndexing == feats2.features.shaderSampledImageArrayDynamicIndexing && + feats1.shaderStorageBufferArrayDynamicIndexing == feats2.features.shaderStorageBufferArrayDynamicIndexing && + feats1.shaderStorageImageArrayDynamicIndexing == feats2.features.shaderStorageImageArrayDynamicIndexing && + feats1.shaderClipDistance == feats2.features.shaderClipDistance && + feats1.shaderCullDistance == feats2.features.shaderCullDistance && + feats1.shaderFloat64 == feats2.features.shaderFloat64 && feats1.shaderInt64 == feats2.features.shaderInt64 && + feats1.shaderInt16 == feats2.features.shaderInt16 && + feats1.shaderResourceResidency == feats2.features.shaderResourceResidency && + feats1.shaderResourceMinLod == feats2.features.shaderResourceMinLod && + feats1.sparseBinding == feats2.features.sparseBinding && + feats1.sparseResidencyBuffer == feats2.features.sparseResidencyBuffer && + feats1.sparseResidencyImage2D == feats2.features.sparseResidencyImage2D && + feats1.sparseResidencyImage3D == feats2.features.sparseResidencyImage3D && + feats1.sparseResidency2Samples == feats2.features.sparseResidency2Samples && + feats1.sparseResidency4Samples == feats2.features.sparseResidency4Samples && + feats1.sparseResidency8Samples == feats2.features.sparseResidency8Samples && + feats1.sparseResidency16Samples == feats2.features.sparseResidency16Samples && + feats1.sparseResidencyAliased == feats2.features.sparseResidencyAliased && + feats1.variableMultisampleRate == feats2.features.variableMultisampleRate && + feats1.inheritedQueries == feats2.features.inheritedQueries; +} + +bool operator==(const VkPhysicalDeviceMemoryProperties& props1, const VkPhysicalDeviceMemoryProperties2& props2) { + bool equal = true; + equal = equal && props1.memoryTypeCount == props2.memoryProperties.memoryTypeCount; + equal = equal && props1.memoryHeapCount == props2.memoryProperties.memoryHeapCount; + for (uint32_t i = 0; i < props1.memoryHeapCount; ++i) { + equal = equal && props1.memoryHeaps[i].size == props2.memoryProperties.memoryHeaps[i].size; + equal = equal && props1.memoryHeaps[i].flags == props2.memoryProperties.memoryHeaps[i].flags; + } + for (uint32_t i = 0; i < props1.memoryTypeCount; ++i) { + equal = equal && props1.memoryTypes[i].propertyFlags == props2.memoryProperties.memoryTypes[i].propertyFlags; + equal = equal && props1.memoryTypes[i].heapIndex == props2.memoryProperties.memoryTypes[i].heapIndex; + } + return equal; +} +bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties& props2) { + return props1.aspectMask == props2.aspectMask && props1.imageGranularity.width == props2.imageGranularity.width && + props1.imageGranularity.height == props2.imageGranularity.height && + props1.imageGranularity.depth == props2.imageGranularity.depth && props1.flags == props2.flags; +} +bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties2& props2) { + return props1 == props2.properties; +} +bool operator==(const VkExternalMemoryProperties& props1, const VkExternalMemoryProperties& props2) { + return props1.externalMemoryFeatures == props2.externalMemoryFeatures && + props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && + props1.compatibleHandleTypes == props2.compatibleHandleTypes; +} +bool operator==(const VkExternalSemaphoreProperties& props1, const VkExternalSemaphoreProperties& props2) { + return props1.externalSemaphoreFeatures == props2.externalSemaphoreFeatures && + props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && + props1.compatibleHandleTypes == props2.compatibleHandleTypes; +} +bool operator==(const VkExternalFenceProperties& props1, const VkExternalFenceProperties& props2) { + return props1.externalFenceFeatures == props2.externalFenceFeatures && + props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && + props1.compatibleHandleTypes == props2.compatibleHandleTypes; +} +bool operator==(const VkSurfaceCapabilitiesKHR& props1, const VkSurfaceCapabilitiesKHR& props2) { + return props1.minImageCount == props2.minImageCount && props1.maxImageCount == props2.maxImageCount && + props1.currentExtent.width == props2.currentExtent.width && props1.currentExtent.height == props2.currentExtent.height && + props1.minImageExtent.width == props2.minImageExtent.width && + props1.minImageExtent.height == props2.minImageExtent.height && + props1.maxImageExtent.width == props2.maxImageExtent.width && + props1.maxImageExtent.height == props2.maxImageExtent.height && + props1.maxImageArrayLayers == props2.maxImageArrayLayers && props1.supportedTransforms == props2.supportedTransforms && + props1.currentTransform == props2.currentTransform && props1.supportedCompositeAlpha == props2.supportedCompositeAlpha && + props1.supportedUsageFlags == props2.supportedUsageFlags; +} +bool operator==(const VkSurfacePresentScalingCapabilitiesEXT& caps1, const VkSurfacePresentScalingCapabilitiesEXT& caps2) { + return caps1.supportedPresentScaling == caps2.supportedPresentScaling && + caps1.supportedPresentGravityX == caps2.supportedPresentGravityX && + caps1.supportedPresentGravityY == caps2.supportedPresentGravityY && + caps1.minScaledImageExtent.width == caps2.minScaledImageExtent.width && + caps1.minScaledImageExtent.height == caps2.minScaledImageExtent.height && + caps1.maxScaledImageExtent.width == caps2.maxScaledImageExtent.width && + caps1.maxScaledImageExtent.height == caps2.maxScaledImageExtent.height; +} +bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormatKHR& format2) { + return format1.format == format2.format && format1.colorSpace == format2.colorSpace; +} +bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormat2KHR& format2) { return format1 == format2.surfaceFormat; } +bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayPropertiesKHR& props2) { + return props1.display == props2.display && props1.physicalDimensions.width == props2.physicalDimensions.width && + props1.physicalDimensions.height == props2.physicalDimensions.height && + props1.physicalResolution.width == props2.physicalResolution.width && + props1.physicalResolution.height == props2.physicalResolution.height && + props1.supportedTransforms == props2.supportedTransforms && props1.planeReorderPossible == props2.planeReorderPossible && + props1.persistentContent == props2.persistentContent; +} +bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayProperties2KHR& props2) { + return props1 == props2.displayProperties; +} +bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModePropertiesKHR& disp2) { + return disp1.displayMode == disp2.displayMode && disp1.parameters.visibleRegion.width == disp2.parameters.visibleRegion.width && + disp1.parameters.visibleRegion.height == disp2.parameters.visibleRegion.height && + disp1.parameters.refreshRate == disp2.parameters.refreshRate; +} + +bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModeProperties2KHR& disp2) { + return disp1 == disp2.displayModeProperties; +} +bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilitiesKHR& caps2) { + return caps1.supportedAlpha == caps2.supportedAlpha && caps1.minSrcPosition.x == caps2.minSrcPosition.x && + caps1.minSrcPosition.y == caps2.minSrcPosition.y && caps1.maxSrcPosition.x == caps2.maxSrcPosition.x && + caps1.maxSrcPosition.y == caps2.maxSrcPosition.y && caps1.minSrcExtent.width == caps2.minSrcExtent.width && + caps1.minSrcExtent.height == caps2.minSrcExtent.height && caps1.maxSrcExtent.width == caps2.maxSrcExtent.width && + caps1.maxSrcExtent.height == caps2.maxSrcExtent.height && caps1.minDstPosition.x == caps2.minDstPosition.x && + caps1.minDstPosition.y == caps2.minDstPosition.y && caps1.maxDstPosition.x == caps2.maxDstPosition.x && + caps1.maxDstPosition.y == caps2.maxDstPosition.y && caps1.minDstExtent.width == caps2.minDstExtent.width && + caps1.minDstExtent.height == caps2.minDstExtent.height && caps1.maxDstExtent.width == caps2.maxDstExtent.width && + caps1.maxDstExtent.height == caps2.maxDstExtent.height; +} + +bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilities2KHR& caps2) { + return caps1 == caps2.capabilities; +} +bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlanePropertiesKHR& props2) { + return props1.currentDisplay == props2.currentDisplay && props1.currentStackIndex == props2.currentStackIndex; +} +bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlaneProperties2KHR& props2) { + return props1 == props2.displayPlaneProperties; +} +bool operator==(const VkExtent2D& ext1, const VkExtent2D& ext2) { return ext1.height == ext2.height && ext1.width == ext2.width; } diff --git a/tests/framework/util/equality_helpers.h b/tests/framework/util/equality_helpers.h new file mode 100644 index 000000000..5c38f5031 --- /dev/null +++ b/tests/framework/util/equality_helpers.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include +#include + +#include + +inline bool string_eq(const char* a, const char* b) noexcept { return a && b && strcmp(a, b) == 0; } +inline bool string_eq(const char* a, const char* b, size_t len) noexcept { return a && b && strncmp(a, b, len) == 0; } + +bool operator==(const VkExtent3D& a, const VkExtent3D& b); +bool operator!=(const VkExtent3D& a, const VkExtent3D& b); + +bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b); +bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b); + +bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b); +bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b); + +bool operator==(const VkLayerProperties& a, const VkLayerProperties& b); +bool operator!=(const VkLayerProperties& a, const VkLayerProperties& b); + +bool operator==(const VkExtensionProperties& a, const VkExtensionProperties& b); +bool operator!=(const VkExtensionProperties& a, const VkExtensionProperties& b); + +bool operator==(const VkPhysicalDeviceFeatures& feats1, const VkPhysicalDeviceFeatures2& feats2); + +bool operator==(const VkPhysicalDeviceMemoryProperties& props1, const VkPhysicalDeviceMemoryProperties2& props2); +bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties& props2); +bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties2& props2); +bool operator==(const VkExternalMemoryProperties& props1, const VkExternalMemoryProperties& props2); +bool operator==(const VkExternalSemaphoreProperties& props1, const VkExternalSemaphoreProperties& props2); +bool operator==(const VkExternalFenceProperties& props1, const VkExternalFenceProperties& props2); +bool operator==(const VkSurfaceCapabilitiesKHR& props1, const VkSurfaceCapabilitiesKHR& props2); +bool operator==(const VkSurfacePresentScalingCapabilitiesEXT& caps1, const VkSurfacePresentScalingCapabilitiesEXT& caps2); +bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormatKHR& format2); +bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormat2KHR& format2); +bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayPropertiesKHR& props2); +bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayProperties2KHR& props2); +bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModePropertiesKHR& disp2); + +bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModeProperties2KHR& disp2); +bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilitiesKHR& caps2); + +bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilities2KHR& caps2); +bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlanePropertiesKHR& props2); +bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlaneProperties2KHR& props2); +bool operator==(const VkExtent2D& ext1, const VkExtent2D& ext2); + +// Allow comparison of vectors of different types as long as their elements are comparable (just has to make sure to only apply when +// T != U) +template >> +bool operator==(const std::vector& a, const std::vector& b) { + return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](const auto& left, const auto& right) { return left == right; }); +} diff --git a/tests/framework/util/folder_manager.cpp b/tests/framework/util/folder_manager.cpp new file mode 100644 index 000000000..c406309ed --- /dev/null +++ b/tests/framework/util/folder_manager.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "folder_manager.h" + +#include +#include + +#include "gtest/gtest.h" + +#include "util/json_writer.h" +#include "util/env_var_wrapper.h" + +namespace fs { +Folder::Folder(std::filesystem::path root_path, std::string name) noexcept : folder((root_path / name).lexically_normal()) { + clear(); + // Don't actually create the folder yet, as we will do it on demand +} +Folder::~Folder() noexcept { clear(); } +Folder::Folder(Folder&& other) noexcept : actually_created(other.actually_created), folder(other.folder) { other.folder.clear(); } +Folder& Folder::operator=(Folder&& other) noexcept { + folder = other.folder; + actually_created = other.actually_created; + other.folder.clear(); + return *this; +} + +void Folder::check_if_first_use() { + if (!actually_created) { + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + if (!std::filesystem::create_directories(folder, err)) { + std::cerr << "Failed to create folder " << folder << " because " << err.message() << "\n"; + } + } + actually_created = true; + } +} + +std::filesystem::path Folder::write_manifest(std::filesystem::path const& name, std::string const& contents) { + check_if_first_use(); + std::filesystem::path out_path = (folder / name).lexically_normal(); + if (!::testing::internal::InDeathTestChild()) { + auto file = std::ofstream(out_path, std::ios_base::trunc | std::ios_base::out); + if (!file) { + std::cerr << "Failed to create manifest " << name << " at " << out_path << "\n"; + return out_path; + } + file << contents << std::endl; + } + insert_file_to_tracking(name); + return out_path; +} + +// close file handle, delete file, remove `name` from managed file list. +void Folder::remove(std::filesystem::path const& name) { + check_if_first_use(); + std::filesystem::path out_path = folder / name; + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + if (!std::filesystem::remove(out_path, err)) { + std::cerr << "Failed to remove file " << name << " at " << out_path << " because " << err.message() << "\n"; + } + } + + auto found = std::find(added_files.begin(), added_files.end(), name); + if (found != added_files.end()) { + added_files.erase(found); + } else { + std::cout << "File " << name << " not in tracked files of folder " << folder << ".\n"; + } +} + +// copy file into this folder +std::filesystem::path Folder::copy_file(std::filesystem::path const& file, std::filesystem::path const& new_name) { + check_if_first_use(); + insert_file_to_tracking(new_name); + + auto new_filepath = folder / new_name; + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + if (!std::filesystem::copy_file(file, new_filepath, err)) { + std::cerr << "Failed to copy file " << file << " to " << new_filepath << " because " << err.message() << "\n"; + } + } + return new_filepath; +} + +std::vector Folder::get_files() const { return added_files; } + +std::filesystem::path Folder::add_symlink(std::filesystem::path const& target, std::filesystem::path const& link_name) { + check_if_first_use(); + + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + std::filesystem::create_symlink(target, folder / link_name, err); + if (err.value() != 0) { + std::cerr << "Failed to create symlink with target" << target << " with name " << folder / link_name << " because " + << err.message() << "\n"; + } + } + insert_file_to_tracking(link_name); + return folder / link_name; +} +void Folder::insert_file_to_tracking(std::filesystem::path const& name) { + auto found = std::find(added_files.begin(), added_files.end(), name); + if (found != added_files.end()) { + std::cout << "Overwriting manifest " << name << ". Was this intended?\n"; + } else { + added_files.emplace_back(name); + } +} + +void Folder::clear() const noexcept { + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + std::filesystem::remove_all(folder, err); + if (err.value() != 0) { + std::cerr << "Failed to remove folder " << folder << " because " << err.message() << "\n"; + } + } +} + +std::filesystem::path category_path_name(ManifestCategory category) { + if (category == ManifestCategory::settings) return "settings.d"; + if (category == ManifestCategory::implicit_layer) return "implicit_layer.d"; + if (category == ManifestCategory::explicit_layer) + return "explicit_layer.d"; + else + return "icd.d"; +} + +FileSystemManager::FileSystemManager() + : root_folder(std::filesystem::path(TEST_EXECUTION_DIRECTORY), + std::string(::testing::UnitTest::GetInstance()->GetInstance()->current_test_suite()->name()) + "_" + + ::testing::UnitTest::GetInstance()->current_test_info()->name()) { + // Clean out test folder in case a previous run's files are still around + root_folder.clear(); + + auto const& root = root_folder.location(); + folders.try_emplace(ManifestLocation::null, root, std::string("null_dir")); + folders.try_emplace(ManifestLocation::driver, root, std::string("icd_manifests")); + folders.try_emplace(ManifestLocation::driver_env_var, root, std::string("icd_env_vars_manifests")); + folders.try_emplace(ManifestLocation::explicit_layer, root, std::string("explicit_layer_manifests")); + folders.try_emplace(ManifestLocation::explicit_layer_env_var, root, std::string("explicit_env_var_layer_folder")); + folders.try_emplace(ManifestLocation::explicit_layer_add_env_var, root, std::string("explicit_add_env_var_layer_folder")); + folders.try_emplace(ManifestLocation::implicit_layer, root, std::string("implicit_layer_manifests")); + folders.try_emplace(ManifestLocation::implicit_layer_env_var, root, std::string("implicit_env_var_layer_manifests")); + folders.try_emplace(ManifestLocation::implicit_layer_add_env_var, root, std::string("implicit_add_env_var_layer_manifests")); + folders.try_emplace(ManifestLocation::override_layer, root, std::string("override_layer_manifests")); +#if defined(_WIN32) + folders.try_emplace(ManifestLocation::windows_app_package, root, std::string("app_package_manifests")); +#endif +#if defined(__APPLE__) + folders.try_emplace(ManifestLocation::macos_bundle, root, std::string("macos_bundle")); +#endif + folders.try_emplace(ManifestLocation::settings_location, root, std::string("settings_location")); + folders.try_emplace(ManifestLocation::unsecured_driver, root, std::string("unsecured_driver")); + folders.try_emplace(ManifestLocation::unsecured_explicit_layer, root, std::string("unsecured_explicit_layer")); + folders.try_emplace(ManifestLocation::unsecured_implicit_layer, root, std::string("unsecured_implicit_layer")); + folders.try_emplace(ManifestLocation::unsecured_settings, root, std::string("unsecured_settings")); +} + +fs::Folder& FileSystemManager::get_folder(ManifestLocation location) noexcept { return folders.at(location); } +fs::Folder const& FileSystemManager::get_folder(ManifestLocation location) const noexcept { return folders.at(location); } + +Folder* FileSystemManager::get_folder_for_given_path(std::filesystem::path const& given_path) noexcept { + auto normalized_path = given_path.lexically_normal(); + for (auto const& [manifest_location, folder] : folders) { + if (folder.location() == normalized_path) { + return &folders.at(manifest_location); + } + } + if (redirected_paths.count(given_path.string()) > 0) { + return &folders.at(redirected_paths.at(normalized_path.string())); + } + for (auto const& [redirected_path_str, redirected_location] : redirected_paths) { + std::filesystem::path redirected_path = redirected_path_str; + const auto mismatch_pair = + std::mismatch(normalized_path.begin(), normalized_path.end(), redirected_path.begin(), redirected_path.end()); + if (mismatch_pair.second == redirected_path.end()) return &folders.at(redirected_location); + } + return nullptr; +} + +bool FileSystemManager::is_folder_path(std::filesystem::path const& path) const noexcept { + for (auto const& [location, folder] : folders) { + if (path == folder.location()) { + return true; + } + } + return false; +} + +void FileSystemManager::add_path_redirect(std::filesystem::path const& redirected_path, ManifestLocation location) { + redirected_paths[redirected_path.string()] = location; +} + +std::filesystem::path FileSystemManager::get_real_path_of_redirected_path(std::filesystem::path const& path) const { + if (redirected_paths.count(path.string()) > 0) { + return folders.at(redirected_paths.at(path.lexically_normal().string())).location(); + } + for (auto const& [redirected_path_str, redirected_location] : redirected_paths) { + std::filesystem::path redirected_path = redirected_path_str; + const auto mismatch_pair = std::mismatch(path.begin(), path.end(), redirected_path.begin(), redirected_path.end()); + if (mismatch_pair.second == redirected_path.end()) return folders.at(redirected_location).location(); + } + return std::filesystem::path{}; +} + +std::filesystem::path FileSystemManager::get_path_redirect_by_manifest_location(ManifestLocation location) const { + for (auto const& [path, redirected_location] : redirected_paths) { + if (redirected_location == location) { + return path; + } + } + return folders.at(ManifestLocation::null).location(); +} + +} // namespace fs diff --git a/tests/framework/util/folder_manager.h b/tests/framework/util/folder_manager.h new file mode 100644 index 000000000..68e69c211 --- /dev/null +++ b/tests/framework/util/folder_manager.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include "test_defines.h" + +namespace fs { + +class Folder { + public: + explicit Folder(std::filesystem::path root_path, std::string name) noexcept; + ~Folder() noexcept; + Folder(Folder const&) = delete; + Folder& operator=(Folder const&) = delete; + Folder(Folder&& other) noexcept; + Folder& operator=(Folder&& other) noexcept; + + // Add a manifest to the folder + std::filesystem::path write_manifest(std::filesystem::path const& name, std::string const& contents); + + // close file handle, delete file, remove `name` from managed file list. + void remove(std::filesystem::path const& name); + + // Remove all contents in the path + void clear() const noexcept; + + // copy file into this folder with name `new_name`. Returns the full path of the file that was copied + std::filesystem::path copy_file(std::filesystem::path const& file, std::filesystem::path const& new_name); + + // location of the managed folder + std::filesystem::path const& location() const { return folder; } + + std::vector get_files() const; + + // Create a symlink in this folder to target with the filename set to link_name + std::filesystem::path add_symlink(std::filesystem::path const& target, std::filesystem::path const& link_name); + + private: + bool actually_created = false; + std::filesystem::path folder; + std::vector added_files; + + void insert_file_to_tracking(std::filesystem::path const& name); + void check_if_first_use(); +}; + +class FileSystemManager { + fs::Folder root_folder; + std::unordered_map folders; + + // paths that this folder appears as to the loader (ex /usr/share/vulkan/explicit_layer.d is in the ManifestLocation::icd folder + std::unordered_map redirected_paths; + + public: + FileSystemManager(); + + Folder& get_folder(ManifestLocation location) noexcept; + Folder const& get_folder(ManifestLocation location) const noexcept; + + // Gets a pointer to the folder that given_path points to. This includes redirected paths as well as the exact path of folders + Folder* get_folder_for_given_path(std::filesystem::path const& given_path) noexcept; + + bool is_folder_path(std::filesystem::path const& path) const noexcept; + + void add_path_redirect(std::filesystem::path const& apparent_path, ManifestLocation location); + + // Returns the first redirected path pointing at by a manifest location. Returns the NULL location if one isn't found + std::filesystem::path get_path_redirect_by_manifest_location(ManifestLocation location) const; + + // Returns the real path that a redirected path points to. Returns an empty path if no redirect is found + std::filesystem::path get_real_path_of_redirected_path(std::filesystem::path const& redirected_path) const; +}; + +} // namespace fs diff --git a/tests/framework/util/functions.h b/tests/framework/util/functions.h new file mode 100644 index 000000000..5debba9fe --- /dev/null +++ b/tests/framework/util/functions.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include + +class FromVoidStarFunc { + void* function; + + public: + FromVoidStarFunc(void* function) : function(function) {} + FromVoidStarFunc(PFN_vkVoidFunction function) : function(reinterpret_cast(function)) {} + + template + operator T() { + return reinterpret_cast(function); + } +}; + +template +PFN_vkVoidFunction to_vkVoidFunction(T func) { + return reinterpret_cast(func); +} + +struct VulkanFunction { + std::string name; + PFN_vkVoidFunction function = nullptr; +}; diff --git a/tests/framework/util/get_executable_path.cpp b/tests/framework/util/get_executable_path.cpp new file mode 100644 index 000000000..9edb5d38d --- /dev/null +++ b/tests/framework/util/get_executable_path.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "get_executable_path.h" + +#if defined(__linux__) || defined(__GNU__) + +#include + +// find application path + name. Path cannot be longer than 1024, returns NULL if it is greater than that. +std::string test_platform_executable_path() { + std::string buffer; + buffer.resize(1024); + ssize_t count = readlink("/proc/self/exe", &buffer[0], buffer.size()); + if (count == -1) return NULL; + if (count == 0) return NULL; + buffer[count] = '\0'; + buffer.resize(count); + return buffer; +} +#elif defined(__APPLE__) + +#include +#include + +std::string test_platform_executable_path() { + std::string buffer; + buffer.resize(1024); + pid_t pid = getpid(); + int ret = proc_pidpath(pid, &buffer[0], static_cast(buffer.size())); + if (ret <= 0) return NULL; + buffer[ret] = '\0'; + buffer.resize(ret); + return buffer; +} +#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) +#include +std::string test_platform_executable_path() { + int mib[] = { + CTL_KERN, +#if defined(__NetBSD__) + KERN_PROC_ARGS, + -1, + KERN_PROC_PATHNAME, +#else + KERN_PROC, + KERN_PROC_PATHNAME, + -1, +#endif + }; + std::string buffer; + buffer.resize(1024); + size_t size = buffer.size(); + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), &buffer[0], &size, NULL, 0) < 0) { + return NULL; + } + buffer.resize(size); + + return buffer; +} +#elif defined(__Fuchsia__) || defined(__OpenBSD__) +std::string test_platform_executable_path() { return {}; } +#elif defined(__QNX__) + +#ifndef SYSCONFDIR +#define SYSCONFDIR "/etc" +#endif + +#include +#include + +std::string test_platform_executable_path() { + std::string buffer; + buffer.resize(1024); + int fd = open("/proc/self/exefile", O_RDONLY); + ssize_t rdsize; + + if (fd == -1) { + return NULL; + } + + rdsize = read(fd, &buffer[0], buffer.size()); + if (rdsize < 0) { + return NULL; + } + buffer[rdsize] = 0x00; + close(fd); + buffer.resize(rdsize); + + return buffer; +} +#endif // defined (__QNX__) + +#if defined(_WIN32) + +#include + +#include + +#include "wide_char_handling.h" + +std::string test_platform_executable_path() { + std::string buffer; + buffer.resize(1024); + DWORD ret = GetModuleFileName(NULL, static_cast(&buffer[0]), (DWORD)buffer.size()); + if (ret == 0) return NULL; + if (ret > buffer.size()) return NULL; + buffer.resize(ret); + buffer[ret] = '\0'; + return narrow(std::filesystem::path(buffer).native()); +} + +#endif diff --git a/tests/framework/util/get_executable_path.h b/tests/framework/util/get_executable_path.h new file mode 100644 index 000000000..bb296e54f --- /dev/null +++ b/tests/framework/util/get_executable_path.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +// find application path + name. Path cannot be longer than 1024, returns NULL if it is greater than that. +std::string test_platform_executable_path(); diff --git a/tests/framework/util/json_writer.cpp b/tests/framework/util/json_writer.cpp new file mode 100644 index 000000000..1a955c09a --- /dev/null +++ b/tests/framework/util/json_writer.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 The Khronos Group Inc. + * Copyright (c) 2023 Valve Corporation + * Copyright (c) 2023 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + * Author: Charles Giessen + */ + +#include "json_writer.h" + +#include "wide_char_handling.h" + +void JsonWriter::StartObject() { + CommaAndNewLine(); + Indent(); + output += "{"; + stack.push(false); +} +void JsonWriter::StartKeyedObject(std::string const& key) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": {"; + stack.push(false); +} +void JsonWriter::EndObject() { + stack.pop(); + output += "\n"; + Indent(); + output += "}"; +} +void JsonWriter::StartKeyedArray(std::string const& key) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": ["; + stack.push(false); +} +void JsonWriter::StartArray() { + CommaAndNewLine(); + Indent(); + output += "["; + stack.push(false); +} +void JsonWriter::EndArray() { + stack.pop(); + output += "\n"; + Indent(); + output += "]"; +} + +void JsonWriter::AddKeyedString(std::string const& key, std::string const& value) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": \"" + escape(value) + "\""; +} +void JsonWriter::AddString(std::string const& value) { + CommaAndNewLine(); + Indent(); + output += "\"" + escape(value) + "\""; +} +#if defined(_WIN32) +void JsonWriter::AddKeyedString(std::string const& key, std::wstring const& value) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": \"" + escape(narrow(value)) + "\""; +} +void JsonWriter::AddString(std::wstring const& value) { + CommaAndNewLine(); + Indent(); + output += "\"" + escape(narrow(value)) + "\""; +} +#endif + +void JsonWriter::AddKeyedBool(std::string const& key, bool value) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": " + (value ? "true" : "false"); +} +void JsonWriter::AddBool(bool value) { + CommaAndNewLine(); + Indent(); + output += std::string(value ? "true" : "false"); +} + +void JsonWriter::AddKeyedNumber(std::string const& key, double number) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": " + std::to_string(number); +} +void JsonWriter::AddNumber(double number) { + CommaAndNewLine(); + Indent(); + output += std::to_string(number); +} + +void JsonWriter::AddKeyedInteger(std::string const& key, int64_t number) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": " + std::to_string(number); +} +void JsonWriter::AddInteger(int64_t number) { + CommaAndNewLine(); + Indent(); + output += std::to_string(number); +} + +// Json doesn't allow `\` in strings, it must be escaped. Thus we have to convert '\\' to '\\\\' in strings +std::string JsonWriter::escape(std::string const& in_path) { + std::string out; + for (auto& c : in_path) { + if (c == '\\') + out += "\\\\"; + else + out += c; + } + return out; +} +std::string JsonWriter::escape(std::filesystem::path const& in_path) { return escape(narrow(in_path.native())); } + +void JsonWriter::CommaAndNewLine() { + if (stack.size() > 0) { + if (stack.top() == false) { + stack.top() = true; + } else { + output += ","; + } + output += "\n"; + } +} +void JsonWriter::Indent() { + for (uint32_t i = 0; i < stack.size(); i++) { + output += '\t'; + } +} diff --git a/tests/framework/util/json_writer.h b/tests/framework/util/json_writer.h new file mode 100644 index 000000000..a5c31545a --- /dev/null +++ b/tests/framework/util/json_writer.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 The Khronos Group Inc. + * Copyright (c) 2023 Valve Corporation + * Copyright (c) 2023 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + * Author: Charles Giessen + */ + +#pragma once + +#include +#include +#include +#include + +#include "wide_char_handling.h" + +// Utility class to simplify the writing of JSON manifest files + +struct JsonWriter { + std::string output; + + // the bool represents whether an object has been written & a comma is needed + std::stack stack; + + void StartObject(); + void StartKeyedObject(std::string const& key); + void EndObject(); + void StartKeyedArray(std::string const& key); + void StartArray(); + void EndArray(); + + void AddKeyedString(std::string const& key, std::string const& value); + void AddString(std::string const& value); +#if defined(_WIN32) + void AddKeyedString(std::string const& key, std::wstring const& value); + void AddString(std::wstring const& value); +#endif + + void AddKeyedBool(std::string const& key, bool value); + void AddBool(bool value); + + void AddKeyedNumber(std::string const& key, double number); + void AddNumber(double number); + + void AddKeyedInteger(std::string const& key, int64_t number); + void AddInteger(int64_t number); + + // Json doesn't allow `\` in strings, it must be escaped. Thus we have to convert '\\' to '\\\\' in strings + static std::string escape(std::string const& in_path); + static std::string escape(std::filesystem::path const& in_path); + + private: + void CommaAndNewLine(); + void Indent(); +}; diff --git a/tests/framework/util/manifest_builders.cpp b/tests/framework/util/manifest_builders.cpp new file mode 100644 index 000000000..6666a5769 --- /dev/null +++ b/tests/framework/util/manifest_builders.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "manifest_builders.h" + +#include "json_writer.h" + +template +void print_object_of_t(JsonWriter& writer, const char* object_name, std::vector const& vec) { + if (vec.size() == 0) return; + writer.StartKeyedObject(object_name); + for (auto& element : vec) { + element.get_manifest_str(writer); + } + writer.EndObject(); +} + +template +void print_array_of_t(JsonWriter& writer, const char* object_name, std::vector const& vec) { + if (vec.size() == 0) return; + writer.StartKeyedArray(object_name); + for (auto& element : vec) { + element.get_manifest_str(writer); + } + writer.EndArray(); +} +void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector const& strings) { + if (strings.size() == 0) return; + writer.StartKeyedArray(object_name); + for (auto const& str : strings) { + writer.AddString(std::filesystem::path(str).native()); + } + writer.EndArray(); +} +void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector const& paths) { + if (paths.size() == 0) return; + writer.StartKeyedArray(object_name); + for (auto const& path : paths) { + writer.AddString(path.native()); + } + writer.EndArray(); +} + +std::string version_to_string(uint32_t version) { + std::string out = std::to_string(VK_API_VERSION_MAJOR(version)) + "." + std::to_string(VK_API_VERSION_MINOR(version)) + "." + + std::to_string(VK_API_VERSION_PATCH(version)); + if (VK_API_VERSION_VARIANT(version) != 0) out += std::to_string(VK_API_VERSION_VARIANT(version)) + "." + out; + return out; +} + +std::string to_text(bool b) { return b ? std::string("true") : std::string("false"); } + +std::string ManifestICD::get_manifest_str() const { + JsonWriter writer; + writer.StartObject(); + writer.AddKeyedString("file_format_version", file_format_version.get_version_str()); + writer.StartKeyedObject("ICD"); + writer.AddKeyedString("library_path", lib_path.native()); + writer.AddKeyedString("api_version", version_to_string(api_version)); + writer.AddKeyedBool("is_portability_driver", is_portability_driver); + if (!library_arch.empty()) writer.AddKeyedString("library_arch", library_arch); + writer.EndObject(); + writer.EndObject(); + return writer.output; +} + +void ManifestLayer::LayerDescription::FunctionOverride::get_manifest_str(JsonWriter& writer) const { + writer.AddKeyedString(vk_func, override_name); +} + +void ManifestLayer::LayerDescription::Extension::get_manifest_str(JsonWriter& writer) const { + writer.StartObject(); + writer.AddKeyedString("name", name); + writer.AddKeyedString("spec_version", std::to_string(spec_version)); + writer.AddKeyedString("spec_version", std::to_string(spec_version)); + print_vector_of_strings(writer, "entrypoints", entrypoints); + writer.EndObject(); +} + +void ManifestLayer::LayerDescription::get_manifest_str(JsonWriter& writer) const { + writer.AddKeyedString("name", name); + writer.AddKeyedString("type", get_type_str(type)); + if (!lib_path.empty()) { + writer.AddKeyedString("library_path", lib_path.native()); + } + writer.AddKeyedString("api_version", version_to_string(api_version)); + writer.AddKeyedString("implementation_version", std::to_string(implementation_version)); + writer.AddKeyedString("description", description); + print_object_of_t(writer, "functions", functions); + print_array_of_t(writer, "instance_extensions", instance_extensions); + print_array_of_t(writer, "device_extensions", device_extensions); + if (!enable_environment.empty()) { + writer.StartKeyedObject("enable_environment"); + writer.AddKeyedString(enable_environment, "1"); + writer.EndObject(); + } + if (!disable_environment.empty()) { + writer.StartKeyedObject("disable_environment"); + writer.AddKeyedString(disable_environment, "1"); + writer.EndObject(); + } + print_vector_of_strings(writer, "component_layers", component_layers); + print_vector_of_strings(writer, "blacklisted_layers", blacklisted_layers); + print_vector_of_strings(writer, "override_paths", override_paths); + print_vector_of_strings(writer, "app_keys", app_keys); + print_object_of_t(writer, "pre_instance_functions", pre_instance_functions); + if (!library_arch.empty()) { + writer.AddKeyedString("library_arch", library_arch); + } +} + +VkLayerProperties ManifestLayer::LayerDescription::get_layer_properties() const { + VkLayerProperties properties{}; + name.copy(properties.layerName, VK_MAX_EXTENSION_NAME_SIZE); + description.copy(properties.description, VK_MAX_EXTENSION_NAME_SIZE); + properties.implementationVersion = implementation_version; + properties.specVersion = api_version; + return properties; +} + +std::string ManifestLayer::get_manifest_str() const { + JsonWriter writer; + writer.StartObject(); + writer.AddKeyedString("file_format_version", file_format_version.get_version_str()); + if (layers.size() == 1) { + writer.StartKeyedObject("layer"); + layers.at(0).get_manifest_str(writer); + writer.EndObject(); + } else { + writer.StartKeyedArray("layers"); + for (size_t i = 0; i < layers.size(); i++) { + writer.StartObject(); + layers.at(i).get_manifest_str(writer); + writer.EndObject(); + } + writer.EndArray(); + } + writer.EndObject(); + return writer.output; +} diff --git a/tests/framework/util/manifest_builders.h b/tests/framework/util/manifest_builders.h new file mode 100644 index 000000000..bc559dda7 --- /dev/null +++ b/tests/framework/util/manifest_builders.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include +#include +#include + +#include + +#include "builder_defines.h" + +// Forward declaration since we take it by reference only +class JsonWriter; + +struct ManifestVersion { + BUILDER_VALUE_WITH_DEFAULT(uint32_t, major, 1) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, minor, 0) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, patch, 0) + + std::string get_version_str() const noexcept { + return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch); + } +}; + +// ManifestICD builder +struct ManifestICD { + BUILDER_VALUE(ManifestVersion, file_format_version) + BUILDER_VALUE(uint32_t, api_version) + BUILDER_VALUE(std::filesystem::path, lib_path) + BUILDER_VALUE(bool, is_portability_driver) + BUILDER_VALUE(std::string, library_arch) + std::string get_manifest_str() const; +}; + +// ManifestLayer builder +struct ManifestLayer { + struct LayerDescription { + enum class Type { INSTANCE, GLOBAL, DEVICE }; + std::string get_type_str(Type layer_type) const { + if (layer_type == Type::GLOBAL) + return "GLOBAL"; + else if (layer_type == Type::DEVICE) + return "DEVICE"; + else // default + return "INSTANCE"; + } + struct FunctionOverride { + BUILDER_VALUE(std::string, vk_func) + BUILDER_VALUE(std::string, override_name) + + void get_manifest_str(JsonWriter& writer) const; + }; + struct Extension { + Extension() noexcept {} + Extension(std::string name, uint32_t spec_version = 0, std::vector entrypoints = {}) noexcept + : name(name), spec_version(spec_version), entrypoints(entrypoints) {} + std::string name; + uint32_t spec_version = 0; + std::vector entrypoints; + void get_manifest_str(JsonWriter& writer) const; + }; + BUILDER_VALUE(std::string, name) + BUILDER_VALUE_WITH_DEFAULT(Type, type, Type::INSTANCE) + BUILDER_VALUE(std::filesystem::path, lib_path) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, api_version, VK_API_VERSION_1_0) + BUILDER_VALUE(uint32_t, implementation_version) + BUILDER_VALUE(std::string, description) + BUILDER_VECTOR(FunctionOverride, functions, function) + BUILDER_VECTOR(Extension, instance_extensions, instance_extension) + BUILDER_VECTOR(Extension, device_extensions, device_extension) + BUILDER_VALUE(std::string, enable_environment) + BUILDER_VALUE(std::string, disable_environment) + BUILDER_VECTOR(std::string, component_layers, component_layer) + BUILDER_VECTOR(std::string, blacklisted_layers, blacklisted_layer) + BUILDER_VECTOR(std::filesystem::path, override_paths, override_path) + BUILDER_VECTOR(FunctionOverride, pre_instance_functions, pre_instance_function) + BUILDER_VECTOR(std::string, app_keys, app_key) + BUILDER_VALUE(std::string, library_arch) + + void get_manifest_str(JsonWriter& writer) const; + VkLayerProperties get_layer_properties() const; + }; + BUILDER_VALUE(ManifestVersion, file_format_version) + BUILDER_VECTOR(LayerDescription, layers, layer) + + std::string get_manifest_str() const; +}; diff --git a/tests/framework/util/platform_wsi.cpp b/tests/framework/util/platform_wsi.cpp new file mode 100644 index 000000000..748abdcf2 --- /dev/null +++ b/tests/framework/util/platform_wsi.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "platform_wsi.h" + +#include "equality_helpers.h" + +const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection) { +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + return "VK_KHR_android_surface"; +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + return "VK_EXT_directfb_surface"; +#elif defined(VK_USE_PLATFORM_FUCHSIA) + return "VK_FUCHSIA_imagepipe_surface"; +#elif defined(VK_USE_PLATFORM_GGP) + return "VK_GGP_stream_descriptor_surface"; +#elif defined(VK_USE_PLATFORM_IOS_MVK) + return "VK_MVK_ios_surface"; +#elif defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT) +#if defined(VK_USE_PLATFORM_MACOS_MVK) + if (string_eq(api_selection, "VK_USE_PLATFORM_MACOS_MVK")) return "VK_MVK_macos_surface"; +#endif +#if defined(VK_USE_PLATFORM_METAL_EXT) + if (string_eq(api_selection, "VK_USE_PLATFORM_METAL_EXT")) return "VK_EXT_metal_surface"; + return "VK_EXT_metal_surface"; +#endif +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + return "VK_QNX_screen_surface"; +#elif defined(VK_USE_PLATFORM_VI_NN) + return "VK_NN_vi_surface"; +#elif defined(VK_USE_PLATFORM_XCB_KHR) || defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) +#if defined(VK_USE_PLATFORM_XCB_KHR) + if (string_eq(api_selection, "VK_USE_PLATFORM_XCB_KHR")) return "VK_KHR_xcb_surface"; +#endif +#if defined(VK_USE_PLATFORM_XLIB_KHR) + if (string_eq(api_selection, "VK_USE_PLATFORM_XLIB_KHR")) return "VK_KHR_xlib_surface"; +#endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (string_eq(api_selection, "VK_USE_PLATFORM_WAYLAND_KHR")) return "VK_KHR_wayland_surface"; +#endif +#if defined(VK_USE_PLATFORM_XCB_KHR) + return "VK_KHR_xcb_surface"; +#endif +#elif defined(VK_USE_PLATFORM_WIN32_KHR) + return "VK_KHR_win32_surface"; +#else + return "VK_KHR_display"; +#endif +} diff --git a/tests/framework/util/platform_wsi.h b/tests/framework/util/platform_wsi.h new file mode 100644 index 000000000..66b7b507e --- /dev/null +++ b/tests/framework/util/platform_wsi.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection); diff --git a/tests/framework/util/test_defines.h b/tests/framework/util/test_defines.h new file mode 100644 index 000000000..63d6a32e9 --- /dev/null +++ b/tests/framework/util/test_defines.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define FRAMEWORK_EXPORT __attribute__((visibility("default"))) +#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) +#define FRAMEWORK_EXPORT __attribute__((visibility("default"))) +#elif defined(_WIN32) +#define FRAMEWORK_EXPORT __declspec(dllexport) +#else +#define FRAMEWORK_EXPORT +#endif + +// Set of platforms with a common set of functionality which is queried throughout the program +#if defined(__linux__) || defined(__APPLE__) || defined(__Fuchsia__) || defined(__QNX__) || defined(__FreeBSD__) || \ + defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__GNU__) +#define TESTING_COMMON_UNIX_PLATFORMS 1 +#else +#define TESTING_COMMON_UNIX_PLATFORMS 0 +#endif + +#include FRAMEWORK_CONFIG_HEADER + +enum class ManifestCategory { implicit_layer, explicit_layer, icd, settings }; + +// Controls whether to create a manifest and where to put it +enum class ManifestDiscoveryType { + generic, // put the manifest in the regular locations + unsecured_generic, // put the manifest in a user folder rather than system + none, // Do not write the manifest anywhere (for Direct Driver Loading) + null_dir, // put the manifest in the 'null_dir' which the loader does not search in (D3DKMT for instance) + env_var, // use the corresponding env-var for it + add_env_var, // use the corresponding add-env-var for it + override_folder, // add to a special folder for the override layer to use +#if defined(_WIN32) + windows_app_package, // let the app package search find it +#endif +#if defined(__APPLE__) + macos_bundle, // place it in a location only accessible to macos bundles +#endif +}; + +enum class LibraryPathType { + absolute, // default for testing - the exact path of the binary + relative, // Relative to the manifest file + default_search_paths, // Dont add any path information to the library_path - force the use of the default search paths +}; + +// Locations that files can go in the test framework +enum class ManifestLocation { + null, + driver, + driver_env_var, + explicit_layer, + explicit_layer_env_var, + explicit_layer_add_env_var, + implicit_layer, + implicit_layer_env_var, + implicit_layer_add_env_var, + override_layer, +#if defined(_WIN32) + windows_app_package, +#endif +#if defined(__APPLE__) + macos_bundle, +#endif + settings_location, + unsecured_driver, + unsecured_explicit_layer, + unsecured_implicit_layer, + unsecured_settings, +}; diff --git a/tests/framework/util/vulkan_object_wrappers.cpp b/tests/framework/util/vulkan_object_wrappers.cpp new file mode 100644 index 000000000..0223ff68f --- /dev/null +++ b/tests/framework/util/vulkan_object_wrappers.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "vulkan_object_wrappers.h" + +#include "platform_wsi.h" + +VkExtensionProperties Extension::get() const noexcept { + VkExtensionProperties props{}; + extensionName.copy(props.extensionName, VK_MAX_EXTENSION_NAME_SIZE); + props.specVersion = specVersion; + return props; +} + +InstanceCreateInfo::InstanceCreateInfo() { + instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; +} + +VkInstanceCreateInfo* InstanceCreateInfo::get() noexcept { + if (fill_in_application_info) { + application_info.pApplicationName = app_name.c_str(); + application_info.pEngineName = engine_name.c_str(); + application_info.applicationVersion = app_version; + application_info.engineVersion = engine_version; + application_info.apiVersion = api_version; + instance_info.pApplicationInfo = &application_info; + } + instance_info.flags = flags; + instance_info.enabledLayerCount = static_cast(enabled_layers.size()); + instance_info.ppEnabledLayerNames = enabled_layers.data(); + instance_info.enabledExtensionCount = static_cast(enabled_extensions.size()); + instance_info.ppEnabledExtensionNames = enabled_extensions.data(); + return &instance_info; +} +InstanceCreateInfo& InstanceCreateInfo::set_api_version(uint32_t major, uint32_t minor, uint32_t patch) { + this->api_version = VK_MAKE_API_VERSION(0, major, minor, patch); + return *this; +} +InstanceCreateInfo& InstanceCreateInfo::setup_WSI(const char* api_selection) { + add_extensions({"VK_KHR_surface", get_platform_wsi_extension(api_selection)}); + return *this; +} + +DeviceQueueCreateInfo::DeviceQueueCreateInfo() { queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; } +DeviceQueueCreateInfo::DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info) { + queue_create_info = *create_info; + for (uint32_t i = 0; i < create_info->queueCount; i++) { + priorities.push_back(create_info->pQueuePriorities[i]); + } +} + +VkDeviceQueueCreateInfo DeviceQueueCreateInfo::get() noexcept { + queue_create_info.pQueuePriorities = priorities.data(); + queue_create_info.queueCount = 1; + queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + return queue_create_info; +} + +DeviceCreateInfo::DeviceCreateInfo(const VkDeviceCreateInfo* create_info) { + dev = *create_info; + for (uint32_t i = 0; i < create_info->enabledExtensionCount; i++) { + enabled_extensions.push_back(create_info->ppEnabledExtensionNames[i]); + } + for (uint32_t i = 0; i < create_info->enabledLayerCount; i++) { + enabled_layers.push_back(create_info->ppEnabledLayerNames[i]); + } + for (uint32_t i = 0; i < create_info->queueCreateInfoCount; i++) { + device_queue_infos.push_back(create_info->pQueueCreateInfos[i]); + } +} + +VkDeviceCreateInfo* DeviceCreateInfo::get() noexcept { + dev.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + dev.enabledLayerCount = static_cast(enabled_layers.size()); + dev.ppEnabledLayerNames = enabled_layers.data(); + dev.enabledExtensionCount = static_cast(enabled_extensions.size()); + dev.ppEnabledExtensionNames = enabled_extensions.data(); + uint32_t index = 0; + for (auto& queue : queue_info_details) { + queue.queue_create_info.queueFamilyIndex = index++; + queue.queue_create_info.queueCount = 1; + device_queue_infos.push_back(queue.get()); + } + + dev.queueCreateInfoCount = static_cast(device_queue_infos.size()); + dev.pQueueCreateInfos = device_queue_infos.data(); + return &dev; +} diff --git a/tests/framework/util/vulkan_object_wrappers.h b/tests/framework/util/vulkan_object_wrappers.h new file mode 100644 index 000000000..7cc923515 --- /dev/null +++ b/tests/framework/util/vulkan_object_wrappers.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include +#include + +#include + +#include "builder_defines.h" + +struct Extension { + BUILDER_VALUE(std::string, extensionName) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, specVersion, VK_API_VERSION_1_0) + + Extension(const char* name, uint32_t specVersion = VK_API_VERSION_1_0) noexcept + : extensionName(name), specVersion(specVersion) {} + Extension(std::string extensionName, uint32_t specVersion = VK_API_VERSION_1_0) noexcept + : extensionName(extensionName), specVersion(specVersion) {} + + VkExtensionProperties get() const noexcept; +}; + +struct MockQueueFamilyProperties { + BUILDER_VALUE(VkQueueFamilyProperties, properties) + BUILDER_VALUE(bool, support_present) + + VkQueueFamilyProperties get() const noexcept { return properties; } +}; + +struct InstanceCreateInfo { + BUILDER_VALUE(VkInstanceCreateInfo, instance_info) + BUILDER_VALUE(VkApplicationInfo, application_info) + BUILDER_VALUE(std::string, app_name) + BUILDER_VALUE(std::string, engine_name) + BUILDER_VALUE(uint32_t, flags) + BUILDER_VALUE(uint32_t, app_version) + BUILDER_VALUE(uint32_t, engine_version) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, api_version, VK_API_VERSION_1_0) + BUILDER_VECTOR(const char*, enabled_layers, layer) + BUILDER_VECTOR(const char*, enabled_extensions, extension) + // tell the get() function to not provide `application_info` + BUILDER_VALUE_WITH_DEFAULT(bool, fill_in_application_info, true) + + InstanceCreateInfo(); + + VkInstanceCreateInfo* get() noexcept; + + InstanceCreateInfo& set_api_version(uint32_t major, uint32_t minor, uint32_t patch); + + InstanceCreateInfo& setup_WSI(const char* api_selection = nullptr); +}; + +struct DeviceQueueCreateInfo { + DeviceQueueCreateInfo(); + DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info); + + BUILDER_VALUE(VkDeviceQueueCreateInfo, queue_create_info) + BUILDER_VECTOR(float, priorities, priority) + + VkDeviceQueueCreateInfo get() noexcept; +}; + +struct DeviceCreateInfo { + DeviceCreateInfo() = default; + DeviceCreateInfo(const VkDeviceCreateInfo* create_info); + + BUILDER_VALUE(VkDeviceCreateInfo, dev) + BUILDER_VECTOR(const char*, enabled_extensions, extension) + BUILDER_VECTOR(const char*, enabled_layers, layer) + BUILDER_VECTOR(DeviceQueueCreateInfo, queue_info_details, device_queue) + + VkDeviceCreateInfo* get() noexcept; + + private: + std::vector device_queue_infos; +}; diff --git a/tests/framework/util/wide_char_handling.cpp b/tests/framework/util/wide_char_handling.cpp new file mode 100644 index 000000000..ac4fb0bff --- /dev/null +++ b/tests/framework/util/wide_char_handling.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "wide_char_handling.h" + +#if defined(_WIN32) + +#include "Windows.h" + +std::string narrow(const std::wstring& utf16) { + if (utf16.empty()) { + return {}; + } + int size = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), nullptr, 0, nullptr, nullptr); + if (size <= 0) { + return {}; + } + std::string utf8(size, '\0'); + if (WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), &utf8[0], size, nullptr, nullptr) != size) { + return {}; + } + return utf8; +} + +std::wstring widen(const std::string& utf8) { + if (utf8.empty()) { + return {}; + } + int size = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), nullptr, 0); + if (size <= 0) { + return {}; + } + std::wstring utf16(size, L'\0'); + if (MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), &utf16[0], size) != size) { + return {}; + } + return utf16; +} +#else +std::string narrow(const std::string& utf16) { return utf16; } +std::string widen(const std::string& utf8) { return utf8; } +#endif diff --git a/tests/framework/util/wide_char_handling.h b/tests/framework/util/wide_char_handling.h new file mode 100644 index 000000000..805afe14c --- /dev/null +++ b/tests/framework/util/wide_char_handling.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +// Define it here so that json_writer.h has access to these functions +#if defined(_WIN32) +// Convert an UTF-16 wstring to an UTF-8 string +std::string narrow(const std::wstring& utf16); +// Convert an UTF-8 string to an UTF-16 wstring +std::wstring widen(const std::string& utf8); +#else +// Do nothing passthrough for the sake of Windows & UTF-16 +std::string narrow(const std::string& utf16); +// Do nothing passthrough for the sake of Windows & UTF-16 +std::string widen(const std::string& utf8); +#endif diff --git a/tests/live_verification/dynamic_loader_behavior/dynamic_library.h b/tests/live_verification/dynamic_loader_behavior/dynamic_library.h index 1d88d306e..b4bff319b 100644 --- a/tests/live_verification/dynamic_loader_behavior/dynamic_library.h +++ b/tests/live_verification/dynamic_loader_behavior/dynamic_library.h @@ -25,7 +25,12 @@ * Author: Charles Giessen */ -#include "test_util.h" +#include +#include + +#include "util/test_defines.h" +#include "util/functions.h" +#include "util/dynamic_library_wrapper.h" extern "C" { @@ -38,7 +43,7 @@ using InitFunction = void (*)(); FRAMEWORK_EXPORT void init(); }; -#if defined(WIN32) +#if defined(_WIN32) #if !defined(LIB_EXT) #define LIB_EXT "dll" #endif diff --git a/tests/live_verification/dynamic_loader_behavior/test_dynamic_linking.cpp b/tests/live_verification/dynamic_loader_behavior/test_dynamic_linking.cpp index 2d2d6b35c..943917ec3 100644 --- a/tests/live_verification/dynamic_loader_behavior/test_dynamic_linking.cpp +++ b/tests/live_verification/dynamic_loader_behavior/test_dynamic_linking.cpp @@ -42,4 +42,4 @@ int main() { std::cout << "Success\n"; #endif return 0; -} \ No newline at end of file +} diff --git a/tests/loader_alloc_callback_tests.cpp b/tests/loader_alloc_callback_tests.cpp index 1701bfb92..f9eba0f2d 100644 --- a/tests/loader_alloc_callback_tests.cpp +++ b/tests/loader_alloc_callback_tests.cpp @@ -440,7 +440,9 @@ TEST(Allocation, CreateInstanceIntentionalAllocFailInvalidManifests) { auto file_name = std::string("invalid_implicit_layer_") + std::to_string(i) + ".json"; std::filesystem::path new_path = env.get_folder(ManifestLocation::implicit_layer).write_manifest(file_name, invalid_jsons[i]); - env.platform_shim->add_manifest(ManifestCategory::implicit_layer, new_path); +#if defined(WIN32) + env.platform_shim->add_manifest_to_registry(ManifestCategory::implicit_layer, new_path); +#endif } const char* layer_name = "VkLayerImplicit0"; diff --git a/tests/loader_envvar_tests.cpp b/tests/loader_envvar_tests.cpp index c94c2ecdd..85a23ddf2 100644 --- a/tests/loader_envvar_tests.cpp +++ b/tests/loader_envvar_tests.cpp @@ -28,6 +28,8 @@ #include "test_environment.h" +#include "util/wide_char_handling.h" + // Don't support vk_icdNegotiateLoaderICDInterfaceVersion // Loader calls vk_icdGetInstanceProcAddr second // does not support vk_icdGetInstanceProcAddr @@ -120,18 +122,25 @@ TEST(EnvVarICDOverrideSetup, TestOnlyDriverEnvVar) { phys_dev_count = 5; ASSERT_EQ(inst2->vkEnumeratePhysicalDevices(inst2.inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); ASSERT_EQ(phys_dev_count, 5U); +} - env.debug_log.clear(); +// Test VK_DRIVER_FILES environment variable with elelvated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyDriverEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::env_var)); + env.get_test_icd(0).add_physical_device("pd0"); - env.platform_shim->set_elevated_privilege(true); + for (uint32_t add = 0; add < 2; ++add) { + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::env_var)) + .add_physical_device("pd" + std::to_string(add) + "0") + .add_physical_device("pd" + std::to_string(add) + "1"); + } - InstWrapper inst3{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst3.create_info, env.debug_log); - inst3.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); EXPECT_TRUE(env.debug_log.find("vkCreateInstance: Found no drivers!")); - - env.platform_shim->set_elevated_privilege(false); } // Test VK_DRIVER_FILES environment variable containing a path to a folder @@ -157,27 +166,30 @@ TEST(EnvVarICDOverrideSetup, TestOnlyDriverEnvVarInFolder) { .add_physical_device("pd" + std::to_string(add) + "1"); } - env.debug_log.clear(); - InstWrapper inst2{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); inst2.CheckCreate(); phys_dev_count = 5; ASSERT_EQ(inst2->vkEnumeratePhysicalDevices(inst2.inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); ASSERT_EQ(phys_dev_count, 5U); +} +// Test VK_DRIVER_FILES environment variable containing a path to a folder with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyDriverEnvVarInFolderWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::env_var).set_is_dir(false)); + env.get_test_icd(0).add_physical_device("pd0"); - env.debug_log.clear(); - - env.platform_shim->set_elevated_privilege(true); + for (uint32_t add = 0; add < 2; ++add) { + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::env_var)) + .add_physical_device("pd" + std::to_string(add) + "0") + .add_physical_device("pd" + std::to_string(add) + "1"); + } - InstWrapper inst3{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst3.create_info, env.debug_log); - inst3.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); EXPECT_TRUE(env.debug_log.find("vkCreateInstance: Found no drivers!")); - - env.platform_shim->set_elevated_privilege(false); } #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) || defined(__QNX__) @@ -203,14 +215,13 @@ TEST(EnvVarICDOverrideSetup, XDG) { // Set up a layer path that includes default and user-specified locations, // so that the test app can find them. Include some badly specified elements as well. // Need to redirect the 'home' directory - std::filesystem::path HOME = "/home/fake_home"; - EnvVarWrapper home_env_var{"HOME", HOME}; - EnvVarWrapper xdg_config_dirs_env_var{"XDG_CONFIG_DIRS", ":/tmp/goober:::::/tmp/goober/::::"}; - EnvVarWrapper xdg_config_home_env_var{"XDG_CONFIG_HOME", ":/tmp/goober:::::/tmp/goober2/::::"}; - EnvVarWrapper xdg_data_dirs_env_var{"XDG_DATA_DIRS", "::::/tmp/goober3:/tmp/goober4/with spaces:::"}; - EnvVarWrapper xdg_data_home_env_var{"XDG_DATA_HOME", "::::/tmp/goober3:/tmp/goober4/with spaces:::"}; - FrameworkEnvironment env{}; + FrameworkEnvironment env{FrameworkSettings{} + .set_home_env_var("/home/fake_home") + .set_xdg_config_dirs_env_var(":/tmp/goober:::::/tmp/goober/::::") + .set_xdg_config_home_env_var(":/tmp/goober:::::/tmp/goober2/::::") + .set_xdg_data_dirs_env_var("::::/tmp/goober3:/tmp/goober4/with spaces:::") + .set_xdg_data_home_env_var("::::/tmp/goober3:/tmp/goober4/with spaces:::")}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); InstWrapper inst{env.vulkan_functions}; @@ -232,9 +243,7 @@ TEST(EnvVarICDOverrideSetup, XDGContainsJsonFile) { // Set up a layer path that includes default and user-specified locations, // so that the test app can find them. Include some badly specified elements as well. // Need to redirect the 'home' directory - EnvVarWrapper xdg_config_dirs_env_var{"XDG_CONFIG_DIRS", "bad_file.json"}; - - FrameworkEnvironment env{}; + FrameworkEnvironment env{FrameworkSettings{}.set_xdg_config_dirs_env_var("bad_file.json")}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); InstWrapper inst{env.vulkan_functions}; @@ -249,24 +258,27 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddDriverEnvVar) { env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::add_env_var)); env.get_test_icd(0).physical_devices.emplace_back("pd0"); - InstWrapper inst1{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); - inst1.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); std::array phys_devs_array; uint32_t phys_dev_count = 1; - ASSERT_EQ(inst1->vkEnumeratePhysicalDevices(inst1.inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); + ASSERT_EQ(inst->vkEnumeratePhysicalDevices(inst.inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); ASSERT_EQ(phys_dev_count, 1U); +} - env.platform_shim->set_elevated_privilege(true); +// Test VK_ADD_DRIVER_FILES environment variable with elelvated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyAddDriverEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::add_env_var)); + env.get_test_icd(0).physical_devices.emplace_back("pd0"); - InstWrapper inst2{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); EXPECT_TRUE(env.debug_log.find("vkCreateInstance: Found no drivers!")); - - env.platform_shim->set_elevated_privilege(false); } // Test Both VK_DRIVER_FILES and VK_ADD_DRIVER_FILES environment variable @@ -295,7 +307,7 @@ TEST(EnvVarICDOverrideSetup, TestBothDriverEnvVars) { TEST(EnvVarICDOverrideSetup, TestOnlyLayerEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); - env.platform_shim->redirect_path("/tmp/carol", env.get_folder(ManifestLocation::explicit_layer_env_var).location()); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::explicit_layer_env_var); const char* layer_name = "TestLayer"; env.add_explicit_layer( @@ -312,35 +324,51 @@ TEST(EnvVarICDOverrideSetup, TestOnlyLayerEnvVar) { std::string vk_layer_path = ":/tmp/carol::::/:"; vk_layer_path += (HOME / "/ with spaces/:::::/tandy:"); EnvVarWrapper layer_path_env_var{"VK_LAYER_PATH", vk_layer_path}; - InstWrapper inst1{env.vulkan_functions}; - inst1.create_info.add_layer(layer_name); - FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); - inst1.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.add_layer(layer_name); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); // look for VK_LAYER_PATHS EXPECT_TRUE(env.debug_log.find("/tmp/carol")); EXPECT_TRUE(env.debug_log.find("/tandy")); EXPECT_TRUE(env.debug_log.find((HOME / "/ with spaces/"))); +} +// Test VK_LAYER_PATH environment variable with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyLayerEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::explicit_layer_env_var); - env.debug_log.clear(); + const char* layer_name = "TestLayer"; + env.add_explicit_layer( + TestLayerDetails(ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name(layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "test_layer.json") + .set_discovery_type(ManifestDiscoveryType::env_var)); - env.platform_shim->set_elevated_privilege(true); + // Now set up a layer path that includes default and user-specified locations, + // so that the test app can find them. Include some badly specified elements as well. + // Need to redirect the 'home' directory + std::filesystem::path HOME = "/home/fake_home"; + EnvVarWrapper home_env_var{"HOME", HOME}; + std::string vk_layer_path = ":/tmp/carol::::/:"; + vk_layer_path += (HOME / "/ with spaces/:::::/tandy:"); + EnvVarWrapper layer_path_env_var{"VK_LAYER_PATH", vk_layer_path}; - InstWrapper inst2{env.vulkan_functions}; - inst2.create_info.add_layer(layer_name); - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(VK_ERROR_LAYER_NOT_PRESENT); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.add_layer(layer_name); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_LAYER_NOT_PRESENT); EXPECT_FALSE(env.debug_log.find("/tmp/carol")); - - env.platform_shim->set_elevated_privilege(false); } // Test VK_ADD_LAYER_PATH environment variable TEST(EnvVarICDOverrideSetup, TestOnlyAddLayerEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); - env.platform_shim->redirect_path("/tmp/carol", env.get_folder(ManifestLocation::explicit_layer_add_env_var).location()); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::explicit_layer_add_env_var); const char* layer_name = "TestLayer"; env.add_explicit_layer( @@ -358,35 +386,52 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddLayerEnvVar) { vk_layer_path += (HOME / "/ with spaces/:::::/tandy:"); EnvVarWrapper add_layer_path_env_var{"VK_ADD_LAYER_PATH", vk_layer_path}; - InstWrapper inst1{env.vulkan_functions}; - inst1.create_info.add_layer(layer_name); - FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); - inst1.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.add_layer(layer_name); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); // look for VK_ADD_LAYER_PATHS EXPECT_TRUE(env.debug_log.find("/tmp/carol")); EXPECT_TRUE(env.debug_log.find("/tandy")); EXPECT_TRUE(env.debug_log.find((HOME / "/ with spaces/"))); +} - env.debug_log.clear(); +// Test VK_ADD_LAYER_PATH environment variable with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyAddLayerEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::explicit_layer_add_env_var); - env.platform_shim->set_elevated_privilege(true); + const char* layer_name = "TestLayer"; + env.add_explicit_layer( + TestLayerDetails(ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name(layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "test_layer.json") + .set_discovery_type(ManifestDiscoveryType::add_env_var)); - InstWrapper inst2{env.vulkan_functions}; - inst2.create_info.add_layer(layer_name); - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(VK_ERROR_LAYER_NOT_PRESENT); + // Set up a layer path that includes default and user-specified locations, + // so that the test app can find them. Include some badly specified elements as well. + // Need to redirect the 'home' directory + std::filesystem::path HOME = "/home/fake_home"; + EnvVarWrapper home_env_var{"HOME", HOME}; + std::string vk_layer_path = ":/tmp/carol::::/:"; + vk_layer_path += (HOME / "/ with spaces/:::::/tandy:"); + EnvVarWrapper add_layer_path_env_var{"VK_ADD_LAYER_PATH", vk_layer_path}; - EXPECT_FALSE(env.debug_log.find("/tmp/carol")); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.add_layer(layer_name); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_LAYER_NOT_PRESENT); - env.platform_shim->set_elevated_privilege(false); + EXPECT_FALSE(env.debug_log.find("/tmp/carol")); } // Test VK_IMPLICIT_LAYER_PATH environment variable TEST(EnvVarICDOverrideSetup, TestOnlyImplicitLayerEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); - env.platform_shim->redirect_path("/tmp/carol", env.get_folder(ManifestLocation::implicit_layer_env_var).location()); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::implicit_layer_env_var); const char* layer_name = "TestLayer"; env.add_implicit_layer(TestLayerDetails(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} @@ -404,39 +449,57 @@ TEST(EnvVarICDOverrideSetup, TestOnlyImplicitLayerEnvVar) { std::string vk_implicit_layer_path = ":/tmp/carol::::/:"; vk_implicit_layer_path += (HOME / "/ with spaces/:::::/tandy:"); EnvVarWrapper implicit_layer_path_env_var{"VK_IMPLICIT_LAYER_PATH", vk_implicit_layer_path}; - InstWrapper inst1{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); - inst1.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - auto active_layers1 = inst1.GetActiveLayers(inst1.GetPhysDev(), 1); - ASSERT_TRUE(string_eq(active_layers1.at(0).layerName, layer_name)); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); + ASSERT_TRUE(string_eq(active_layers.at(0).layerName, layer_name)); // look for VK_IMPLICIT_LAYER_PATHS EXPECT_TRUE(env.debug_log.find("/tmp/carol")); EXPECT_TRUE(env.debug_log.find("/tandy")); EXPECT_TRUE(env.debug_log.find((HOME / "/ with spaces/"))); +} - env.debug_log.clear(); +// Test VK_IMPLICIT_LAYER_PATH environment variable run with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyImplicitLayerEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::implicit_layer_env_var); + + const char* layer_name = "TestLayer"; + env.add_implicit_layer(TestLayerDetails(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} + .set_name(layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_disable_environment("DISABLE_ENV")), + "test_layer.json") + .set_discovery_type(ManifestDiscoveryType::env_var)); - env.platform_shim->set_elevated_privilege(true); + // Now set up a layer path that includes default and user-specified locations, + // so that the test app can find them. Include some badly specified elements as well. + // Need to redirect the 'home' directory + std::filesystem::path HOME = "/home/fake_home"; + EnvVarWrapper home_env_var{"HOME", HOME}; + std::string vk_implicit_layer_path = ":/tmp/carol::::/:"; + vk_implicit_layer_path += (HOME / "/ with spaces/:::::/tandy:"); + EnvVarWrapper implicit_layer_path_env_var{"VK_IMPLICIT_LAYER_PATH", vk_implicit_layer_path}; - InstWrapper inst2{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - auto active_layers2 = inst2.GetActiveLayers(inst2.GetPhysDev(), 0); - ASSERT_TRUE(active_layers2.empty()); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 0); + ASSERT_TRUE(active_layers.empty()); EXPECT_FALSE(env.debug_log.find("/tmp/carol")); - - env.platform_shim->set_elevated_privilege(false); } // Test VK_ADD_IMPLICIT_LAYER_PATH environment variable TEST(EnvVarICDOverrideSetup, TestOnlyAddImplicitLayerEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); - env.platform_shim->redirect_path("/tmp/carol", env.get_folder(ManifestLocation::implicit_layer_add_env_var).location()); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::implicit_layer_add_env_var); const char* layer_name = "TestLayer"; env.add_implicit_layer(TestLayerDetails(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} @@ -453,7 +516,7 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddImplicitLayerEnvVar) { EnvVarWrapper home_env_var{"HOME", HOME}; std::string vk_add_implicit_layer_path = ":/tmp/carol::::/:"; vk_add_implicit_layer_path += (HOME / "/ with spaces/:::::/tandy:"); - EnvVarWrapper add_implicit_layer_path_env_var{"VK_ADD_LAYER_PATH", vk_add_implicit_layer_path}; + EnvVarWrapper add_implicit_layer_path_env_var{"VK_ADD_IMPLICIT_LAYER_PATH", vk_add_implicit_layer_path}; InstWrapper inst1{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); @@ -466,21 +529,39 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddImplicitLayerEnvVar) { EXPECT_TRUE(env.debug_log.find("/tmp/carol")); EXPECT_TRUE(env.debug_log.find("/tandy")); EXPECT_TRUE(env.debug_log.find((HOME / "/ with spaces/"))); +} - env.debug_log.clear(); +// Test VK_ADD_IMPLICIT_LAYER_PATH environment variable running with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyAddImplicitLayerEnvVarWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::implicit_layer_add_env_var); - env.platform_shim->set_elevated_privilege(true); + const char* layer_name = "TestLayer"; + env.add_implicit_layer(TestLayerDetails(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} + .set_name(layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_disable_environment("DISABLE_ENV")), + "test_layer.json") + .set_discovery_type(ManifestDiscoveryType::add_env_var)); - InstWrapper inst2{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(); + // Set up a layer path that includes default and user-specified locations, + // so that the test app can find them. Include some badly specified elements as well. + // Need to redirect the 'home' directory + std::filesystem::path HOME = "/home/fake_home"; + EnvVarWrapper home_env_var{"HOME", HOME}; + std::string vk_add_implicit_layer_path = ":/tmp/carol::::/:"; + vk_add_implicit_layer_path += (HOME / "/ with spaces/:::::/tandy:"); + EnvVarWrapper add_implicit_layer_path_env_var{"VK_ADD_IMPLICIT_LAYER_PATH", vk_add_implicit_layer_path}; - auto active_layers2 = inst2.GetActiveLayers(inst2.GetPhysDev(), 0); - ASSERT_TRUE(active_layers2.empty()); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - EXPECT_FALSE(env.debug_log.find("/tmp/carol")); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 0); + ASSERT_TRUE(active_layers.empty()); - env.platform_shim->set_elevated_privilege(false); + EXPECT_FALSE(env.debug_log.find("/tmp/carol")); } #endif diff --git a/tests/loader_layer_tests.cpp b/tests/loader_layer_tests.cpp index 0fae6ce96..3babc5961 100644 --- a/tests/loader_layer_tests.cpp +++ b/tests/loader_layer_tests.cpp @@ -27,6 +27,9 @@ #include "test_environment.h" +#include "util/test_defines.h" +#include "util/get_executable_path.h" + void CheckLogForLayerString(FrameworkEnvironment& env, const char* implicit_layer_name, bool check_for_enable) { { InstWrapper inst{env.vulkan_functions}; @@ -968,7 +971,8 @@ TEST(ImplicitLayers, DuplicateLayers) { .set_description("actually_layer_1") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("if_you_can")), - "regular_layer_1.json")); + "regular_layer_1.json") + .set_discovery_type(ManifestDiscoveryType::add_env_var)); auto& layer1 = env.get_test_layer(0); layer1.set_description("actually_layer_1"); layer1.set_make_spurious_log_in_create_instance("actually_layer_1"); @@ -979,17 +983,10 @@ TEST(ImplicitLayers, DuplicateLayers) { .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("if_you_can")), "regular_layer_1.json") - // use override folder as just a folder and manually add it to the implicit layer search paths - .set_discovery_type(ManifestDiscoveryType::override_folder)); + .set_discovery_type(ManifestDiscoveryType::generic)); auto& layer2 = env.get_test_layer(1); layer2.set_description("actually_layer_2"); layer2.set_make_spurious_log_in_create_instance("actually_layer_2"); -#if defined(WIN32) - env.platform_shim->add_manifest(ManifestCategory::implicit_layer, env.get_folder(ManifestLocation::override_layer).location()); -#elif COMMON_UNIX_PLATFORMS - env.platform_shim->redirect_path(std::filesystem::path(USER_LOCAL_SHARE_DIR "/vulkan/implicit_layer.d"), - env.get_folder(ManifestLocation::override_layer).location()); -#endif auto layer_props = env.GetLayerProperties(2); ASSERT_TRUE(string_eq(same_layer_name_1, layer_props[0].layerName)); @@ -1926,7 +1923,7 @@ TEST(OverrideMetaLayer, AppKeysDoesNotContainCurrentApplication) { } } -TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromSecureLocation) { +TEST(OverrideMetaLayer, RunningWithRegularPrivilegesFromSecureLocation) { FrameworkEnvironment env; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); @@ -1950,38 +1947,58 @@ TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromSecureLocation) { .add_override_path(override_layer_folder.location())), "meta_test_layer.json"}); - { // try with no elevated privileges - auto layer_props = env.GetLayerProperties(2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); + // try with no elevated privileges + auto layer_props = env.GetLayerProperties(2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); - InstWrapper inst{env.vulkan_functions}; - inst.create_info.set_api_version(1, 1, 0); - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); - auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); - env.debug_log.clear(); - } + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(1, 1, 0); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); + auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); +} - env.platform_shim->set_elevated_privilege(true); +TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromSecureLocation) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); - { // try with elevated privileges - auto layer_props = env.GetLayerProperties(2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); + auto& override_layer_folder = env.get_folder(ManifestLocation::override_layer); - InstWrapper inst{env.vulkan_functions}; - inst.create_info.set_api_version(1, 1, 0); - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); - auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, active_layer_props)); - } + const char* regular_layer_name = "VK_LAYER_TestLayer_1"; + override_layer_folder.write_manifest("regular_test_layer.json", + ManifestLayer{} + .add_layer(ManifestLayer::LayerDescription{} + .set_name(regular_layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_api_version(VK_MAKE_API_VERSION(0, 1, 1, 0))) + .get_manifest_str()); + auto override_folder_location = override_layer_folder.location().string(); + env.add_implicit_layer(TestLayerDetails{ + ManifestLayer{}.set_file_format_version({1, 2, 0}).add_layer(ManifestLayer::LayerDescription{} + .set_name(lunarg_meta_layer_name) + .set_api_version(VK_MAKE_API_VERSION(0, 1, 1, 0)) + .add_component_layer(regular_layer_name) + .set_disable_environment("DisableMeIfYouCan") + .add_override_path(override_layer_folder.location())), + "meta_test_layer.json"}); + + // try with elevated privileges + auto layer_props = env.GetLayerProperties(2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(1, 1, 0); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); + auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, active_layer_props)); } // Override layer should not be found and thus not loaded when running with elevated privileges -TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromUnsecureLocation) { +TEST(OverrideMetaLayer, RunningWithRegularPrivilegesFromUnsecureLocation) { FrameworkEnvironment env; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); @@ -2005,32 +2022,51 @@ TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromUnsecureLocation) { "meta_test_layer.json"} .set_discovery_type(ManifestDiscoveryType::unsecured_generic)); - { // try with no elevated privileges - auto layer_props = env.GetLayerProperties(2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); + auto layer_props = env.GetLayerProperties(2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); - InstWrapper inst{env.vulkan_functions}; - inst.create_info.set_api_version(1, 1, 0); - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); - env.debug_log.clear(); - auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, active_layer_props)); - } + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(1, 1, 0); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); + env.debug_log.clear(); + auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, active_layer_props)); +} - env.platform_shim->set_elevated_privilege(true); +TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromUnsecureLocation) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); - { // try with no elevated privileges - ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + auto& override_layer_folder = env.get_folder(ManifestLocation::override_layer); - InstWrapper inst{env.vulkan_functions}; - inst.create_info.set_api_version(1, 1, 0); - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_FALSE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); - ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); - } + const char* regular_layer_name = "VK_LAYER_TestLayer_1"; + override_layer_folder.write_manifest("regular_test_layer.json", + ManifestLayer{} + .add_layer(ManifestLayer::LayerDescription{} + .set_name(regular_layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_api_version(VK_MAKE_API_VERSION(0, 1, 1, 0))) + .get_manifest_str()); + env.add_implicit_layer(TestLayerDetails{ + ManifestLayer{}.set_file_format_version({1, 2, 0}).add_layer(ManifestLayer::LayerDescription{} + .set_name(lunarg_meta_layer_name) + .set_api_version(VK_MAKE_API_VERSION(0, 1, 1, 0)) + .add_component_layer(regular_layer_name) + .set_disable_environment("DisableMeIfYouCan") + .add_override_path(override_layer_folder.location())), + "meta_test_layer.json"} + .set_discovery_type(ManifestDiscoveryType::unsecured_generic)); + + ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(1, 1, 0); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + ASSERT_FALSE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); + ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); } // Makes sure explicit layers can't override pre-instance functions even if enabled by the override layer @@ -2825,6 +2861,16 @@ TEST(ExplicitLayers, CorrectOrderOfApplicationEnabledLayers) { } } +// Helpers +bool contains(std::vector const& vec, const char* name) { + return std::any_of(std::begin(vec), std::end(vec), + [name](VkExtensionProperties const& elem) { return string_eq(name, elem.extensionName); }); +} +bool contains(std::vector const& vec, const char* name) { + return std::any_of(std::begin(vec), std::end(vec), + [name](VkLayerProperties const& elem) { return string_eq(name, elem.layerName); }); +} + TEST(LayerExtensions, ImplicitNoAdditionalInstanceExtension) { FrameworkEnvironment env; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); @@ -5380,34 +5426,31 @@ TEST(TestLayers, AllowFilterWithImplicitLayer) { auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); ASSERT_TRUE(string_eq(layers.at(0).layerName, layer_name)); } + env.loader_settings.add_app_specific_setting(AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration( + LoaderSettingsLayerConfiguration{} + .set_name(layer_name) + .set_control("on") + .set_path(env.get_shimmed_layer_manifest_path(0)) + .set_treat_as_implicit_manifest(true))); + env.update_loader_settings(env.loader_settings); { - env.loader_settings.add_app_specific_setting(AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration( - LoaderSettingsLayerConfiguration{} - .set_name(layer_name) - .set_control("on") - .set_path(env.get_shimmed_layer_manifest_path(0)) - .set_treat_as_implicit_manifest(true))); - env.update_loader_settings(env.loader_settings); - InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); ASSERT_TRUE(string_eq(layers.at(0).layerName, layer_name)); } + env.loader_settings.app_specific_settings.at(0).layer_configurations.at(0).set_control("off"); + env.update_loader_settings(env.loader_settings); { - env.loader_settings.app_specific_settings.at(0).layer_configurations.at(0).set_control("off"); - env.update_loader_settings(env.loader_settings); - InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); } + env.loader_settings.app_specific_settings.at(0).layer_configurations.at(0).set_control("auto"); + env.update_loader_settings(env.loader_settings); { - env.loader_settings.app_specific_settings.at(0).layer_configurations.at(0).set_control("auto"); - env.update_loader_settings(env.loader_settings); - InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -5417,7 +5460,7 @@ TEST(TestLayers, AllowFilterWithImplicitLayer) { env.remove_loader_settings(); - // Set the disable_environment variable + // Set the layer specific disable_environment variable - layer should never load if this is set EnvVarWrapper set_disable_env_var{disable_env_var, "1"}; { @@ -5441,7 +5484,6 @@ TEST(TestLayers, AllowFilterWithImplicitLayer) { InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); - // layer's disable_environment takes priority ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); } { diff --git a/tests/loader_regression_tests.cpp b/tests/loader_regression_tests.cpp index c2d652f6c..60b108812 100644 --- a/tests/loader_regression_tests.cpp +++ b/tests/loader_regression_tests.cpp @@ -3872,7 +3872,7 @@ TEST(DuplicateRegistryEntries, Layers) { auto null_path = env.get_folder(ManifestLocation::null).location() / "test_layer.json"; - env.platform_shim->add_manifest(ManifestCategory::explicit_layer, null_path); + env.platform_shim->add_manifest_to_registry(ManifestCategory::explicit_layer, null_path); const char* layer_name = "TestLayer"; env.add_explicit_layer( @@ -3890,7 +3890,7 @@ TEST(DuplicateRegistryEntries, Layers) { TEST(DuplicateRegistryEntries, Drivers) { FrameworkEnvironment env{}; auto null_path = env.get_folder(ManifestLocation::null).location() / "test_icd_0.json"; - env.platform_shim->add_manifest(ManifestCategory::icd, null_path); + env.platform_shim->add_manifest_to_registry(ManifestCategory::icd, null_path); env.add_icd(TestICDDetails{TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA}.set_discovery_type(ManifestDiscoveryType::null_dir)) .add_physical_device("physical_device_0") @@ -3908,8 +3908,6 @@ TEST(DuplicateRegistryEntries, Drivers) { TEST(LibraryLoading, SystemLocations) { FrameworkEnvironment env{}; - EnvVarWrapper ld_library_path("LD_LIBRARY_PATH", env.get_folder(ManifestLocation::driver).location().string()); - ld_library_path.add_to_list(env.get_folder(ManifestLocation::explicit_layer).location()); auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_library_path_type(LibraryPathType::default_search_paths)) .add_physical_device({}); @@ -3941,16 +3939,13 @@ TEST(LibraryLoading, SystemLocations) { } #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) -// Check that valid symlinks do not cause the loader to crash when directly in an XDG env-var -TEST(ManifestDiscovery, ValidSymlinkInXDGEnvVar) { - FrameworkEnvironment env{FrameworkSettings{}.set_enable_default_search_paths(false)}; +// Check that valid symlinks do not cause the loader to crash when found in an unsecure location +TEST(ManifestDiscovery, ValidSymlinkInUnsecureLocation) { + FrameworkEnvironment env; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA).set_discovery_type(ManifestDiscoveryType::override_folder)) .add_physical_device({}); - auto symlink_path = - env.get_folder(ManifestLocation::driver_env_var).add_symlink(env.get_icd_manifest_path(0), "symlink_to_driver.json"); - - EnvVarWrapper xdg_config_dirs_env_var{"XDG_CONFIG_DIRS", symlink_path}; + env.add_symlink(ManifestLocation::unsecured_driver, env.get_icd_manifest_path(0), "symlink_to_driver.json"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); @@ -3959,29 +3954,23 @@ TEST(ManifestDiscovery, ValidSymlinkInXDGEnvVar) { // Check that valid symlinks do not cause the loader to crash TEST(ManifestDiscovery, ValidSymlink) { - FrameworkEnvironment env{FrameworkSettings{}.set_enable_default_search_paths(false)}; + FrameworkEnvironment env{FrameworkSettings{}}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA).set_discovery_type(ManifestDiscoveryType::override_folder)) .add_physical_device({}); - auto symlink_path = - env.get_folder(ManifestLocation::driver_env_var).add_symlink(env.get_icd_manifest_path(0), "symlink_to_driver.json"); - - env.platform_shim->set_fake_path(ManifestCategory::icd, env.get_folder(ManifestLocation::driver_env_var).location()); + env.add_symlink(ManifestLocation::driver, env.get_icd_manifest_path(0), "symlink_to_driver.json"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); inst.CheckCreate(); } -// Check that invalid symlinks do not cause the loader to crash when directly in an XDG env-var -TEST(ManifestDiscovery, InvalidSymlinkXDGEnvVar) { - FrameworkEnvironment env{FrameworkSettings{}.set_enable_default_search_paths(false)}; - - auto symlink_path = - env.get_folder(ManifestLocation::driver) - .add_symlink(env.get_folder(ManifestLocation::driver).location() / "nothing_here.json", "symlink_to_nothing.json"); +// Check that invalid symlinks do not cause the loader to crash when found in an unsecure location +TEST(ManifestDiscovery, InvalidSymlinkInUnsecureLocation) { + FrameworkEnvironment env; - EnvVarWrapper xdg_config_dirs_env_var{symlink_path}; + env.add_symlink(ManifestLocation::unsecured_driver, env.get_folder(ManifestLocation::driver).location() / "nothing_here.json", + "symlink_to_nothing.json"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); @@ -3990,13 +3979,10 @@ TEST(ManifestDiscovery, InvalidSymlinkXDGEnvVar) { // Check that invalid symlinks do not cause the loader to crash TEST(ManifestDiscovery, InvalidSymlink) { - FrameworkEnvironment env{FrameworkSettings{}.set_enable_default_search_paths(false)}; - - auto symlink_path = - env.get_folder(ManifestLocation::driver_env_var) - .add_symlink(env.get_folder(ManifestLocation::driver).location() / "nothing_here.json", "symlink_to_nothing.json"); + FrameworkEnvironment env; - env.platform_shim->set_fake_path(ManifestCategory::icd, env.get_folder(ManifestLocation::driver_env_var).location()); + env.add_symlink(ManifestLocation::driver, env.get_folder(ManifestLocation::driver).location() / "nothing_here.json", + "symlink_to_nothing.json"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); @@ -4220,7 +4206,9 @@ TEST(InvalidManifest, ICD) { for (size_t i = 0; i < invalid_jsons.size(); i++) { auto file_name = std::string("invalid_driver_") + std::to_string(i) + ".json"; std::filesystem::path new_path = env.get_folder(ManifestLocation::driver).write_manifest(file_name, invalid_jsons[i]); - env.platform_shim->add_manifest(ManifestCategory::icd, new_path); +#if defined(WIN32) + env.platform_shim->add_manifest_to_registry(ManifestCategory::icd, new_path); +#endif } InstWrapper inst{env.vulkan_functions}; @@ -4245,7 +4233,9 @@ TEST(InvalidManifest, Layer) { auto file_name = std::string("invalid_implicit_layer_") + std::to_string(i) + ".json"; std::filesystem::path new_path = env.get_folder(ManifestLocation::implicit_layer).write_manifest(file_name, invalid_jsons[i]); - env.platform_shim->add_manifest(ManifestCategory::implicit_layer, new_path); +#if defined(WIN32) + env.platform_shim->add_manifest_to_registry(ManifestCategory::implicit_layer, new_path); +#endif } InstWrapper inst{env.vulkan_functions}; diff --git a/tests/loader_settings_tests.cpp b/tests/loader_settings_tests.cpp index adc2ae995..8c1fcb90d 100644 --- a/tests/loader_settings_tests.cpp +++ b/tests/loader_settings_tests.cpp @@ -29,31 +29,33 @@ #include -std::string get_settings_location_log_message([[maybe_unused]] FrameworkEnvironment const& env, - [[maybe_unused]] bool use_secure = false) { +#include "util/get_executable_path.h" +#include "util/json_writer.h" +#include "util/test_defines.h" + +std::string get_settings_location_log_message([[maybe_unused]] FrameworkEnvironment const& env, bool use_secure = true) { std::string s = "Using layer configurations found in loader settings from "; #if defined(WIN32) - return s + (env.get_folder(ManifestLocation::settings_location).location() / "vk_loader_settings.json").string(); -#elif COMMON_UNIX_PLATFORMS - if (use_secure) - return s + "/etc/vulkan/loader_settings.d/vk_loader_settings.json"; - else - return s + "/home/fake_home/.local/share/vulkan/loader_settings.d/vk_loader_settings.json"; + ManifestLocation settings_location = use_secure ? ManifestLocation::settings_location : ManifestLocation::unsecured_settings; + return s + (env.get_folder(settings_location).location() / "vk_loader_settings.json").string(); +#elif TESTING_COMMON_UNIX_PLATFORMS + return s + (use_secure ? env.secure_manifest_base_location : env.unsecure_manifest_base_location) + + "/vulkan/loader_settings.d/vk_loader_settings.json"; #endif } +std::string get_unsecure_settings_location_log_message(FrameworkEnvironment const& env) { + return get_settings_location_log_message(env, false); +} -std::string get_settings_not_in_use_log_message([[maybe_unused]] FrameworkEnvironment const& env, - [[maybe_unused]] bool use_secure = false) { +std::string get_settings_not_in_use_log_message([[maybe_unused]] FrameworkEnvironment const& env, bool use_secure) { std::string s = "vk_loader_settings.json file found at \""; #if defined(WIN32) - s += (env.get_folder(ManifestLocation::settings_location).location() / "vk_loader_settings.json").string(); -#elif COMMON_UNIX_PLATFORMS - if (use_secure) - s += "/etc/vulkan/loader_settings.d/vk_loader_settings.json"; - else - s += "/home/fake_home/.local/share/vulkan/loader_settings.d/vk_loader_settings.json"; + ManifestLocation settings_location = use_secure ? ManifestLocation::settings_location : ManifestLocation::unsecured_settings; + return s + (env.get_folder(settings_location).location() / "vk_loader_settings.json").string(); +#elif TESTING_COMMON_UNIX_PLATFORMS + return s + (use_secure ? env.secure_manifest_base_location : env.unsecure_manifest_base_location) + + "/vulkan/loader_settings.d/vk_loader_settings.json\" but did not contain any valid settings."; #endif - return s + "\" but did not contain any valid settings."; } enum class LayerType { exp, @@ -124,39 +126,56 @@ TEST(SettingsFile, SettingsInUnsecuredLocation) { ManifestLayer::LayerDescription{}.set_name(regular_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), "regular_test_layer.json"} .set_discovery_type(ManifestDiscoveryType::override_folder)); - env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( - AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} - .set_name(regular_layer_name) - .set_path(env.get_layer_manifest_path()) - .set_control("on")))); - { - auto layer_props = env.GetLayerProperties(1); - EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); + env.update_loader_settings( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} + .set_name(regular_layer_name) + .set_path(env.get_layer_manifest_path()) + .set_control("on"))), + false); - InstWrapper inst{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); + auto layer_props = env.GetLayerProperties(1); + EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env))); - env.debug_log.clear(); - auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); - ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); - } - env.platform_shim->set_elevated_privilege(true); - { - ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - InstWrapper inst{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); + ASSERT_TRUE(env.debug_log.find(get_unsecure_settings_location_log_message(env))); + env.debug_log.clear(); + auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); + ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); +} - ASSERT_FALSE(env.debug_log.find(get_settings_location_log_message(env))); - ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); - } +TEST(SettingsFile, SettingsInUnsecuredLocationRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); + const char* regular_layer_name = "VK_LAYER_TestLayer_0"; + env.add_explicit_layer(TestLayerDetails{ + ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name(regular_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "regular_test_layer.json"} + .set_discovery_type(ManifestDiscoveryType::override_folder)); + env.update_loader_settings( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} + .set_name(regular_layer_name) + .set_path(env.get_layer_manifest_path()) + .set_control("on"))), + false); + + ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + + ASSERT_FALSE(env.debug_log.find(get_unsecure_settings_location_log_message(env))); + ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); } TEST(SettingsFile, SettingsInSecuredLocation) { - FrameworkEnvironment env{FrameworkSettings{}.set_secure_loader_settings(true)}; + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); const char* regular_layer_name = "VK_LAYER_TestLayer_0"; @@ -170,33 +189,18 @@ TEST(SettingsFile, SettingsInSecuredLocation) { .set_name(regular_layer_name) .set_path(env.get_layer_manifest_path()) .set_control("on")))); - { - auto layer_props = env.GetLayerProperties(1); - EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); - - InstWrapper inst{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env, true))); - env.debug_log.clear(); - auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); - ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); - } - env.platform_shim->set_elevated_privilege(true); - { - auto layer_props = env.GetLayerProperties(1); - EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); + auto layer_props = env.GetLayerProperties(1); + EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); - InstWrapper inst{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env, true))); - env.debug_log.clear(); - auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); - ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); - } + ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env))); + env.debug_log.clear(); + auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); + ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); } // Make sure settings file can have multiple sets of settings @@ -505,7 +509,7 @@ TEST(SettingsFile, LayerListIsEmpty) { writer.EndArray(); writer.EndObject(); writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); auto layer_props = env.GetLayerProperties(1); ASSERT_TRUE(string_eq(layer_props.at(0).layerName, implicit_layer_name)); @@ -513,7 +517,7 @@ TEST(SettingsFile, LayerListIsEmpty) { InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_not_in_use_log_message(env))); + ASSERT_TRUE(env.debug_log.find(get_settings_not_in_use_log_message(env, true))); auto actice_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 1); ASSERT_TRUE(string_eq(actice_layer_props.at(0).layerName, implicit_layer_name)); } @@ -552,7 +556,7 @@ TEST(SettingsFile, InvalidSettingsFile) { ASSERT_TRUE(fuzzer_output_json_file.is_open()); std::stringstream fuzzer_output_json; fuzzer_output_json << fuzzer_output_json_file.rdbuf(); - env.write_settings_file(fuzzer_output_json.str()); + env.write_settings_file(fuzzer_output_json.str(), true); check_integrity(); } @@ -563,7 +567,7 @@ TEST(SettingsFile, InvalidSettingsFile) { writer.StartObject(); writer.AddKeyedString("file_format_version", "0.0.0"); writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); check_integrity(); } @@ -577,7 +581,7 @@ TEST(SettingsFile, InvalidSettingsFile) { writer.StartKeyedObject("settings"); writer.EndObject(); writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); check_integrity(); } @@ -594,7 +598,7 @@ TEST(SettingsFile, InvalidSettingsFile) { writer.EndObject(); } writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); check_integrity(); } @@ -1773,8 +1777,8 @@ TEST(SettingsFile, EnvVarsWork_VK_LOADER_LAYERS_DISABLE) { TEST(SettingsFile, MultipleKeysInRegistryInUnsecureLocation) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); - env.platform_shim->add_unsecured_manifest(ManifestCategory::settings, "jank_path"); - env.platform_shim->add_unsecured_manifest(ManifestCategory::settings, "jank_path2"); + env.platform_shim->add_unsecured_manifest_to_registry(ManifestCategory::settings, "jank_path"); + env.platform_shim->add_unsecured_manifest_to_registry(ManifestCategory::settings, "jank_path2"); const char* regular_layer_name = "VK_LAYER_TestLayer_0"; env.add_explicit_layer(TestLayerDetails{ @@ -1782,11 +1786,13 @@ TEST(SettingsFile, MultipleKeysInRegistryInUnsecureLocation) { ManifestLayer::LayerDescription{}.set_name(regular_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), "regular_test_layer.json"} .set_discovery_type(ManifestDiscoveryType::override_folder)); - env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( - AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} - .set_name(regular_layer_name) - .set_path(env.get_layer_manifest_path()) - .set_control("on")))); + env.update_loader_settings( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} + .set_name(regular_layer_name) + .set_path(env.get_layer_manifest_path()) + .set_control("on"))), + false); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); auto layer_props = env.GetLayerProperties(1); @@ -1796,16 +1802,16 @@ TEST(SettingsFile, MultipleKeysInRegistryInUnsecureLocation) { FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env))); + ASSERT_TRUE(env.debug_log.find(get_unsecure_settings_location_log_message(env))); auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); } TEST(SettingsFile, MultipleKeysInRegistryInSecureLocation) { - FrameworkEnvironment env{FrameworkSettings{}.set_secure_loader_settings(true)}; + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); - env.platform_shim->add_manifest(ManifestCategory::settings, "jank_path"); - env.platform_shim->add_manifest(ManifestCategory::settings, "jank_path2"); + env.platform_shim->add_manifest_to_registry(ManifestCategory::settings, "jank_path"); + env.platform_shim->add_manifest_to_registry(ManifestCategory::settings, "jank_path2"); const char* regular_layer_name = "VK_LAYER_TestLayer_0"; env.add_explicit_layer(TestLayerDetails{ @@ -1821,7 +1827,6 @@ TEST(SettingsFile, MultipleKeysInRegistryInSecureLocation) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); // Make sure it works if the settings file is in the HKEY_LOCAL_MACHINE - env.platform_shim->set_elevated_privilege(true); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); { auto layer_props = env.GetLayerProperties(1); @@ -2571,14 +2576,14 @@ TEST(SettingsFile, StderrLog_NoOutput) { ManifestLayer{}.add_layer( ManifestLayer::LayerDescription{}.set_name(explicit_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), "explicit_test_layer1.json"}); - env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( AppSpecificSettings{} .add_layer_configuration(LoaderSettingsLayerConfiguration{} .set_name(explicit_layer_name) .set_path(env.get_shimmed_layer_manifest_path()) .set_control("auto")) .add_layer_configuration( - LoaderSettingsLayerConfiguration{}.set_name("VK_LAYER_missing").set_path("/road/to/nowhere").set_control("auto")))); + LoaderSettingsLayerConfiguration{}.set_name("VK_LAYER_missing").set_path("/road/to/nowhere").set_control("auto"))); env.loader_settings.app_specific_settings.at(0).stderr_log = {""}; env.update_loader_settings(env.loader_settings); @@ -2662,14 +2667,14 @@ TEST(SettingsFile, NoStderr_log_but_VK_LOADER_DEBUG) { ManifestLayer::LayerDescription{}.set_name(explicit_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), "explicit_test_layer1.json"}); - env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( AppSpecificSettings{} .add_layer_configuration(LoaderSettingsLayerConfiguration{} .set_name(explicit_layer_name) .set_path(env.get_shimmed_layer_manifest_path()) .set_control("auto")) .add_layer_configuration( - LoaderSettingsLayerConfiguration{}.set_name("VK_LAYER_missing").set_path("/road/to/nowhere").set_control("auto")))); + LoaderSettingsLayerConfiguration{}.set_name("VK_LAYER_missing").set_path("/road/to/nowhere").set_control("auto"))); env.loader_settings.app_specific_settings.at(0).stderr_log = {}; env.update_loader_settings(env.loader_settings); @@ -3176,7 +3181,7 @@ TEST(SettingsFile, InvalidAdditionalDriversField) { writer.EndArray(); writer.EndObject(); writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -3377,7 +3382,6 @@ TEST(SettingsFile, DriverConfigurationsAndAdditionalDrivers) { LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[1])); env.update_loader_settings(env.loader_settings); - env.update_loader_settings(env.loader_settings); InstWrapper inst{env.vulkan_functions}; inst.create_info.set_api_version(VK_API_VERSION_1_1); inst.CheckCreate(); diff --git a/tests/loader_testing_main.cpp b/tests/loader_testing_main.cpp index 018bea434..263c1e36f 100644 --- a/tests/loader_testing_main.cpp +++ b/tests/loader_testing_main.cpp @@ -27,6 +27,8 @@ #include "test_environment.h" +#include "util/test_defines.h" + // Makes any failed assertion throw, allowing for graceful cleanup of resources instead of hard aborts class ThrowListener : public testing::EmptyTestEventListener { void OnTestPartResult(const testing::TestPartResult& result) override { @@ -70,14 +72,16 @@ int main(int argc, char** argv) { EnvVarWrapper vk_loader_layers_disable_env_var{"VK_LOADER_LAYERS_DISABLE"}; EnvVarWrapper vk_loader_debug_env_var{"VK_LOADER_DEBUG"}; EnvVarWrapper vk_loader_disable_inst_ext_filter_env_var{"VK_LOADER_DISABLE_INST_EXT_FILTER"}; + EnvVarWrapper vk_loader_disable_select_env_var{"VK_LOADER_DISABLE_SELECT"}; -#if COMMON_UNIX_PLATFORMS + // even though apple shouldn't have XDG env-vars set, the loader looks for them so we have to clear them +#if TESTING_COMMON_UNIX_PLATFORMS // Set only one of the 4 XDG variables to /etc, let everything else be empty - EnvVarWrapper xdg_config_home_env_var{"XDG_CONFIG_HOME", ETC_DIR}; + EnvVarWrapper xdg_config_home_env_var{"XDG_CONFIG_HOME"}; EnvVarWrapper xdg_config_dirs_env_var{"XDG_CONFIG_DIRS"}; EnvVarWrapper xdg_data_home_env_var{"XDG_DATA_HOME"}; EnvVarWrapper xdg_data_dirs_env_var{"XDG_DATA_DIRS"}; - EnvVarWrapper home_env_var{"HOME", HOME_DIR}; + EnvVarWrapper home_env_var{"HOME", "/home/test_home_directory"}; #endif ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener); diff --git a/tests/loader_unknown_ext_tests.cpp b/tests/loader_unknown_ext_tests.cpp index 782275682..162ceabd3 100644 --- a/tests/loader_unknown_ext_tests.cpp +++ b/tests/loader_unknown_ext_tests.cpp @@ -27,6 +27,8 @@ #include "test_environment.h" +#include "framework/util/dispatchable_handle.h" + enum class TestConfig { add_layer_implementation, add_layer_interception, diff --git a/tests/loader_version_tests.cpp b/tests/loader_version_tests.cpp index c13c7f60a..e097ff3ce 100644 --- a/tests/loader_version_tests.cpp +++ b/tests/loader_version_tests.cpp @@ -338,9 +338,9 @@ TEST(MultipleICDConfig, Basic) { env.get_test_icd(1).physical_devices.at(0).properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; env.get_test_icd(2).physical_devices.at(0).properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_CPU; - copy_string_to_char_array("dev0", env.get_test_icd(0).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); - copy_string_to_char_array("dev1", env.get_test_icd(1).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); - copy_string_to_char_array("dev2", env.get_test_icd(2).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev0").copy(env.get_test_icd(0).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev1").copy(env.get_test_icd(1).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev2").copy(env.get_test_icd(2).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); From 508795c7f1325d1e045038b3d8a5ea764cabdaf5 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 25 Jul 2025 15:43:36 -0500 Subject: [PATCH 16/65] loader settings unix search path follows driver/layer searching The searching for the loader_settings.json file relied on faulty assumptions about which paths would be used, notably ignoring XDG env-vars, as well as SYSCONFDIR, EXTRASYSCONFDIR, and the fallback xdg paths. --- loader/loader.c | 2 +- loader/settings.c | 125 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 106 insertions(+), 21 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 85e3223af..6102a525d 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -3270,7 +3270,7 @@ void copy_data_file_info(const char *cur_path, const char *relative_path, size_t char *cur_write = *output_path; while (cur_path[start] != '\0') { - while (cur_path[start] == PATH_SEPARATOR) { + while (cur_path[start] == PATH_SEPARATOR && cur_path[start] != '\0') { start++; } stop = start; diff --git a/loader/settings.c b/loader/settings.c index a28c34fae..cc6a60c24 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -387,46 +387,131 @@ VkResult parse_device_configurations(const struct loader_instance* inst, cJSON* return res; } +#if COMMON_UNIX_PLATFORMS +// Given a base and suffix path, determine if a file at that location exists, and if it is return success. +// Since base may contain multiple paths seperated by PATH_SEPARATOR, we must extract each segment and check segment + suffix +// individually VkResult check_if_settings_path_exists(const struct loader_instance* inst, const char* base, const char* suffix, char** settings_file_path) { if (NULL == base || NULL == suffix) { return VK_ERROR_INITIALIZATION_FAILED; } + size_t base_len = strlen(base); size_t suffix_len = strlen(suffix); - size_t path_len = base_len + suffix_len + 1; - *settings_file_path = loader_instance_heap_calloc(inst, path_len, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); - if (NULL == *settings_file_path) { - return VK_ERROR_OUT_OF_HOST_MEMORY; - } - loader_strncpy(*settings_file_path, path_len, base, base_len); - loader_strncat(*settings_file_path, path_len, suffix, suffix_len); - if (!loader_platform_file_exists(*settings_file_path)) { + uint32_t start = 0; + uint32_t stop = 0; + while (base[start] != '\0' && start < base_len && stop < base_len) { + start = stop; + stop = start + 1; + while (base[stop] != PATH_SEPARATOR && base[stop] != '\0' && stop < base_len) { + stop++; + } + + size_t segment_len = (stop - start); + if (segment_len <= 1) { + // segment is *just* a PATH_SEPARATOR, skip it + continue; + } + size_t path_len = segment_len + suffix_len + 1; + *settings_file_path = loader_instance_heap_calloc(inst, path_len, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); + if (NULL == *settings_file_path) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + loader_strncpy(*settings_file_path, path_len, base + start, segment_len); + loader_strncat(*settings_file_path, path_len, suffix, suffix_len); + + if (loader_platform_file_exists(*settings_file_path)) { + return VK_SUCCESS; + } loader_instance_heap_free(inst, *settings_file_path); *settings_file_path = NULL; - return VK_ERROR_INITIALIZATION_FAILED; } - return VK_SUCCESS; + + return VK_ERROR_INITIALIZATION_FAILED; } + +// Follow the logic of read_data_files_in_search_paths but only look for "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME VkResult get_unix_settings_path(const struct loader_instance* inst, char** settings_file_path) { - VkResult res = - check_if_settings_path_exists(inst, loader_secure_getenv("HOME", inst), - "/.local/share/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, settings_file_path); + // First, get XDG env-vars we use. Don't need to worry about free'ing because on linux getenv is non-allocating + char* xdg_config_home = loader_secure_getenv("XDG_CONFIG_HOME", inst); + char* xdg_config_dirs = loader_secure_getenv("XDG_CONFIG_DIRS", inst); + char* xdg_data_home = loader_secure_getenv("XDG_DATA_HOME", inst); + char* xdg_data_dirs = loader_secure_getenv("XDG_DATA_DIRS", inst); + + // Use fallback directories for xdg_config_dirs and xdg_data_dirs if they are NULL. +#if !defined(__Fuchsia__) && !defined(__QNX__) + if (NULL == xdg_config_dirs || '\0' == xdg_config_dirs[0]) { + xdg_config_dirs = FALLBACK_CONFIG_DIRS; + } +#endif + +#if !defined(__Fuchsia__) && !defined(__QNX__) + if (NULL == xdg_data_dirs || '\0' == xdg_data_dirs[0]) { + xdg_data_dirs = FALLBACK_DATA_DIRS; + } +#endif + + VkResult res = check_if_settings_path_exists(inst, xdg_config_home, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + + res = check_if_settings_path_exists(inst, xdg_data_home, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + + // Check home if either xdg_config_home or xdg_data_home wasn't set + char* home = loader_secure_getenv("HOME", inst); + if (home != NULL) { + if (NULL == xdg_config_home || '\0' == xdg_config_home[0]) { + res = check_if_settings_path_exists(inst, home, "/.config/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + } + if (NULL == xdg_data_home || '\0' == xdg_data_home[0]) { + res = check_if_settings_path_exists(inst, home, "/.local/share/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + } + } + + res = check_if_settings_path_exists(inst, xdg_config_dirs, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); if (res == VK_SUCCESS) { return res; } - // If HOME isn't set, fallback to XDG_DATA_HOME - res = check_if_settings_path_exists(inst, loader_secure_getenv("XDG_DATA_HOME", inst), - "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, settings_file_path); + + res = check_if_settings_path_exists(inst, SYSCONFDIR, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } +#if defined(EXTRASYSCONFDIR) + + res = check_if_settings_path_exists(inst, EXTRASYSCONFDIR, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); if (res == VK_SUCCESS) { return res; } - // if XDG_DATA_HOME isn't set, fallback to /etc. - // note that the settings_fil_path_suffix stays the same since its the same layout as for XDG_DATA_HOME - return check_if_settings_path_exists(inst, "/etc", "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, - settings_file_path); +#endif + res = check_if_settings_path_exists(inst, xdg_data_dirs, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + + return VK_ERROR_INITIALIZATION_FAILED; } +#endif bool check_if_layer_configurations_are_equal(loader_settings_layer_configuration* a, loader_settings_layer_configuration* b) { if (!a->name || !b->name || 0 != strcmp(a->name, b->name)) { From b54033fde026143565519b2d35db206a0059f754 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 28 Jan 2025 10:21:42 -0600 Subject: [PATCH 17/65] Add ubuntu-24.04-arm runner to github actions CI --- .github/workflows/build.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0e8542cb7..41142b398 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -176,6 +176,32 @@ jobs: - run: cmake --build build - run: ctest --parallel --output-on-failure -E UnknownFunction --test-dir build/ + linux-arm: + needs: codegen + runs-on: ubuntu-24.04-arm + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Test CMake min + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.22.1 + - run: sudo apt update + - run: sudo apt install --yes --no-install-recommends libwayland-dev libxrandr-dev + - run: | + cmake -S. -B build \ + -D CMAKE_BUILD_TYPE=Debug \ + -D BUILD_TESTS=ON \ + -D UPDATE_DEPS=ON \ + -D LOADER_ENABLE_ADDRESS_SANITIZER=ON \ + -D BUILD_WERROR=ON + - run: cmake --build build + - run: ctest --parallel --output-on-failure --test-dir build/ + - run: cmake --install build --prefix /tmp + windows_vs: # windows is 2x expensive to run on GitHub machines, so only run if we know something else simple passed as well needs: linux-no-asm From f2389e27734347c1d9f40e03be53f69f969976b1 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 1 Aug 2025 10:55:42 -0500 Subject: [PATCH 18/65] Use correct format specifier for size_t --- loader/loader.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 6102a525d..65ee2d105 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -435,7 +435,7 @@ VkResult normalize_path(const struct loader_instance *inst, char **passed_in_pat if (actual_len == 0) { size_t last_error = (size_t)GetLastError(); loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, - "normalize_path: Call to GetFullPathNameA() failed with error code %llu when given the path %s", last_error, + "normalize_path: Call to GetFullPathNameA() failed with error code %zu when given the path %s", last_error, *passed_in_path); res = VK_ERROR_INITIALIZATION_FAILED; goto out; @@ -456,7 +456,7 @@ VkResult normalize_path(const struct loader_instance *inst, char **passed_in_pat if (actual_len == 0) { size_t last_error = (size_t)GetLastError(); loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, - "normalize_path: Call to GetFullPathNameA() failed with error code %llu when given the path %s", last_error, + "normalize_path: Call to GetFullPathNameA() failed with error code %zu when given the path %s", last_error, *passed_in_path); res = VK_ERROR_INITIALIZATION_FAILED; goto out; @@ -464,7 +464,7 @@ VkResult normalize_path(const struct loader_instance *inst, char **passed_in_pat } else if (actual_len + 1 != path_len) { loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "normalize_path: Call to GetFullPathNameA() with too small of a buffer when given the path %s after the " - "initial call to GetFullPathNameA() failed for the same reason. Buffer size is %llu, actual size is %llu", + "initial call to GetFullPathNameA() failed for the same reason. Buffer size is %zu, actual size is %zu", *passed_in_path, (size_t)path_len, (size_t)actual_len); res = VK_ERROR_INITIALIZATION_FAILED; goto out; From 7a1d96aae809216705b2d0ed6c36ec747ab00845 Mon Sep 17 00:00:00 2001 From: ziga-lunarg Date: Mon, 4 Aug 2025 19:42:02 +0300 Subject: [PATCH 19/65] build: Update to header 1.4.324 --- CMakeLists.txt | 2 +- loader/loader.rc | 4 ++-- scripts/known_good.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9d434af6..7766618d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.323 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.324 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/loader.rc b/loader/loader.rc index a2f68cbf8..0a41511b3 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 323, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.323.Dev Build" +#define VER_FILE_VERSION 1, 4, 324, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.324.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index b62307217..fe506fc72 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.323" + "commit": "v1.4.324" }, { "name": "googletest", @@ -42,4 +42,4 @@ "googletest": "GOOGLETEST_INSTALL_DIR", "detours": "DETOURS_INSTALL_DIR" } -} \ No newline at end of file +} From ffd25df8da11ee13627a9a00d9efb0f6d3aab9ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 23:34:39 +0000 Subject: [PATCH 20/65] build(deps): bump github/codeql-action from 3.29.4 to 3.29.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.4 to 3.29.5. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/4e828ff8d448a8a6e532957b1811f387a63867e8...51f77329afa6477de8c49fc9c7046c15b9a4e79d) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 10a04aec1..559f86236 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 - uses: actions/setup-python@v5 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: category: "/language:${{matrix.language}}" From f0969bf51bb8a6185d20d2577f05e01faa50f398 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Sat, 2 Aug 2025 16:43:16 -0500 Subject: [PATCH 21/65] Print deviceUUID correctly by using %02x --- loader/loader.c | 4 ++-- loader/settings.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 65ee2d105..1e1eb16bb 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -7191,7 +7191,7 @@ VkResult loader_apply_settings_device_configurations(struct loader_instance *ins inst, VULKAN_LOADER_WARN_BIT, 0, "loader_apply_settings_device_configurations: settings file contained device_configuration which does not " "appear in the enumerated VkPhysicalDevices. Missing VkPhysicalDevice with deviceName: \"%s\" and deviceUUID: " - "%x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x", + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", inst->settings.device_configurations[i].deviceName, id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); } else { @@ -7199,7 +7199,7 @@ VkResult loader_apply_settings_device_configurations(struct loader_instance *ins inst, VULKAN_LOADER_WARN_BIT, 0, "loader_apply_settings_device_configurations: settings file contained device_configuration which does not " "appear in the enumerated VkPhysicalDevices. Missing VkPhysicalDevice with deviceUUID: " - "%x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x", + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); } diff --git a/loader/settings.c b/loader/settings.c index cc6a60c24..34dbb5a40 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -606,7 +606,8 @@ void log_settings(const struct loader_instance* inst, loader_settings* settings) for (uint32_t i = 0; i < settings->device_configuration_count; i++) { loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "---- Device Configuration [%d] ----", i); uint8_t* id = settings->device_configurations[i].deviceUUID; - loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "deviceUUID: %x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x", id[0], id[1], id[2], + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "deviceUUID: %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); } } From 6e96ed6079954f027c70299c35892de64a81efaf Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 4 Aug 2025 20:58:14 -0500 Subject: [PATCH 22/65] More settings tests --- tests/loader_settings_tests.cpp | 165 ++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/tests/loader_settings_tests.cpp b/tests/loader_settings_tests.cpp index 8c1fcb90d..dfc9fa5b1 100644 --- a/tests/loader_settings_tests.cpp +++ b/tests/loader_settings_tests.cpp @@ -3398,3 +3398,168 @@ TEST(SettingsFile, DriverConfigurationsAndAdditionalDrivers) { ASSERT_TRUE(0 == memcmp(vulkan_11_props.deviceUUID, uuids[1].data(), VK_UUID_SIZE * sizeof(uint8_t))); } + +TEST(SettingsFile, InvalidDriverConfigurations) { + FrameworkEnvironment env{}; + std::vector uuids{3, VulkanUUID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}; + + // Mix up the uuid's so that they are all unique + int count = 1; + for (auto& uuid : uuids) { + std::rotate(uuid.begin(), uuid.begin() + count, uuid.end()); + count++; + } + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("regular_driver") + .set_deviceUUID(uuids[0]) + .finish()); + + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::override_folder)) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("additional_device") + .set_deviceUUID(uuids[2]) + .finish()); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{} + .set_additional_drivers_use_exclusively(true) + .add_driver_configuration(LoaderSettingsDriverConfiguration{}.set_path(env.get_icd_manifest_path(1))) + .add_device_configuration(LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[1]))); + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pd = inst.GetPhysDev(); +} + +TEST(SettingsFile, DeviceConfigurationReordersAdditionalDrivers) { + FrameworkEnvironment env{}; + std::vector uuids{3, VulkanUUID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}; + + // Mix up the uuid's so that they are all unique + int count = 1; + for (auto& uuid : uuids) { + std::rotate(uuid.begin(), uuid.begin() + count, uuid.end()); + count++; + } + + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("regular_driverA") + .set_deviceUUID(uuids[0]) + .finish()); + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("regular_driverB") + .set_deviceUUID(uuids[1]) + .finish()); + + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::override_folder)) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("additional_device") + .set_deviceUUID(uuids[2]) + .finish()); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{} + .add_driver_configuration(LoaderSettingsDriverConfiguration{}.set_path(env.get_icd_manifest_path(2))) + .add_device_configuration(LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[2])) + .add_device_configuration(LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[0])) + .add_device_configuration(LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[1]))); + env.update_loader_settings(env.loader_settings); + { + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto pd = inst.GetPhysDevs(3).at(0); + VkPhysicalDeviceProperties props{}; + env.vulkan_functions.vkGetPhysicalDeviceProperties(pd, &props); + ASSERT_TRUE(string_eq(props.deviceName, "additional_device")); + } + { // do the same check but with 1.1 so we can check that the UUID matches + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(VK_API_VERSION_1_1); + inst.CheckCreate(); + auto pd = inst.GetPhysDevs(3).at(0); + VkPhysicalDeviceProperties props{}; + env.vulkan_functions.vkGetPhysicalDeviceProperties(pd, &props); + ASSERT_TRUE(string_eq(props.deviceName, "additional_device")); + + VkPhysicalDeviceVulkan11Properties vulkan_11_props{}; + vulkan_11_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; + VkPhysicalDeviceProperties2 props2{}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &vulkan_11_props; + inst->vkGetPhysicalDeviceProperties2(pd, &props2); + + ASSERT_TRUE(0 == memcmp(vulkan_11_props.deviceUUID, uuids[2].data(), VK_UUID_SIZE * sizeof(uint8_t))); + } +} + +TEST(SettingsFile, DeviceConfigurationReordersExclusiveAdditionalDrivers) { + FrameworkEnvironment env{}; + std::vector uuids{3, VulkanUUID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}; + + // Mix up the uuid's so that they are all unique + int count = 1; + for (auto& uuid : uuids) { + std::rotate(uuid.begin(), uuid.begin() + count, uuid.end()); + count++; + } + + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("regular_driverA") + .set_deviceUUID(uuids[0]) + .finish()); + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("regular_driverB") + .set_deviceUUID(uuids[1]) + .finish()); + + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_discovery_type(ManifestDiscoveryType::override_folder)) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_physical_device(PhysicalDevice{} + .set_api_version(VK_API_VERSION_1_1) + .set_deviceName("additional_device") + .set_deviceUUID(uuids[2]) + .finish()); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{} + .set_additional_drivers_use_exclusively(true) + .add_driver_configuration(LoaderSettingsDriverConfiguration{}.set_path(env.get_icd_manifest_path(2))) + .add_device_configuration(LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[2]))); + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(VK_API_VERSION_1_1); + inst.CheckCreate(); + auto pd = inst.GetPhysDev(); + VkPhysicalDeviceProperties props{}; + env.vulkan_functions.vkGetPhysicalDeviceProperties(pd, &props); + ASSERT_TRUE(string_eq(props.deviceName, "additional_device")); + + VkPhysicalDeviceVulkan11Properties vulkan_11_props{}; + vulkan_11_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; + VkPhysicalDeviceProperties2 props2{}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &vulkan_11_props; + inst->vkGetPhysicalDeviceProperties2(pd, &props2); + + ASSERT_TRUE(0 == memcmp(vulkan_11_props.deviceUUID, uuids[2].data(), VK_UUID_SIZE * sizeof(uint8_t))); +} From 434131b094a1989390380a85e2b32a54df764aaa Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 4 Aug 2025 21:01:12 -0500 Subject: [PATCH 23/65] Fix invalid settings configuration parsing errors If any of the device configurations fields failed to parse, this would cause the entire array of device configurations to fail to parse, which is surprising. --- loader/settings.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/loader/settings.c b/loader/settings.c index 34dbb5a40..c0ed15ec9 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -368,10 +368,13 @@ VkResult parse_device_configurations(const struct loader_instance* inst, cJSON* res = VK_ERROR_INITIALIZATION_FAILED; goto out; } - res = parse_device_configuration(inst, device, &(loader_settings->device_configurations[i++])); - if (VK_SUCCESS != res) { + res = parse_device_configuration(inst, device, &(loader_settings->device_configurations[i])); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { goto out; + } else if (res != VK_SUCCESS) { + continue; } + i++; } out: if (res != VK_SUCCESS) { From 9fb3eda0778ec06f5041931a2cd806041e6b9531 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 4 Aug 2025 21:05:17 -0500 Subject: [PATCH 24/65] Log settings file device_configuration deviceName's --- loader/settings.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/loader/settings.c b/loader/settings.c index c0ed15ec9..ac456743c 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -612,6 +612,9 @@ void log_settings(const struct loader_instance* inst, loader_settings* settings) loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "deviceUUID: %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); + if ('\0' != settings->device_configurations[i].deviceName[0]) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "deviceName: %s", settings->device_configurations[i].deviceName); + } } } loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "---------------------------------"); From 670bc546a48f4f9176a8e90ae4506e206530c165 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 4 Aug 2025 21:07:28 -0500 Subject: [PATCH 25/65] Check json object type in settings file device configurations --- loader/settings.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/loader/settings.c b/loader/settings.c index ac456743c..5d15e4964 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -306,6 +306,11 @@ VkResult parse_device_configuration(const struct loader_instance* inst, cJSON* d res = VK_ERROR_INITIALIZATION_FAILED; goto out; } + + if (deviceUUID_array->type != cJSON_Array) { + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } if (VK_UUID_SIZE != loader_cJSON_GetArraySize(deviceUUID_array)) { res = VK_ERROR_INITIALIZATION_FAILED; goto out; From c3394a6c87cca4729e7544d1ae69b0f4db69f6f6 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 4 Aug 2025 22:14:01 -0500 Subject: [PATCH 26/65] Log when device configurations removes VkPhysicalDevices --- loader/loader.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 1e1eb16bb..99daa47ea 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -7122,6 +7122,15 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_EnumeratePhysicalDevices(VkInstance in // device_configurations in the settings file. VkResult loader_apply_settings_device_configurations(struct loader_instance *inst, uint32_t *pPhysicalDeviceCount, VkPhysicalDevice *pPhysicalDevices) { + loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, + "Reordering the output of vkEnumeratePhysicalDevices to match the loader settings device configurations list"); + + bool *pd_was_added = loader_stack_alloc(inst->phys_dev_count_term * sizeof(bool)); + if (NULL == pd_was_added) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + memset(pd_was_added, 0, inst->phys_dev_count_term * sizeof(bool)); + bool *pd_supports_11 = loader_stack_alloc(inst->phys_dev_count_term * sizeof(bool)); if (NULL == pd_supports_11) { return VK_ERROR_OUT_OF_HOST_MEMORY; @@ -7177,9 +7186,10 @@ VkResult loader_apply_settings_device_configurations(struct loader_instance *ins *pPhysicalDeviceCount = written_output_index; // write out how many were written return VK_INCOMPLETE; } + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "pPhysicalDevices array index %d is set to \"%s\" ", + written_output_index, pd_props[j].deviceName); pPhysicalDevices[written_output_index++] = (VkPhysicalDevice)inst->phys_devs_term[j]; - loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, "Insert VkPhysicalDevice \"%s\" to the pPhysicalDevices list", - pd_props[j].deviceName); + pd_was_added[j] = true; break; } } @@ -7205,6 +7215,23 @@ VkResult loader_apply_settings_device_configurations(struct loader_instance *ins } } } + + for (uint32_t j = 0; j < inst->phys_dev_count_term; j++) { + if (!pd_was_added[j]) { + loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, + "VkPhysicalDevice \"%s\" did not appear in the settings file device configurations list, so was not added " + "to the pPhysicalDevices array", + pd_props[j].deviceName); + } + } + + if (written_output_index == 0) { + loader_log(inst, VULKAN_LOADER_WARN_BIT, 0, + "loader_apply_settings_device_configurations: None of the settings file device configurations had " + "deviceUUID's that corresponded to enumerated VkPhysicalDevices. Returning VK_ERROR_INITIALIZATION_FAILED"); + return VK_ERROR_INITIALIZATION_FAILED; + } + *pPhysicalDeviceCount = written_output_index; // update with how many were written return VK_SUCCESS; } From 046e658417e1c1454791c1f080e26c6a22e1dd78 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 5 Aug 2025 10:57:21 -0500 Subject: [PATCH 27/65] Correct settings tests when no devices are present --- tests/loader_settings_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/loader_settings_tests.cpp b/tests/loader_settings_tests.cpp index dfc9fa5b1..fae400367 100644 --- a/tests/loader_settings_tests.cpp +++ b/tests/loader_settings_tests.cpp @@ -3302,7 +3302,7 @@ TEST(SettingsFile, MissingDriverConfiguration) { InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); - auto pd = inst.GetPhysDev(); + inst.GetPhysDev(VK_ERROR_INITIALIZATION_FAILED); } // Three drivers, second on has the matching UUID in the settings file. @@ -3433,7 +3433,7 @@ TEST(SettingsFile, InvalidDriverConfigurations) { InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); - auto pd = inst.GetPhysDev(); + auto pd = inst.GetPhysDev(VK_ERROR_INITIALIZATION_FAILED); } TEST(SettingsFile, DeviceConfigurationReordersAdditionalDrivers) { From 07aa86589862b3888c3f09a11bbb34243f1efc13 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Thu, 31 Jul 2025 13:22:20 -0500 Subject: [PATCH 28/65] Speedup tests by putting physical devices in a map Requires extensive modifications to the tests since previously many tests accessed the physical device array directly. --- tests/framework/icd/test_icd.cpp | 127 ++-- tests/framework/icd/test_icd.h | 39 +- tests/loader_alloc_callback_tests.cpp | 9 +- tests/loader_debug_ext_tests.cpp | 29 +- tests/loader_envvar_tests.cpp | 4 +- tests/loader_get_proc_addr_tests.cpp | 25 +- tests/loader_layer_tests.cpp | 80 +-- tests/loader_phys_dev_inst_ext_tests.cpp | 476 +++++++------- tests/loader_regression_tests.cpp | 776 +++++++++++------------ tests/loader_threading_tests.cpp | 4 +- tests/loader_unknown_ext_tests.cpp | 59 +- tests/loader_version_tests.cpp | 111 ++-- tests/loader_wsi_tests.cpp | 69 +- 13 files changed, 909 insertions(+), 899 deletions(-) diff --git a/tests/framework/icd/test_icd.cpp b/tests/framework/icd/test_icd.cpp index b657672b7..01bf1dd36 100644 --- a/tests/framework/icd/test_icd.cpp +++ b/tests/framework/icd/test_icd.cpp @@ -99,7 +99,7 @@ bool IsInstanceExtensionEnabled(const char* extension_name) { } bool IsPhysicalDeviceExtensionAvailable(const char* extension_name) { - for (auto& phys_dev : icd.physical_devices) { + for (auto const& [phys_dev_handle, phys_dev] : icd.physical_devices) { if (phys_dev.extensions.end() != std::find_if(phys_dev.extensions.begin(), phys_dev.extensions.end(), [extension_name](Extension const& ext) { return ext.extensionName == extension_name; })) { @@ -110,22 +110,13 @@ bool IsPhysicalDeviceExtensionAvailable(const char* extension_name) { } PhysicalDevice& GetPhysDevice(VkPhysicalDevice physicalDevice) { - for (auto& phys_dev : icd.physical_devices) { - if (phys_dev.vk_physical_device.handle == physicalDevice) return phys_dev; + if (icd.physical_devices.count(physicalDevice) > 0) { + return icd.physical_devices.at(physicalDevice); } assert(false && "vkPhysicalDevice not found!"); - return icd.physical_devices[0]; + return icd.physical_devices.at(icd.physical_devices.begin()->first); } -std::vector::iterator find_physical_device(VkPhysicalDevice physicalDevice) { - auto found = std::find_if(icd.physical_devices.begin(), icd.physical_devices.end(), [physicalDevice](PhysicalDevice& phys_dev) { - return phys_dev.vk_physical_device.handle == physicalDevice; - }); - return found; -} - -bool is_physical_device_found(std::vector::iterator found) { return found != icd.physical_devices.end(); } - bool is_valid_surface(VkSurfaceKHR surface_to_check) { uint64_t surf_handle = (uint64_t)(surface_to_check); auto found = std::find(icd.surface_handles.begin(), icd.surface_handles.end(), surf_handle); @@ -134,22 +125,24 @@ bool is_valid_surface(VkSurfaceKHR surface_to_check) { struct FindDevice { bool found = false; - uint32_t phys_dev_index = 0; + VkPhysicalDevice phys_dev = 0; uint32_t dev_index = 0; }; FindDevice lookup_device(VkDevice device) { FindDevice fd{}; - for (uint32_t p = 0; p < icd.physical_devices.size(); p++) { - auto const& phys_dev = icd.physical_devices.at(p); + auto it = icd.device_to_physical_device_map.find(device); + if (it != icd.device_to_physical_device_map.end()) { + auto& phys_dev = icd.physical_devices.at(it->second); + fd.found = true; + fd.phys_dev = phys_dev.vk_physical_device.handle; for (uint32_t d = 0; d < phys_dev.device_handles.size(); d++) { if (phys_dev.device_handles.at(d) == device) { - fd.found = true; - fd.phys_dev_index = p; fd.dev_index = d; - return fd; + break; } } + return fd; } return fd; } @@ -289,15 +282,22 @@ VKAPI_ATTR VkResult VKAPI_CALL test_vkEnumeratePhysicalDevices([[maybe_unused]] if (pPhysicalDevices == nullptr) { *pPhysicalDeviceCount = static_cast(icd.physical_devices.size()); } else { + using SortPair = std::pair; + std::vector in_order_phys_dev; + for (auto const& [phys_dev_handle, phys_dev] : icd.physical_devices) { + in_order_phys_dev.push_back({phys_dev.iteration_order, phys_dev_handle}); + } + + std::sort(in_order_phys_dev.begin(), in_order_phys_dev.end(), + [](SortPair const& a, SortPair const& b) { return a.first < b.first; }); + uint32_t handles_written = 0; - for (size_t i = 0; i < icd.physical_devices.size(); i++) { - if (i < *pPhysicalDeviceCount) { - handles_written++; - pPhysicalDevices[i] = icd.physical_devices[i].vk_physical_device.handle; - } else { + for (auto const& phys_dev : in_order_phys_dev) { + if (handles_written + 1 > *pPhysicalDeviceCount) { *pPhysicalDeviceCount = handles_written; return VK_INCOMPLETE; } + pPhysicalDevices[handles_written++] = phys_dev.second; } *pPhysicalDeviceCount = handles_written; } @@ -337,10 +337,15 @@ test_vkEnumeratePhysicalDeviceGroups([[maybe_unused]] VkInstance instance, uint3 result = VK_INCOMPLETE; break; } + pPhysicalDeviceGroupProperties[device_group].subsetAllocation = false; pPhysicalDeviceGroupProperties[device_group].physicalDeviceCount = 1; - pPhysicalDeviceGroupProperties[device_group].physicalDevices[0] = - icd.physical_devices[device_group].vk_physical_device.handle; + for (auto const& [phys_dev_handle, phys_dev] : icd.physical_devices) { + if (phys_dev.iteration_order == device_group) { + pPhysicalDeviceGroupProperties[device_group].physicalDevices[0] = phys_dev_handle; + break; + } + } for (size_t i = 1; i < VK_MAX_DEVICE_GROUP_SIZE; i++) { pPhysicalDeviceGroupProperties[device_group].physicalDevices[i] = {}; } @@ -479,7 +484,7 @@ VKAPI_ATTR void VKAPI_CALL test_vkDestroyDebugReportCallbackEXT([[maybe_unused]] VKAPI_ATTR VkResult VKAPI_CALL test_vkDebugMarkerSetObjectTagEXT(VkDevice dev, const VkDebugMarkerObjectTagInfoEXT* pTagInfo) { if (pTagInfo && pTagInfo->objectType == VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT) { VkPhysicalDevice pd = (VkPhysicalDevice)(uintptr_t)(pTagInfo->object); - if (pd != icd.physical_devices.at(lookup_device(dev).phys_dev_index).vk_physical_device.handle) return VK_ERROR_DEVICE_LOST; + if (pd != lookup_device(dev).phys_dev) return VK_ERROR_DEVICE_LOST; } if (pTagInfo && pTagInfo->objectType == VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT) { if (pTagInfo->object != icd.surface_handles.at(0)) return VK_ERROR_DEVICE_LOST; @@ -492,7 +497,7 @@ VKAPI_ATTR VkResult VKAPI_CALL test_vkDebugMarkerSetObjectTagEXT(VkDevice dev, c VKAPI_ATTR VkResult VKAPI_CALL test_vkDebugMarkerSetObjectNameEXT(VkDevice dev, const VkDebugMarkerObjectNameInfoEXT* pNameInfo) { if (pNameInfo && pNameInfo->objectType == VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT) { VkPhysicalDevice pd = (VkPhysicalDevice)(uintptr_t)(pNameInfo->object); - if (pd != icd.physical_devices.at(lookup_device(dev).phys_dev_index).vk_physical_device.handle) return VK_ERROR_DEVICE_LOST; + if (pd != lookup_device(dev).phys_dev) return VK_ERROR_DEVICE_LOST; } if (pNameInfo && pNameInfo->objectType == VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT) { if (pNameInfo->object != icd.surface_handles.at(0)) return VK_ERROR_DEVICE_LOST; @@ -509,7 +514,7 @@ VKAPI_ATTR void VKAPI_CALL test_vkCmdDebugMarkerInsertEXT(VkCommandBuffer, const VKAPI_ATTR VkResult VKAPI_CALL test_vkSetDebugUtilsObjectNameEXT(VkDevice dev, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { if (pNameInfo && pNameInfo->objectType == VK_OBJECT_TYPE_PHYSICAL_DEVICE) { VkPhysicalDevice pd = (VkPhysicalDevice)(uintptr_t)(pNameInfo->objectHandle); - if (pd != icd.physical_devices.at(lookup_device(dev).phys_dev_index).vk_physical_device.handle) return VK_ERROR_DEVICE_LOST; + if (pd != lookup_device(dev).phys_dev) return VK_ERROR_DEVICE_LOST; } if (pNameInfo && pNameInfo->objectType == VK_OBJECT_TYPE_SURFACE_KHR) { if (pNameInfo->objectHandle != icd.surface_handles.at(0)) return VK_ERROR_DEVICE_LOST; @@ -522,7 +527,7 @@ VKAPI_ATTR VkResult VKAPI_CALL test_vkSetDebugUtilsObjectNameEXT(VkDevice dev, c VKAPI_ATTR VkResult VKAPI_CALL test_vkSetDebugUtilsObjectTagEXT(VkDevice dev, const VkDebugUtilsObjectTagInfoEXT* pTagInfo) { if (pTagInfo && pTagInfo->objectType == VK_OBJECT_TYPE_PHYSICAL_DEVICE) { VkPhysicalDevice pd = (VkPhysicalDevice)(uintptr_t)(pTagInfo->objectHandle); - if (pd != icd.physical_devices.at(lookup_device(dev).phys_dev_index).vk_physical_device.handle) return VK_ERROR_DEVICE_LOST; + if (pd != lookup_device(dev).phys_dev) return VK_ERROR_DEVICE_LOST; } if (pTagInfo && pTagInfo->objectType == VK_OBJECT_TYPE_SURFACE_KHR) { if (pTagInfo->objectHandle != icd.surface_handles.at(0)) return VK_ERROR_DEVICE_LOST; @@ -570,19 +575,19 @@ VKAPI_ATTR void VKAPI_CALL test_vkGetPhysicalDeviceQueueFamilyProperties(VkPhysi VKAPI_ATTR VkResult VKAPI_CALL test_vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { check_allocator_handle(pAllocator); - auto found = find_physical_device(physicalDevice); - if (!is_physical_device_found(found)) { + if (icd.physical_devices.count(physicalDevice) == 0) { return VK_ERROR_INITIALIZATION_FAILED; } + auto& found = icd.physical_devices.at(physicalDevice); auto device_handle = DispatchableHandle(); *pDevice = device_handle.handle; - found->device_handles.push_back(device_handle.handle); - found->device_create_infos.push_back(DeviceCreateInfo{pCreateInfo}); + found.device_handles.push_back(device_handle.handle); + found.device_create_infos.emplace_back(pCreateInfo); for (uint32_t i = 0; i < pCreateInfo->queueCreateInfoCount; i++) { - found->queue_handles.emplace_back(); + found.queue_handles.emplace_back(); } + icd.device_to_physical_device_map.emplace(device_handle.handle, physicalDevice); icd.device_handles.emplace_back(std::move(device_handle)); - return VK_SUCCESS; } @@ -592,9 +597,10 @@ VKAPI_ATTR void VKAPI_CALL test_vkDestroyDevice(VkDevice device, const VkAllocat if (found != icd.device_handles.end()) icd.device_handles.erase(found); auto fd = lookup_device(device); if (!fd.found) return; - auto& phys_dev = icd.physical_devices.at(fd.phys_dev_index); + auto& phys_dev = icd.physical_devices.at(fd.phys_dev); phys_dev.device_handles.erase(phys_dev.device_handles.begin() + fd.dev_index); phys_dev.device_create_infos.erase(phys_dev.device_create_infos.begin() + fd.dev_index); + icd.device_to_physical_device_map.erase(device); } VKAPI_ATTR VkResult VKAPI_CALL generic_tool_props_function([[maybe_unused]] VkPhysicalDevice physicalDevice, uint32_t* pToolCount, @@ -877,9 +883,8 @@ VKAPI_ATTR VkResult VKAPI_CALL test_vkGetPhysicalDeviceSurfaceSupportKHR(VkPhysi } } if (nullptr != pSupported) { - auto found = find_physical_device(physicalDevice); - if (is_physical_device_found(found)) { - *pSupported = found->queue_family_properties.at(queueFamilyIndex).support_present; + if (icd.physical_devices.count(physicalDevice) > 0) { + *pSupported = icd.physical_devices.at(physicalDevice).queue_family_properties.at(queueFamilyIndex).support_present; } else { *pSupported = VK_FALSE; return VK_SUCCESS; @@ -1139,7 +1144,7 @@ VKAPI_ATTR void VKAPI_CALL test_vkGetDeviceQueue([[maybe_unused]] VkDevice devic uint32_t queueIndex, VkQueue* pQueue) { auto fd = lookup_device(device); if (fd.found) { - *pQueue = icd.physical_devices.at(fd.phys_dev_index).queue_handles[queueIndex].handle; + *pQueue = icd.physical_devices.at(fd.phys_dev).queue_handles[queueIndex].handle; } } @@ -1667,7 +1672,7 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL get_physical_device_func([[maybe_unused return to_vkVoidFunction(test_vkGetPhysicalDeviceToolPropertiesEXT); } - for (auto& phys_dev : icd.physical_devices) { + for (auto const& [phys_dev_handle, phys_dev] : icd.physical_devices) { for (auto& func : phys_dev.custom_physical_device_functions) { if (func.name == pName) { return to_vkVoidFunction(func.function); @@ -1703,9 +1708,9 @@ PFN_vkVoidFunction get_instance_func(VkInstance instance, const char* pName) { return nullptr; } -bool should_check(std::vector const& exts, VkDevice device, const char* ext_name) { - if (device == NULL) return true; // always look if device is NULL - for (auto const& ext : exts) { +bool should_check(std::vector* exts, VkDevice device, const char* ext_name) { + if (exts == nullptr || device == VK_NULL_HANDLE) return true; // always look if device is NULL + for (auto const& ext : *exts) { if (string_eq(ext, ext_name)) { return true; } @@ -1714,18 +1719,20 @@ bool should_check(std::vector const& exts, VkDevice device, const c } PFN_vkVoidFunction get_device_func(VkDevice device, const char* pName) { - DeviceCreateInfo create_info{}; + std::vector* enabled_extensions; + FindDevice found_device{}; if (device != nullptr) { - auto fd = lookup_device(device); - if (!fd.found) return NULL; - create_info = icd.physical_devices.at(fd.phys_dev_index).device_create_infos.at(fd.dev_index); + found_device = lookup_device(device); + if (!found_device.found) return NULL; + enabled_extensions = + &icd.physical_devices.at(found_device.phys_dev).device_create_infos.at(found_device.dev_index).enabled_extensions; } if (string_eq(pName, "vkCreateCommandPool")) return to_vkVoidFunction(test_vkCreateCommandPool); if (string_eq(pName, "vkAllocateCommandBuffers")) return to_vkVoidFunction(test_vkAllocateCommandBuffers); if (string_eq(pName, "vkDestroyCommandPool")) return to_vkVoidFunction(test_vkDestroyCommandPool); if (string_eq(pName, "vkGetDeviceQueue")) return to_vkVoidFunction(test_vkGetDeviceQueue); if (string_eq(pName, "vkDestroyDevice")) return to_vkVoidFunction(test_vkDestroyDevice); - if (should_check(create_info.enabled_extensions, device, "VK_KHR_swapchain")) { + if (should_check(enabled_extensions, device, "VK_KHR_swapchain")) { if (string_eq(pName, "vkCreateSwapchainKHR")) return to_vkVoidFunction(test_vkCreateSwapchainKHR); if (string_eq(pName, "vkGetSwapchainImagesKHR")) return to_vkVoidFunction(test_vkGetSwapchainImagesKHR); if (string_eq(pName, "vkDestroySwapchainKHR")) return to_vkVoidFunction(test_vkDestroySwapchainKHR); @@ -1733,14 +1740,14 @@ PFN_vkVoidFunction get_device_func(VkDevice device, const char* pName) { if (icd.icd_api_version >= VK_API_VERSION_1_1 && string_eq(pName, "vkGetDeviceGroupSurfacePresentModesKHR")) return to_vkVoidFunction(test_vkGetDeviceGroupSurfacePresentModesKHR); } - if (should_check(create_info.enabled_extensions, device, "VK_KHR_display_swapchain")) { + if (should_check(enabled_extensions, device, "VK_KHR_display_swapchain")) { if (string_eq(pName, "vkCreateSharedSwapchainsKHR")) return to_vkVoidFunction(test_vkCreateSharedSwapchainsKHR); } - if (should_check(create_info.enabled_extensions, device, "VK_KHR_device_group")) { + if (should_check(enabled_extensions, device, "VK_KHR_device_group")) { if (string_eq(pName, "vkGetDeviceGroupSurfacePresentModesKHR")) return to_vkVoidFunction(test_vkGetDeviceGroupSurfacePresentModesKHR); } - if (should_check(create_info.enabled_extensions, device, "VK_EXT_debug_marker")) { + if (should_check(enabled_extensions, device, "VK_EXT_debug_marker")) { if (string_eq(pName, "vkDebugMarkerSetObjectTagEXT")) return to_vkVoidFunction(test_vkDebugMarkerSetObjectTagEXT); if (string_eq(pName, "vkDebugMarkerSetObjectNameEXT")) return to_vkVoidFunction(test_vkDebugMarkerSetObjectNameEXT); if (string_eq(pName, "vkCmdDebugMarkerBeginEXT")) return to_vkVoidFunction(test_vkCmdDebugMarkerBeginEXT); @@ -1757,13 +1764,21 @@ PFN_vkVoidFunction get_device_func(VkDevice device, const char* pName) { if (string_eq(pName, "vkCmdEndDebugUtilsLabelEXT")) return to_vkVoidFunction(test_vkCmdEndDebugUtilsLabelEXT); if (string_eq(pName, "vkCmdInsertDebugUtilsLabelEXT")) return to_vkVoidFunction(test_vkCmdInsertDebugUtilsLabelEXT); } - // look for device functions setup from a test - for (const auto& phys_dev : icd.physical_devices) { - for (const auto& function : phys_dev.known_device_functions) { + if (found_device.found) { + // look for device functions setup from a test + for (const auto& function : icd.physical_devices.at(found_device.phys_dev).known_device_functions) { if (function.name == pName) { return to_vkVoidFunction(function.function); } } + } else { + for (const auto& [handle, phys_dev] : icd.physical_devices) { + for (const auto& function : phys_dev.known_device_functions) { + if (function.name == pName) { + return to_vkVoidFunction(function.function); + } + } + } } return nullptr; } diff --git a/tests/framework/icd/test_icd.h b/tests/framework/icd/test_icd.h index 57ee27d4b..d84f4f8a3 100644 --- a/tests/framework/icd/test_icd.h +++ b/tests/framework/icd/test_icd.h @@ -30,6 +30,7 @@ #include #include #include +#include #include "util/dispatchable_handle.h" #include "util/platform_wsi.h" @@ -130,6 +131,9 @@ struct PhysicalDevice { PhysicalDevice&& finish() { return std::move(*this); } + // Defines the order this physical device appears in vkEnumeratePhysicalDevices + uint32_t iteration_order = 0; + // Objects created from this physical device std::vector device_handles; std::vector device_create_infos; @@ -149,9 +153,14 @@ struct PhysicalDevice { struct PhysicalDeviceGroup { PhysicalDeviceGroup() {} PhysicalDeviceGroup(PhysicalDevice const& physical_device) { physical_device_handles.push_back(&physical_device); } + PhysicalDeviceGroup(PhysicalDevice const* physical_device) { physical_device_handles.push_back(physical_device); } PhysicalDeviceGroup(std::vector const& physical_devices) { physical_device_handles.insert(physical_device_handles.end(), physical_devices.begin(), physical_devices.end()); } + PhysicalDeviceGroup& use_physical_device(PhysicalDevice const* physical_device) { + physical_device_handles.push_back(physical_device); + return *this; + } PhysicalDeviceGroup& use_physical_device(PhysicalDevice const& physical_device) { physical_device_handles.push_back(&physical_device); return *this; @@ -197,7 +206,31 @@ struct TestICD { BUILDER_VECTOR(Extension, instance_extensions, instance_extension) std::vector enabled_instance_extensions; - BUILDER_VECTOR_MOVE_ONLY(PhysicalDevice, physical_devices, physical_device); + std::unordered_map physical_devices; + TestICD& add_physical_device(PhysicalDevice&& physical_device) { + physical_device.iteration_order = physical_devices.size(); + physical_devices.emplace(physical_device.vk_physical_device.handle, std::move(physical_device)); + return *this; + } + + PhysicalDevice& add_and_get_physical_device(PhysicalDevice&& physical_device) { + VkPhysicalDevice pd = physical_device.vk_physical_device.handle; + physical_device.iteration_order = physical_devices.size(); + physical_devices.emplace(physical_device.vk_physical_device.handle, std::move(physical_device)); + return physical_devices.at(pd); + } + + PhysicalDevice& add_physical_device_at_index(size_t index, PhysicalDevice&& physical_device) { + VkPhysicalDevice pd = physical_device.vk_physical_device.handle; + physical_device.iteration_order = index; + for (auto& [handle, phys_dev] : physical_devices) { + if (phys_dev.iteration_order >= index) { + phys_dev.iteration_order++; + } + } + physical_devices.emplace(physical_device.vk_physical_device.handle, std::move(physical_device)); + return physical_devices.at(pd); + } BUILDER_VECTOR(PhysicalDeviceGroup, physical_device_groups, physical_device_group); @@ -237,6 +270,10 @@ struct TestICD { return info; } + // Speedup looking for physical devices by not having to iterate through the entire physical_device map to find a particular + // physical device + std::unordered_map device_to_physical_device_map; + #if defined(WIN32) BUILDER_VALUE(LUID, adapterLUID) #endif // defined(WIN32) diff --git a/tests/loader_alloc_callback_tests.cpp b/tests/loader_alloc_callback_tests.cpp index f9eba0f2d..1a2c3c4a7 100644 --- a/tests/loader_alloc_callback_tests.cpp +++ b/tests/loader_alloc_callback_tests.cpp @@ -714,8 +714,7 @@ TEST(Allocation, CreateInstanceDeviceIntentionalAllocFail) { .set_library_arch(sizeof(void*) == 8 ? "64" : "32")) .set_icd_api_version(VK_API_VERSION_1_1) .add_instance_extension("VK_KHR_get_physical_device_properties2") - .add_physical_device("physical_device_0") - .physical_devices.at(0) + .add_and_get_physical_device("physical_device_0") .add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, false}) .add_extensions({"VK_EXT_one", "VK_EXT_two", "VK_EXT_three", "VK_EXT_four", "VK_EXT_five"}); } @@ -880,7 +879,7 @@ TEST(Allocation, EnumeratePhysicalDevicesIntentionalAllocFail) { auto& driver = env.reset_icd(); for (uint32_t i = 0; i < physical_dev_count; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)) + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)) .add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, false}); } MemoryTracker tracker{{false, 0, true, fail_index}}; @@ -902,7 +901,7 @@ TEST(Allocation, EnumeratePhysicalDevicesIntentionalAllocFail) { ASSERT_EQ(physical_dev_count, returned_physical_count); for (uint32_t i = 0; i < 2; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(physical_dev_count)) + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(physical_dev_count)) .add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, false}); physical_dev_count += 1; } @@ -976,7 +975,7 @@ TEST(Allocation, CreateInstanceDeviceWithDXGIDriverIntentionalAllocFail) { for (uint32_t i = 0; i < 2; i++) { auto& driver = env.get_test_icd(i); - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)) + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)) .add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, false}); } diff --git a/tests/loader_debug_ext_tests.cpp b/tests/loader_debug_ext_tests.cpp index aa6b7f0e7..bb068a82e 100644 --- a/tests/loader_debug_ext_tests.cpp +++ b/tests/loader_debug_ext_tests.cpp @@ -53,8 +53,8 @@ class DebugReportTest : public ::testing::Test { env = std::unique_ptr(new FrameworkEnvironment()); for (uint32_t icd = 0; icd < 3; ++icd) { env->add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_0)); - env->get_test_icd(icd).physical_devices.push_back({}); - env->get_test_icd(icd).physical_devices.push_back({}); + env->get_test_icd(icd).add_physical_device({}); + env->get_test_icd(icd).add_physical_device({}); } // Initialize the expected output allow_any_message = false; @@ -387,8 +387,8 @@ class DebugUtilTest : public ::testing::Test { env = std::unique_ptr(new FrameworkEnvironment()); for (uint32_t icd = 0; icd < 3; ++icd) { env->add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_0)); - env->get_test_icd(icd).physical_devices.push_back({}); - env->get_test_icd(icd).physical_devices.push_back({}); + env->get_test_icd(icd).add_physical_device({}); + env->get_test_icd(icd).add_physical_device({}); } // Initialize the expected output allow_any_message = false; @@ -1041,9 +1041,8 @@ void CheckDeviceFunctions(FrameworkEnvironment& env, bool use_GIPA, bool enable_ TEST(GetProcAddr, DebugFuncsWithTerminator) { FrameworkEnvironment env{}; - auto& driver = - env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).setup_WSI().add_physical_device("physical_device_0"); - driver.physical_devices.at(0).add_extensions({"VK_KHR_swapchain"}); + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).setup_WSI(); + auto& phys_dev = driver.add_and_get_physical_device("physical_device_0").add_extensions({"VK_KHR_swapchain"}); // Hardware doesn't support the debug extensions // Use getDeviceProcAddr & vary enabling the debug extensions @@ -1056,7 +1055,7 @@ TEST(GetProcAddr, DebugFuncsWithTerminator) { // Now set the hardware to support the extensions and run the situations again driver.add_instance_extensions({"VK_EXT_debug_utils", "VK_EXT_debug_report"}); - driver.physical_devices.at(0).add_extensions({"VK_EXT_debug_marker"}); + phys_dev.add_extensions({"VK_EXT_debug_marker"}); // Use getDeviceProcAddr & vary enabling the debug extensions ASSERT_NO_FATAL_FAILURE(CheckDeviceFunctions(env, false, false, true)); @@ -1069,9 +1068,10 @@ TEST(GetProcAddr, DebugFuncsWithTerminator) { TEST(GetProcAddr, DebugFuncsWithTrampoline) { FrameworkEnvironment env{}; - auto& driver = - env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).setup_WSI().add_physical_device("physical_device_0"); - driver.physical_devices.at(0).add_extensions({"VK_KHR_swapchain"}); + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)) + .setup_WSI() + .add_and_get_physical_device("physical_device_0") + .add_extensions({"VK_KHR_swapchain"}); // Hardware doesn't support the debug extensions // Use getDeviceProcAddr & vary enabling the debug extensions @@ -1103,9 +1103,10 @@ TEST(GetProcAddr, DebugFuncsWithTrampoline) { TEST(GetProcAddr, DebugFuncsWithDebugExtsForceAdded) { FrameworkEnvironment env{}; - auto& driver = - env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).setup_WSI().add_physical_device("physical_device_0"); - driver.physical_devices.at(0).add_extensions({"VK_KHR_swapchain"}); + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)) + .setup_WSI() + .add_and_get_physical_device("physical_device_0") + .add_extensions({"VK_KHR_swapchain"}); // Hardware doesn't support the debug extensions // Use getDeviceProcAddr & vary enabling the debug extensions diff --git a/tests/loader_envvar_tests.cpp b/tests/loader_envvar_tests.cpp index 85a23ddf2..a73b5af13 100644 --- a/tests/loader_envvar_tests.cpp +++ b/tests/loader_envvar_tests.cpp @@ -256,7 +256,7 @@ TEST(EnvVarICDOverrideSetup, XDGContainsJsonFile) { TEST(EnvVarICDOverrideSetup, TestOnlyAddDriverEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::add_env_var)); - env.get_test_icd(0).physical_devices.emplace_back("pd0"); + env.get_test_icd(0).add_and_get_physical_device("pd0"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); @@ -272,7 +272,7 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddDriverEnvVar) { TEST(EnvVarICDOverrideSetup, TestOnlyAddDriverEnvVarRunningWithElevatedPrivileges) { FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::add_env_var)); - env.get_test_icd(0).physical_devices.emplace_back("pd0"); + env.get_test_icd(0).add_and_get_physical_device("pd0"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); diff --git a/tests/loader_get_proc_addr_tests.cpp b/tests/loader_get_proc_addr_tests.cpp index a2fe578d8..88c7613b0 100644 --- a/tests/loader_get_proc_addr_tests.cpp +++ b/tests/loader_get_proc_addr_tests.cpp @@ -202,8 +202,9 @@ TEST(GetProcAddr, Verify10FunctionsLoadWithMultipleDrivers) { // and return VK_SUCCESS to maintain previous behavior. TEST(GetDeviceProcAddr, SwapchainFuncsWithTerminator) { FrameworkEnvironment env{}; - auto& driver = - env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).setup_WSI().add_physical_device("physical_device_0"); + auto& test_physical_device = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)) + .setup_WSI() + .add_and_get_physical_device("physical_device_0"); InstWrapper inst(env.vulkan_functions); inst.create_info.add_extension("VK_EXT_debug_utils"); @@ -250,7 +251,7 @@ TEST(GetDeviceProcAddr, SwapchainFuncsWithTerminator) { log.logger.clear(); ASSERT_FALSE(dev_funcs.vkDestroySwapchainKHR); } - driver.physical_devices.at(0).add_extensions({"VK_KHR_swapchain", "VK_KHR_display_swapchain", "VK_EXT_debug_marker"}); + test_physical_device.add_extensions({"VK_KHR_swapchain", "VK_KHR_display_swapchain", "VK_EXT_debug_marker"}); { DeviceWrapper dev{inst}; dev.create_info.add_extensions({"VK_KHR_swapchain", "VK_KHR_display_swapchain", "VK_EXT_debug_marker"}); @@ -328,15 +329,15 @@ TEST(GetProcAddr, PreserveLayerGettingVkCreateDeviceWithNullInstance) { TEST(GetDeviceProcAddr, AppQueries11FunctionsWhileOnlyEnabling10) { FrameworkEnvironment env{}; - auto& driver = + auto& test_physical_device = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)) .set_icd_api_version(VK_API_VERSION_1_1) - .add_physical_device( + .add_and_get_physical_device( PhysicalDevice{}.set_api_version(VK_API_VERSION_1_1).add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME).finish()); std::vector functions = {"vkGetDeviceQueue2", "vkCmdDispatchBase", "vkCreateDescriptorUpdateTemplate"}; for (const auto& f : functions) { - driver.physical_devices.back().add_device_function(VulkanFunction{f, [] {}}); + test_physical_device.add_device_function(VulkanFunction{f, [] {}}); } { // doesn't enable the feature or extension InstWrapper inst{env.vulkan_functions}; @@ -382,15 +383,15 @@ TEST(GetDeviceProcAddr, AppQueries11FunctionsWhileOnlyEnabling10) { TEST(GetDeviceProcAddr, AppQueries12FunctionsWhileOnlyEnabling11) { FrameworkEnvironment env{}; - auto& driver = + auto& test_physical_device = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_2)) .set_icd_api_version(VK_API_VERSION_1_2) - .add_physical_device( + .add_and_get_physical_device( PhysicalDevice{}.set_api_version(VK_API_VERSION_1_2).add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME).finish()); std::vector functions = {"vkCmdDrawIndirectCount", "vkCmdNextSubpass2", "vkGetBufferDeviceAddress", "vkGetDeviceMemoryOpaqueCaptureAddress"}; for (const auto& f : functions) { - driver.physical_devices.back().add_device_function(VulkanFunction{f, [] {}}); + test_physical_device.add_device_function(VulkanFunction{f, [] {}}); } { // doesn't enable the feature or extension InstWrapper inst{env.vulkan_functions}; @@ -439,16 +440,16 @@ TEST(GetDeviceProcAddr, AppQueries12FunctionsWhileOnlyEnabling11) { TEST(GetDeviceProcAddr, AppQueries13FunctionsWhileOnlyEnabling12) { FrameworkEnvironment env{}; - auto& driver = + auto& test_physical_device = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_3)) .set_icd_api_version(VK_API_VERSION_1_3) - .add_physical_device( + .add_and_get_physical_device( PhysicalDevice{}.set_api_version(VK_API_VERSION_1_3).add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME).finish()); std::vector functions = {"vkCreatePrivateDataSlot", "vkGetDeviceBufferMemoryRequirements", "vkCmdWaitEvents2", "vkGetDeviceImageSparseMemoryRequirements"}; for (const auto& f : functions) { - driver.physical_devices.back().add_device_function(VulkanFunction{f, [] {}}); + test_physical_device.add_device_function(VulkanFunction{f, [] {}}); } { // doesn't enable the feature or extension InstWrapper inst{env.vulkan_functions}; diff --git a/tests/loader_layer_tests.cpp b/tests/loader_layer_tests.cpp index 3babc5961..a98850996 100644 --- a/tests/loader_layer_tests.cpp +++ b/tests/loader_layer_tests.cpp @@ -4803,6 +4803,7 @@ TEST(LayerPhysDeviceMod, AddPhysicalDevices) { VkPhysicalDeviceProperties properties{}; properties.apiVersion = VK_API_VERSION_1_2; properties.vendorID = 0x11000000 + (icd << 6); + std::array added_phys_devs; for (uint32_t dev = 0; dev < 3; ++dev) { properties.deviceID = properties.vendorID + dev; properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; @@ -4812,12 +4813,11 @@ TEST(LayerPhysDeviceMod, AddPhysicalDevices) { #else strncpy(properties.deviceName, dev_name.c_str(), VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); #endif - cur_icd.add_physical_device({}); - cur_icd.physical_devices.back().set_properties(properties); + added_phys_devs[dev] = &cur_icd.add_and_get_physical_device({}).set_properties(properties); } - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[0]); - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[1]); - cur_icd.physical_device_groups.back().use_physical_device(cur_icd.physical_devices[2]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[0]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[1]); + cur_icd.physical_device_groups.back().use_physical_device(added_phys_devs[2]); } const uint32_t icd_devices = 6; @@ -4880,6 +4880,7 @@ TEST(LayerPhysDeviceMod, RemovePhysicalDevices) { VkPhysicalDeviceProperties properties{}; properties.apiVersion = VK_API_VERSION_1_2; properties.vendorID = 0x11000000 + (icd << 6); + std::array added_phys_devs; for (uint32_t dev = 0; dev < 3; ++dev) { properties.deviceID = properties.vendorID + dev; properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; @@ -4889,12 +4890,11 @@ TEST(LayerPhysDeviceMod, RemovePhysicalDevices) { #else strncpy(properties.deviceName, dev_name.c_str(), VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); #endif - cur_icd.add_physical_device({}); - cur_icd.physical_devices.back().set_properties(properties); + added_phys_devs[dev] = &cur_icd.add_and_get_physical_device({}).set_properties(properties); } - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[0]); - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[1]); - cur_icd.physical_device_groups.back().use_physical_device(cur_icd.physical_devices[2]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[0]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[1]); + cur_icd.physical_device_groups.back().use_physical_device(added_phys_devs[2]); } const uint32_t icd_devices = 6; @@ -4930,6 +4930,7 @@ TEST(LayerPhysDeviceMod, ReorderPhysicalDevices) { VkPhysicalDeviceProperties properties{}; properties.apiVersion = VK_API_VERSION_1_2; properties.vendorID = 0x11000000 + (icd << 6); + std::array added_phys_devs; for (uint32_t dev = 0; dev < 3; ++dev) { properties.deviceID = properties.vendorID + dev; properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; @@ -4939,12 +4940,11 @@ TEST(LayerPhysDeviceMod, ReorderPhysicalDevices) { #else strncpy(properties.deviceName, dev_name.c_str(), VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); #endif - cur_icd.add_physical_device({}); - cur_icd.physical_devices.back().set_properties(properties); + added_phys_devs[dev] = &cur_icd.add_and_get_physical_device({}).set_properties(properties); } - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[0]); - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[1]); - cur_icd.physical_device_groups.back().use_physical_device(cur_icd.physical_devices[2]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[0]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[1]); + cur_icd.physical_device_groups.back().use_physical_device(added_phys_devs[2]); } const uint32_t icd_devices = 6; @@ -4980,6 +4980,7 @@ TEST(LayerPhysDeviceMod, AddRemoveAndReorderPhysicalDevices) { VkPhysicalDeviceProperties properties{}; properties.apiVersion = VK_API_VERSION_1_2; properties.vendorID = 0x11000000 + (icd << 6); + std::array added_phys_devs; for (uint32_t dev = 0; dev < 3; ++dev) { properties.deviceID = properties.vendorID + dev; properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; @@ -4989,12 +4990,11 @@ TEST(LayerPhysDeviceMod, AddRemoveAndReorderPhysicalDevices) { #else strncpy(properties.deviceName, dev_name.c_str(), VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); #endif - cur_icd.add_physical_device({}); - cur_icd.physical_devices.back().set_properties(properties); + added_phys_devs[dev] = &cur_icd.add_and_get_physical_device({}).set_properties(properties); } - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[0]); - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[1]); - cur_icd.physical_device_groups.back().use_physical_device(cur_icd.physical_devices[2]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[0]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[1]); + cur_icd.physical_device_groups.back().use_physical_device(added_phys_devs[2]); } const uint32_t icd_devices = 6; @@ -5056,6 +5056,7 @@ TEST(LayerPhysDeviceMod, AddPhysicalDeviceGroups) { VkPhysicalDeviceProperties properties{}; properties.apiVersion = VK_API_VERSION_1_2; properties.vendorID = 0x11000000 + (icd << 6); + std::array added_phys_devs; for (uint32_t dev = 0; dev < 3; ++dev) { properties.deviceID = properties.vendorID + dev; properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; @@ -5065,12 +5066,11 @@ TEST(LayerPhysDeviceMod, AddPhysicalDeviceGroups) { #else strncpy(properties.deviceName, dev_name.c_str(), VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); #endif - cur_icd.add_physical_device({}); - cur_icd.physical_devices.back().set_properties(properties); + added_phys_devs[dev] = &cur_icd.add_and_get_physical_device({}).set_properties(properties); } - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[0]); - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[1]); - cur_icd.physical_device_groups.back().use_physical_device(cur_icd.physical_devices[2]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[0]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[1]); + cur_icd.physical_device_groups.back().use_physical_device(added_phys_devs[2]); } const uint32_t icd_groups = 4; @@ -5143,6 +5143,7 @@ TEST(LayerPhysDeviceMod, RemovePhysicalDeviceGroups) { VkPhysicalDeviceProperties properties{}; properties.apiVersion = VK_API_VERSION_1_2; properties.vendorID = 0x11000000 + (icd << 6); + std::array added_phys_devs; for (uint32_t dev = 0; dev < 3; ++dev) { properties.deviceID = properties.vendorID + dev; properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; @@ -5152,12 +5153,11 @@ TEST(LayerPhysDeviceMod, RemovePhysicalDeviceGroups) { #else strncpy(properties.deviceName, dev_name.c_str(), VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); #endif - cur_icd.add_physical_device({}); - cur_icd.physical_devices.back().set_properties(properties); + added_phys_devs[dev] = &cur_icd.add_and_get_physical_device({}).set_properties(properties); } - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[0]); - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[1]); - cur_icd.physical_device_groups.back().use_physical_device(cur_icd.physical_devices[2]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[0]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[1]); + cur_icd.physical_device_groups.back().use_physical_device(added_phys_devs[2]); } const uint32_t icd_groups = 3; @@ -5195,6 +5195,7 @@ TEST(LayerPhysDeviceMod, ReorderPhysicalDeviceGroups) { VkPhysicalDeviceProperties properties{}; properties.apiVersion = VK_API_VERSION_1_2; properties.vendorID = 0x11000000 + (icd << 6); + std::array added_phys_devs; for (uint32_t dev = 0; dev < 3; ++dev) { properties.deviceID = properties.vendorID + dev; properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; @@ -5204,12 +5205,11 @@ TEST(LayerPhysDeviceMod, ReorderPhysicalDeviceGroups) { #else strncpy(properties.deviceName, dev_name.c_str(), VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); #endif - cur_icd.add_physical_device({}); - cur_icd.physical_devices.back().set_properties(properties); + added_phys_devs[dev] = &cur_icd.add_and_get_physical_device({}).set_properties(properties); } - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[0]); - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[1]); - cur_icd.physical_device_groups.back().use_physical_device(cur_icd.physical_devices[2]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[0]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[1]); + cur_icd.physical_device_groups.back().use_physical_device(added_phys_devs[2]); } const uint32_t icd_groups = 4; @@ -5247,6 +5247,7 @@ TEST(LayerPhysDeviceMod, AddRemoveAndReorderPhysicalDeviceGroups) { VkPhysicalDeviceProperties properties{}; properties.apiVersion = VK_API_VERSION_1_2; properties.vendorID = 0x11000000 + (icd << 6); + std::array added_phys_devs; for (uint32_t dev = 0; dev < 3; ++dev) { properties.deviceID = properties.vendorID + dev; properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; @@ -5256,12 +5257,11 @@ TEST(LayerPhysDeviceMod, AddRemoveAndReorderPhysicalDeviceGroups) { #else strncpy(properties.deviceName, dev_name.c_str(), VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); #endif - cur_icd.add_physical_device({}); - cur_icd.physical_devices.back().set_properties(properties); + added_phys_devs[dev] = &cur_icd.add_and_get_physical_device({}).set_properties(properties); } - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[0]); - cur_icd.physical_device_groups.back().use_physical_device(cur_icd.physical_devices[1]); - cur_icd.physical_device_groups.emplace_back(cur_icd.physical_devices[2]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[0]); + cur_icd.physical_device_groups.back().use_physical_device(added_phys_devs[1]); + cur_icd.physical_device_groups.emplace_back(added_phys_devs[2]); } const uint32_t icd_groups = 4; diff --git a/tests/loader_phys_dev_inst_ext_tests.cpp b/tests/loader_phys_dev_inst_ext_tests.cpp index 00c1eaf4c..15bd9251b 100644 --- a/tests/loader_phys_dev_inst_ext_tests.cpp +++ b/tests/loader_phys_dev_inst_ext_tests.cpp @@ -60,7 +60,7 @@ void FillInRandomDeviceProps(VkPhysicalDeviceProperties& props, uint32_t api_ver TEST(LoaderInstPhysDevExts, PhysDevProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -73,7 +73,7 @@ TEST(LoaderInstPhysDevExts, PhysDevProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevProps2KHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -87,9 +87,9 @@ TEST(LoaderInstPhysDevExts, PhysDevProps2KHRNoICDSupport) { TEST(LoaderInstPhysDevExts, PhysDevProps2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_API_VERSION_1_0, 5, 123); + FillInRandomDeviceProps(test_physical_device.properties, VK_API_VERSION_1_0, 5, 123); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -125,9 +125,9 @@ TEST(LoaderInstPhysDevExts, PhysDevProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_1)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_API_VERSION_1_1, 5, 123); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device.properties, VK_API_VERSION_1_1, 5, 123); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -227,9 +227,9 @@ TEST(LoaderInstPhysDevExts, PhysDevProps2KHRInstanceSupports11) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_0)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_API_VERSION_1_0, 5, 123); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device.properties, VK_API_VERSION_1_0, 5, 123); InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -312,8 +312,7 @@ TEST(LoaderInstPhysDevExts, PhysDevProps2Mixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -415,7 +414,7 @@ void FillInRandomFeatures(VkPhysicalDeviceFeatures& feats) { TEST(LoaderInstPhysDevExts, PhysDevFeats2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -428,7 +427,7 @@ TEST(LoaderInstPhysDevExts, PhysDevFeats2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevFeatsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -443,8 +442,8 @@ TEST(LoaderInstPhysDevExts, PhysDevFeats2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomFeatures(env.get_test_icd(0).physical_devices.back().features); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomFeatures(test_physical_device.features); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -473,10 +472,10 @@ TEST(LoaderInstPhysDevExts, PhysDevFeats2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_1)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - env.get_test_icd(0).physical_devices.back().set_api_version(VK_API_VERSION_1_1); - FillInRandomFeatures(env.get_test_icd(0).physical_devices.back().features); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + test_physical_device.set_api_version(VK_API_VERSION_1_1); + FillInRandomFeatures(test_physical_device.features); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -556,9 +555,9 @@ TEST(LoaderInstPhysDevExts, PhysDevFeats2KHRInstanceSupports11) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_0)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomFeatures(env.get_test_icd(0).physical_devices.back().features); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomFeatures(test_physical_device.features); InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -628,8 +627,7 @@ TEST(LoaderInstPhysDevExts, PhysDevFeatsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -678,7 +676,7 @@ void FillInRandomFormatProperties(std::vector& props) { TEST(LoaderInstPhysDevExts, PhysDevFormatProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -692,7 +690,7 @@ TEST(LoaderInstPhysDevExts, PhysDevFormatProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevFormatPropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -708,8 +706,8 @@ TEST(LoaderInstPhysDevExts, PhysDevFormatProps2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomFormatProperties(env.get_test_icd(0).physical_devices.back().format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomFormatProperties(test_physical_device.format_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions( @@ -745,10 +743,10 @@ TEST(LoaderInstPhysDevExts, PhysDevFormatProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_1)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - env.get_test_icd(0).physical_devices.back().set_api_version(VK_API_VERSION_1_1); - FillInRandomFormatProperties(env.get_test_icd(0).physical_devices.back().format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + test_physical_device.set_api_version(VK_API_VERSION_1_1); + FillInRandomFormatProperties(test_physical_device.format_properties); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -834,9 +832,9 @@ TEST(LoaderInstPhysDevExts, PhysDevFormatProps2KHRInstanceSupports11) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomFormatProperties(env.get_test_icd(0).physical_devices.back().format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomFormatProperties(test_physical_device.format_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -912,8 +910,7 @@ TEST(LoaderInstPhysDevExts, PhysDevFormatPropsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -966,7 +963,7 @@ void FillInRandomImageFormatData(VkImageFormatProperties& props) { TEST(LoaderInstPhysDevExts, PhysDevImageFormatProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -980,7 +977,7 @@ TEST(LoaderInstPhysDevExts, PhysDevImageFormatProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevImageFormatPropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -996,8 +993,8 @@ TEST(LoaderInstPhysDevExts, PhysDevImageFormatProps2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomImageFormatData(env.get_test_icd(0).physical_devices.back().image_format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomImageFormatData(test_physical_device.image_format_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -1046,9 +1043,9 @@ TEST(LoaderInstPhysDevExts, PhysDevImageFormatProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomImageFormatData(env.get_test_icd(0).physical_devices.back().image_format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomImageFormatData(test_physical_device.image_format_properties); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -1189,9 +1186,9 @@ TEST(LoaderInstPhysDevExts, PhysDevImageFormatProps2KHRInstanceSupports11) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomImageFormatData(env.get_test_icd(0).physical_devices.back().image_format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomImageFormatData(test_physical_device.image_format_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -1289,8 +1286,7 @@ TEST(LoaderInstPhysDevExts, PhysDevImageFormatPropsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -1349,7 +1345,7 @@ TEST(LoaderInstPhysDevExts, PhysDevImageFormatPropsMixed) { TEST(LoaderInstPhysDevExts, PhysDevMemoryProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -1363,7 +1359,7 @@ TEST(LoaderInstPhysDevExts, PhysDevMemoryProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevMemoryPropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -1393,8 +1389,8 @@ TEST(LoaderInstPhysDevExts, PhysDevMemoryProps2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomMemoryData(env.get_test_icd(0).physical_devices.back().memory_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomMemoryData(test_physical_device.memory_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -1425,9 +1421,9 @@ TEST(LoaderInstPhysDevExts, PhysDevMemoryProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomMemoryData(env.get_test_icd(0).physical_devices.back().memory_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomMemoryData(test_physical_device.memory_properties); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -1508,9 +1504,9 @@ TEST(LoaderInstPhysDevExts, PhysDevMemoryProps2KHRInstanceSupports11) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomMemoryData(env.get_test_icd(0).physical_devices.back().memory_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomMemoryData(test_physical_device.memory_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -1580,8 +1576,7 @@ TEST(LoaderInstPhysDevExts, PhysDevMemoryPropsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -1621,7 +1616,7 @@ TEST(LoaderInstPhysDevExts, PhysDevMemoryPropsMixed) { TEST(LoaderInstPhysDevExts, PhysDevQueueFamilyProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -1635,7 +1630,7 @@ TEST(LoaderInstPhysDevExts, PhysDevQueueFamilyProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevQueueFamilyPropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -1666,8 +1661,8 @@ TEST(LoaderInstPhysDevExts, PhysDevQueueFamilyProps2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - uint32_t num_fam = FillInRandomQueueFamilyData(env.get_test_icd(0).physical_devices.back().queue_family_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + uint32_t num_fam = FillInRandomQueueFamilyData(test_physical_device.queue_family_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -1707,9 +1702,9 @@ TEST(LoaderInstPhysDevExts, PhysDevQueueFamilyProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - uint32_t num_fam = FillInRandomQueueFamilyData(env.get_test_icd(0).physical_devices.back().queue_family_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + uint32_t num_fam = FillInRandomQueueFamilyData(test_physical_device.queue_family_properties); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -1820,9 +1815,9 @@ TEST(LoaderInstPhysDevExts, PhysDevQueueFamilyProps2KHRInstanceSupports11) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - uint32_t num_fam = FillInRandomQueueFamilyData(env.get_test_icd(0).physical_devices.back().queue_family_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + uint32_t num_fam = FillInRandomQueueFamilyData(test_physical_device.queue_family_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -1907,8 +1902,7 @@ TEST(LoaderInstPhysDevExts, PhysDevQueueFamilyPropsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -1956,7 +1950,7 @@ TEST(LoaderInstPhysDevExts, PhysDevQueueFamilyPropsMixed) { TEST(LoaderInstPhysDevExts, PhysDevSparseImageFormatProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -1970,7 +1964,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSparseImageFormatProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevSparseImageFormatPropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -1997,8 +1991,8 @@ TEST(LoaderInstPhysDevExts, PhysDevSparseImageFormatProps2KHRInstanceAndICDSuppo FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomSparseImageFormatData(env.get_test_icd(0).physical_devices.back().sparse_image_format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomSparseImageFormatData(test_physical_device.sparse_image_format_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -2052,9 +2046,9 @@ TEST(LoaderInstPhysDevExts, PhysDevSparseImageFormatProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomSparseImageFormatData(env.get_test_icd(0).physical_devices.back().sparse_image_format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomSparseImageFormatData(test_physical_device.sparse_image_format_properties); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -2207,9 +2201,9 @@ TEST(LoaderInstPhysDevExts, PhysDevSparseImageFormatProps2KHRInstanceSupports11) FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); - FillInRandomSparseImageFormatData(env.get_test_icd(0).physical_devices.back().sparse_image_format_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + FillInRandomSparseImageFormatData(test_physical_device.sparse_image_format_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -2309,8 +2303,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSparseImageFormatPropsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -2378,7 +2371,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSparseImageFormatPropsMixed) { TEST(LoaderInstPhysDevExts, PhysDevExtBufPropsKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -2392,7 +2385,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufPropsKHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevExtBufPropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME); @@ -2415,8 +2408,8 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufProps2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomExtMemoryData(env.get_test_icd(0).physical_devices.back().external_memory_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomExtMemoryData(test_physical_device.external_memory_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME); @@ -2434,7 +2427,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufProps2KHRInstanceAndICDSupport) { VkPhysicalDeviceExternalBufferInfoKHR info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO_KHR}; VkExternalBufferPropertiesKHR props{VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES_KHR}; GetPhysicalDeviceExternalBufferPropertiesKHR(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_memory_properties, props.externalMemoryProperties); + ASSERT_EQ(test_physical_device.external_memory_properties, props.externalMemoryProperties); } // Test vkGetPhysicalDeviceExternalBufferProperties where instance supports, an ICD, and a device under that ICD @@ -2445,9 +2438,9 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 0}); - FillInRandomExtMemoryData(env.get_test_icd(0).physical_devices.back().external_memory_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 0}); + FillInRandomExtMemoryData(test_physical_device.external_memory_properties); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -2465,7 +2458,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufProps2Simple) { VkPhysicalDeviceExternalBufferInfo info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO}; VkExternalBufferProperties props{VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES}; GetPhysicalDeviceExternalBufferProperties(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_memory_properties, props.externalMemoryProperties); + ASSERT_EQ(test_physical_device.external_memory_properties, props.externalMemoryProperties); } { // Now do the same logic but the application didn't enable 1.0 or the extension so they get the emulated call InstWrapper instance(env.vulkan_functions); @@ -2515,7 +2508,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufProps2Simple) { VkPhysicalDeviceExternalBufferInfo info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO}; VkExternalBufferProperties props{VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES}; GetPhysicalDeviceExternalBufferProperties(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_memory_properties, props.externalMemoryProperties); + ASSERT_EQ(test_physical_device.external_memory_properties, props.externalMemoryProperties); ASSERT_FALSE(log.find("Emulating call in ICD")); } } @@ -2555,8 +2548,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufPropsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -2590,8 +2582,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufPropsMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -2627,7 +2618,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtBufPropsMixed) { TEST(LoaderInstPhysDevExts, PhysDevExtSemPropsKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -2641,7 +2632,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemPropsKHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevExtSemPropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME); @@ -2666,8 +2657,8 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemProps2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomExtSemData(env.get_test_icd(0).physical_devices.back().external_semaphore_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomExtSemData(test_physical_device.external_semaphore_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME); @@ -2685,7 +2676,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemProps2KHRInstanceAndICDSupport) { VkPhysicalDeviceExternalSemaphoreInfoKHR info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO_KHR}; VkExternalSemaphorePropertiesKHR props{VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES_KHR}; GetPhysicalDeviceExternalSemaphorePropertiesKHR(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_semaphore_properties, props); + ASSERT_EQ(test_physical_device.external_semaphore_properties, props); } // Test vkGetPhysicalDeviceExternalSemaphoreProperties where instance supports, an ICD, and a device under that ICD @@ -2696,9 +2687,9 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 0}); - FillInRandomExtSemData(env.get_test_icd(0).physical_devices.back().external_semaphore_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 0}); + FillInRandomExtSemData(test_physical_device.external_semaphore_properties); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -2716,7 +2707,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemProps2Simple) { VkPhysicalDeviceExternalSemaphoreInfo info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO}; VkExternalSemaphoreProperties props{VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES}; GetPhysicalDeviceExternalSemaphoreProperties(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_semaphore_properties, props); + ASSERT_EQ(test_physical_device.external_semaphore_properties, props); } { // Now do the same logic but the application didn't enable 1.0 or the extension so they get the emulated call InstWrapper instance(env.vulkan_functions); @@ -2764,7 +2755,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemProps2Simple) { VkPhysicalDeviceExternalSemaphoreInfo info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO}; VkExternalSemaphoreProperties props{VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES}; GetPhysicalDeviceExternalSemaphoreProperties(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_semaphore_properties, props); + ASSERT_EQ(test_physical_device.external_semaphore_properties, props); ASSERT_FALSE(log.find("Emulating call in ICD")); } } @@ -2804,8 +2795,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemPropsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -2839,8 +2829,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemPropsMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -2875,7 +2864,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtSemPropsMixed) { TEST(LoaderInstPhysDevExts, PhysDevExtFencePropsKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -2889,7 +2878,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFencePropsKHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevExtFencePropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME); @@ -2914,8 +2903,8 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFenceProps2KHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomExtFenceData(env.get_test_icd(0).physical_devices.back().external_fence_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomExtFenceData(test_physical_device.external_fence_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME); @@ -2933,7 +2922,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFenceProps2KHRInstanceAndICDSupport) { VkPhysicalDeviceExternalFenceInfoKHR info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO_KHR}; VkExternalFencePropertiesKHR props{VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES_KHR}; GetPhysicalDeviceExternalFencePropertiesKHR(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_fence_properties, props); + ASSERT_EQ(test_physical_device.external_fence_properties, props); } // Test vkGetPhysicalDeviceExternalFenceProperties where instance supports, an ICD, and a device under that ICD @@ -2944,9 +2933,9 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFenceProps2Simple) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).icd_api_version = VK_API_VERSION_1_1; env.get_test_icd(0).add_instance_extension({VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 0}); - FillInRandomExtFenceData(env.get_test_icd(0).physical_devices.back().external_fence_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.extensions.push_back({VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 0}); + FillInRandomExtFenceData(test_physical_device.external_fence_properties); { InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -2964,7 +2953,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFenceProps2Simple) { VkPhysicalDeviceExternalFenceInfo info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO}; VkExternalFenceProperties props{VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES}; GetPhysicalDeviceExternalFenceProperties(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_fence_properties, props); + ASSERT_EQ(test_physical_device.external_fence_properties, props); } { // Now do the same logic but the application didn't enable 1.0 or the extension so they get the emulated call InstWrapper instance(env.vulkan_functions); @@ -3013,7 +3002,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFenceProps2Simple) { VkPhysicalDeviceExternalFenceInfo info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO}; VkExternalFenceProperties props{VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES}; GetPhysicalDeviceExternalFenceProperties(physical_device, &info, &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().external_fence_properties, props); + ASSERT_EQ(test_physical_device.external_fence_properties, props); ASSERT_FALSE(log.find("Emulating call in ICD")); } } @@ -3052,8 +3041,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFencePropsMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -3087,8 +3075,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFencePropsMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -3123,7 +3110,7 @@ TEST(LoaderInstPhysDevExts, PhysDevExtFencePropsMixed) { TEST(LoaderInstPhysDevExts, PhysDevSurfaceCaps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -3137,7 +3124,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceCaps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevSurfaceCaps2KHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME); @@ -3174,10 +3161,10 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceCaps2KHRInstanceAndICDSupport) { Extension third_ext{VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME}; auto& cur_icd = env.get_test_icd(0); cur_icd.add_instance_extensions({first_ext, second_ext, third_ext}); - cur_icd.physical_devices.push_back({}); + auto& test_physical_device = cur_icd.add_and_get_physical_device({}); cur_icd.min_icd_interface_version = 3; cur_icd.enable_icd_wsi = true; - FillInRandomSurfaceCapsData(env.get_test_icd(0).physical_devices.back().surface_capabilities); + FillInRandomSurfaceCapsData(test_physical_device.surface_capabilities); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions( @@ -3257,8 +3244,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceCaps2KHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); cur_dev.extensions.push_back({VK_KHR_SURFACE_EXTENSION_NAME, 0}); cur_dev.extensions.push_back({VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, 0}); @@ -3316,7 +3302,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceCaps2KHRMixed) { TEST(LoaderInstPhysDevExts, PhysDevSurfaceFormats2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -3330,7 +3316,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceFormats2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevSurfaceFormats2KHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME); @@ -3359,10 +3345,10 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceFormats2KHRInstanceAndICDSupport) { Extension third_ext{VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME}; auto& cur_icd = env.get_test_icd(0); cur_icd.add_instance_extensions({first_ext, second_ext, third_ext}); - cur_icd.physical_devices.push_back({}); + auto& test_physical_device = cur_icd.add_and_get_physical_device({}); cur_icd.min_icd_interface_version = 3; cur_icd.enable_icd_wsi = true; - FillInRandomSurfaceFormatsData(env.get_test_icd(0).physical_devices.back().surface_formats); + FillInRandomSurfaceFormatsData(test_physical_device.surface_formats); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions( @@ -3392,10 +3378,10 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceFormats2KHRInstanceAndICDSupport) { std::vector formats{}; uint32_t count_1 = 0; ASSERT_EQ(VK_SUCCESS, GetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &count_1, nullptr)); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().surface_formats.size(), count_1); + ASSERT_EQ(test_physical_device.surface_formats.size(), count_1); formats.resize(count_1); ASSERT_EQ(VK_SUCCESS, GetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &count_1, formats.data())); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().surface_formats.size(), count_1); + ASSERT_EQ(test_physical_device.surface_formats.size(), count_1); VkPhysicalDeviceSurfaceInfo2KHR info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, nullptr, surface}; std::vector formats2{}; @@ -3450,8 +3436,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceFormats2KHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); cur_dev.extensions.push_back({VK_KHR_SURFACE_EXTENSION_NAME, 0}); cur_dev.extensions.push_back({VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, 0}); @@ -3523,7 +3508,7 @@ TEST(LoaderInstPhysDevExts, PhysDevSurfaceFormats2KHRMixed) { TEST(LoaderInstPhysDevExts, PhysDevDispPropsKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -3537,7 +3522,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPropsKHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevDispPropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_DISPLAY_EXTENSION_NAME); @@ -3574,8 +3559,8 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPropsKHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomDisplayPropData(env.get_test_icd(0).physical_devices.back().display_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomDisplayPropData(test_physical_device.display_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); @@ -3593,12 +3578,12 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPropsKHRInstanceAndICDSupport) { std::vector props{}; uint32_t prop_count = 0; ASSERT_EQ(VK_SUCCESS, GetPhysicalDeviceDisplayPropertiesKHR(physical_device, &prop_count, nullptr)); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().display_properties.size(), prop_count); + ASSERT_EQ(test_physical_device.display_properties.size(), prop_count); props.resize(prop_count); ASSERT_EQ(VK_SUCCESS, GetPhysicalDeviceDisplayPropertiesKHR(physical_device, &prop_count, props.data())); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().display_properties.size(), prop_count); + ASSERT_EQ(test_physical_device.display_properties.size(), prop_count); - ASSERT_EQ(props, env.get_test_icd(0).physical_devices.back().display_properties); + ASSERT_EQ(props, test_physical_device.display_properties); } // Test vkGetPhysicalDeviceDisplayPropertiesKHR where instance supports it with some ICDs that both support @@ -3637,8 +3622,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPropsKHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -3672,8 +3656,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPropsKHRMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -3711,7 +3694,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPropsKHRMixed) { TEST(LoaderInstPhysDevExts, PhysDevDispPlanePropsKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -3725,7 +3708,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlanePropsKHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevDispPlanePropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_DISPLAY_EXTENSION_NAME); @@ -3750,8 +3733,8 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlanePropsKHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomDisplayPlanePropData(env.get_test_icd(0).physical_devices.back().display_plane_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomDisplayPlanePropData(test_physical_device.display_plane_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); @@ -3769,12 +3752,12 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlanePropsKHRInstanceAndICDSupport) { std::vector props{}; uint32_t prop_count = 0; ASSERT_EQ(VK_SUCCESS, GetPhysicalDeviceDisplayPlanePropertiesKHR(physical_device, &prop_count, nullptr)); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().display_plane_properties.size(), prop_count); + ASSERT_EQ(test_physical_device.display_plane_properties.size(), prop_count); props.resize(prop_count); ASSERT_EQ(VK_SUCCESS, GetPhysicalDeviceDisplayPlanePropertiesKHR(physical_device, &prop_count, props.data())); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().display_plane_properties.size(), prop_count); + ASSERT_EQ(test_physical_device.display_plane_properties.size(), prop_count); - ASSERT_EQ(props, env.get_test_icd(0).physical_devices.back().display_plane_properties); + ASSERT_EQ(props, test_physical_device.display_plane_properties); } // Test vkGetPhysicalDeviceDisplayPlanePropertiesKHR where instance supports it with some ICDs that both support @@ -3813,8 +3796,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlanePropsKHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -3848,8 +3830,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlanePropsKHRMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -3887,7 +3868,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlanePropsKHRMixed) { TEST(LoaderInstPhysDevExts, GetDispPlaneSupDispsKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -3901,7 +3882,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneSupDispsKHRNoSupport) { TEST(LoaderInstPhysDevExts, GetDispPlaneSupDispsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_DISPLAY_EXTENSION_NAME); @@ -3925,8 +3906,8 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneSupDispsKHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - GenerateRandomDisplays(env.get_test_icd(0).physical_devices.back().displays); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + GenerateRandomDisplays(test_physical_device.displays); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); @@ -3944,12 +3925,12 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneSupDispsKHRInstanceAndICDSupport) { std::vector disps{}; uint32_t disp_count = 0; ASSERT_EQ(VK_SUCCESS, GetDisplayPlaneSupportedDisplaysKHR(physical_device, 0, &disp_count, nullptr)); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().displays.size(), disp_count); + ASSERT_EQ(test_physical_device.displays.size(), disp_count); disps.resize(disp_count); ASSERT_EQ(VK_SUCCESS, GetDisplayPlaneSupportedDisplaysKHR(physical_device, 0, &disp_count, disps.data())); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().displays.size(), disp_count); + ASSERT_EQ(test_physical_device.displays.size(), disp_count); - ASSERT_EQ(disps, env.get_test_icd(0).physical_devices.back().displays); + ASSERT_EQ(disps, test_physical_device.displays); } // Test vkGetDisplayPlaneSupportedDisplaysKHR where instance supports it with some ICDs that both support @@ -3988,8 +3969,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneSupDispsKHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -4023,8 +4003,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneSupDispsKHRMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -4062,7 +4041,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneSupDispsKHRMixed) { TEST(LoaderInstPhysDevExts, GetDispModePropsKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -4075,7 +4054,7 @@ TEST(LoaderInstPhysDevExts, GetDispModePropsKHRNoSupport) { TEST(LoaderInstPhysDevExts, GetDispModePropsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_DISPLAY_EXTENSION_NAME); @@ -4101,8 +4080,8 @@ TEST(LoaderInstPhysDevExts, GetDispModePropsKHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - GenerateRandomDisplayModeProps(env.get_test_icd(0).physical_devices.back().display_mode_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + GenerateRandomDisplayModeProps(test_physical_device.display_mode_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); @@ -4119,12 +4098,12 @@ TEST(LoaderInstPhysDevExts, GetDispModePropsKHRInstanceAndICDSupport) { std::vector props{}; uint32_t props_count = 0; ASSERT_EQ(VK_SUCCESS, GetDisplayModePropertiesKHR(physical_device, VK_NULL_HANDLE, &props_count, nullptr)); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().display_mode_properties.size(), props_count); + ASSERT_EQ(test_physical_device.display_mode_properties.size(), props_count); props.resize(props_count); ASSERT_EQ(VK_SUCCESS, GetDisplayModePropertiesKHR(physical_device, VK_NULL_HANDLE, &props_count, props.data())); - ASSERT_EQ(env.get_test_icd(0).physical_devices.back().display_mode_properties.size(), props_count); + ASSERT_EQ(test_physical_device.display_mode_properties.size(), props_count); - ASSERT_EQ(props, env.get_test_icd(0).physical_devices.back().display_mode_properties); + ASSERT_EQ(props, test_physical_device.display_mode_properties); } // Test vkGetDisplayModePropertiesKHR where instance supports it with some ICDs that both support @@ -4163,8 +4142,7 @@ TEST(LoaderInstPhysDevExts, GetDispModePropsKHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -4197,8 +4175,7 @@ TEST(LoaderInstPhysDevExts, GetDispModePropsKHRMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -4237,7 +4214,7 @@ TEST(LoaderInstPhysDevExts, GetDispModePropsKHRMixed) { TEST(LoaderInstPhysDevExts, GetDispModesKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -4250,7 +4227,7 @@ TEST(LoaderInstPhysDevExts, GetDispModesKHRNoSupport) { TEST(LoaderInstPhysDevExts, GetDispModesKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_DISPLAY_EXTENSION_NAME); @@ -4265,8 +4242,8 @@ TEST(LoaderInstPhysDevExts, GetDispModesKHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.back().display_mode = CreateRandomDisplayMode(); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device.display_mode = CreateRandomDisplayMode(); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); @@ -4283,7 +4260,7 @@ TEST(LoaderInstPhysDevExts, GetDispModesKHRInstanceAndICDSupport) { VkDisplayModeKHR mode{}; VkDisplayModeCreateInfoKHR create_info{VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR}; ASSERT_EQ(VK_SUCCESS, CreateDisplayModeKHR(physical_device, VK_NULL_HANDLE, &create_info, nullptr, &mode)); - ASSERT_EQ(mode, env.get_test_icd(0).physical_devices.back().display_mode); + ASSERT_EQ(mode, test_physical_device.display_mode); } // Test vkCreateDisplayModeKHR where instance supports it with some ICDs that both support @@ -4322,8 +4299,7 @@ TEST(LoaderInstPhysDevExts, GetDispModesKHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -4356,8 +4332,7 @@ TEST(LoaderInstPhysDevExts, GetDispModesKHRMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -4390,7 +4365,7 @@ TEST(LoaderInstPhysDevExts, GetDispModesKHRMixed) { TEST(LoaderInstPhysDevExts, GetDispPlaneCapsKHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -4403,7 +4378,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCapsKHRNoSupport) { TEST(LoaderInstPhysDevExts, GetDispPlaneCapsKHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_DISPLAY_EXTENSION_NAME); @@ -4439,8 +4414,8 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCapsKHRInstanceAndICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); env.get_test_icd(0).add_instance_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({}); - GenerateRandomDisplayPlaneCaps(env.get_test_icd(0).physical_devices.back().display_plane_capabilities); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + GenerateRandomDisplayPlaneCaps(test_physical_device.display_plane_capabilities); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension({VK_KHR_DISPLAY_EXTENSION_NAME}); @@ -4456,7 +4431,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCapsKHRInstanceAndICDSupport) { VkDisplayPlaneCapabilitiesKHR caps{}; ASSERT_EQ(VK_SUCCESS, GetDisplayPlaneCapabilitiesKHR(physical_device, 0, 0, &caps)); - ASSERT_EQ(caps, env.get_test_icd(0).physical_devices.back().display_plane_capabilities); + ASSERT_EQ(caps, test_physical_device.display_plane_capabilities); } // Test vkGetDisplayPlaneCapabilitiesKHR where instance supports it with some ICDs that both support @@ -4495,8 +4470,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCapsKHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 if ((icd == 0 && dev == 1) || icd == 3) { @@ -4529,8 +4503,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCapsKHRMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -4563,7 +4536,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCapsKHRMixed) { TEST(LoaderInstPhysDevExts, PhysDevDispProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -4577,7 +4550,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevDispProps2KHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME); @@ -4595,8 +4568,8 @@ TEST(LoaderInstPhysDevExts, PhysDevDispProps2KHRInstanceAndICDSupport) { Extension first_ext{VK_KHR_DISPLAY_EXTENSION_NAME}; Extension second_ext{VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME}; env.get_test_icd(0).add_instance_extensions({first_ext, second_ext}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomDisplayPropData(env.get_test_icd(0).physical_devices.back().display_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomDisplayPropData(test_physical_device.display_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions({VK_KHR_DISPLAY_EXTENSION_NAME, VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME}); @@ -4668,8 +4641,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispProps2KHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); cur_dev.extensions.push_back({VK_KHR_DISPLAY_EXTENSION_NAME, 0}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 @@ -4723,7 +4695,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispProps2KHRMixed) { TEST(LoaderInstPhysDevExts, PhysDevDispPlaneProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -4737,7 +4709,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlaneProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, PhysDevDispPlaneProps2KHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME); @@ -4755,8 +4727,8 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlaneProps2KHRInstanceAndICDSupport) { Extension first_ext{VK_KHR_DISPLAY_EXTENSION_NAME}; Extension second_ext{VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME}; env.get_test_icd(0).add_instance_extensions({first_ext, second_ext}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomDisplayPlanePropData(env.get_test_icd(0).physical_devices.back().display_plane_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomDisplayPlanePropData(test_physical_device.display_plane_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions({VK_KHR_DISPLAY_EXTENSION_NAME, VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME}); @@ -4827,8 +4799,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlaneProps2KHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); cur_dev.extensions.push_back({VK_KHR_DISPLAY_EXTENSION_NAME, 0}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 @@ -4881,7 +4852,7 @@ TEST(LoaderInstPhysDevExts, PhysDevDispPlaneProps2KHRMixed) { TEST(LoaderInstPhysDevExts, GetDispModeProps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -4894,7 +4865,7 @@ TEST(LoaderInstPhysDevExts, GetDispModeProps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, GetDispModeProps2KHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME); @@ -4911,8 +4882,8 @@ TEST(LoaderInstPhysDevExts, GetDispModeProps2KHRInstanceAndICDSupport) { Extension first_ext{VK_KHR_DISPLAY_EXTENSION_NAME}; Extension second_ext{VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME}; env.get_test_icd(0).add_instance_extensions({first_ext, second_ext}); - env.get_test_icd(0).physical_devices.push_back({}); - GenerateRandomDisplayModeProps(env.get_test_icd(0).physical_devices.back().display_mode_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + GenerateRandomDisplayModeProps(test_physical_device.display_mode_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions({VK_KHR_DISPLAY_EXTENSION_NAME, VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME}); @@ -4981,8 +4952,7 @@ TEST(LoaderInstPhysDevExts, GetDispModeProps2KHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); cur_dev.extensions.push_back({VK_KHR_DISPLAY_EXTENSION_NAME, 0}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 @@ -5033,7 +5003,7 @@ TEST(LoaderInstPhysDevExts, GetDispModeProps2KHRMixed) { TEST(LoaderInstPhysDevExts, GetDispPlaneCaps2KHRNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -5046,7 +5016,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCaps2KHRNoSupport) { TEST(LoaderInstPhysDevExts, GetDispPlaneCaps2KHRNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME); @@ -5063,8 +5033,8 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCaps2KHRInstanceAndICDSupport) { Extension first_ext{VK_KHR_DISPLAY_EXTENSION_NAME}; Extension second_ext{VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME}; env.get_test_icd(0).add_instance_extensions({first_ext, second_ext}); - env.get_test_icd(0).physical_devices.push_back({}); - FillInRandomDisplayPropData(env.get_test_icd(0).physical_devices.back().display_properties); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + FillInRandomDisplayPropData(test_physical_device.display_properties); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions({VK_KHR_DISPLAY_EXTENSION_NAME, VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME}); @@ -5125,8 +5095,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCaps2KHRMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); cur_dev.extensions.push_back({VK_KHR_DISPLAY_EXTENSION_NAME, 0}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 @@ -5173,7 +5142,7 @@ TEST(LoaderInstPhysDevExts, GetDispPlaneCaps2KHRMixed) { TEST(LoaderInstPhysDevExts, AcquireDrmDisplayEXTNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -5186,7 +5155,7 @@ TEST(LoaderInstPhysDevExts, AcquireDrmDisplayEXTNoSupport) { TEST(LoaderInstPhysDevExts, AcquireDrmDisplayEXTNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_EXT_ACQUIRE_DRM_DISPLAY_EXTENSION_NAME); @@ -5203,8 +5172,8 @@ TEST(LoaderInstPhysDevExts, AcquireDrmDisplayEXTInstanceAndICDSupport) { Extension first_ext{VK_KHR_DISPLAY_EXTENSION_NAME}; Extension second_ext{VK_EXT_ACQUIRE_DRM_DISPLAY_EXTENSION_NAME}; env.get_test_icd(0).add_instance_extensions({first_ext, second_ext}); - env.get_test_icd(0).physical_devices.push_back({}); - GenerateRandomDisplays(env.get_test_icd(0).physical_devices.back().displays); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + GenerateRandomDisplays(test_physical_device.displays); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions({VK_KHR_DISPLAY_EXTENSION_NAME, VK_EXT_ACQUIRE_DRM_DISPLAY_EXTENSION_NAME}); @@ -5259,8 +5228,7 @@ TEST(LoaderInstPhysDevExts, AcquireDrmDisplayEXTMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); cur_dev.extensions.push_back({VK_KHR_DISPLAY_EXTENSION_NAME, 0}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 @@ -5294,8 +5262,7 @@ TEST(LoaderInstPhysDevExts, AcquireDrmDisplayEXTMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -5325,7 +5292,7 @@ TEST(LoaderInstPhysDevExts, AcquireDrmDisplayEXTMixed) { TEST(LoaderInstPhysDevExts, GetDrmDisplayEXTNoSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -5338,7 +5305,7 @@ TEST(LoaderInstPhysDevExts, GetDrmDisplayEXTNoSupport) { TEST(LoaderInstPhysDevExts, GetDrmDisplayEXTNoICDSupport) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(0).physical_devices.push_back({}); + env.get_test_icd(0).add_physical_device({}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_EXT_ACQUIRE_DRM_DISPLAY_EXTENSION_NAME); @@ -5355,8 +5322,8 @@ TEST(LoaderInstPhysDevExts, GetDrmDisplayEXTInstanceAndICDSupport) { Extension first_ext{VK_KHR_DISPLAY_EXTENSION_NAME}; Extension second_ext{VK_EXT_ACQUIRE_DRM_DISPLAY_EXTENSION_NAME}; env.get_test_icd(0).add_instance_extensions({first_ext, second_ext}); - env.get_test_icd(0).physical_devices.push_back({}); - GenerateRandomDisplays(env.get_test_icd(0).physical_devices.back().displays); + auto& test_physical_device = env.get_test_icd(0).add_and_get_physical_device({}); + GenerateRandomDisplays(test_physical_device.displays); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extensions({VK_KHR_DISPLAY_EXTENSION_NAME, VK_EXT_ACQUIRE_DRM_DISPLAY_EXTENSION_NAME}); @@ -5372,7 +5339,7 @@ TEST(LoaderInstPhysDevExts, GetDrmDisplayEXTInstanceAndICDSupport) { VkDisplayKHR display = VK_NULL_HANDLE; ASSERT_EQ(VK_SUCCESS, GetDrmDisplayEXT(physical_device, 0, 0, &display)); - ASSERT_EQ(display, env.get_test_icd(0).physical_devices.back().displays[0]); + ASSERT_EQ(display, test_physical_device.displays[0]); } // Test vkGetDrmDisplayEXT where instance supports it with some ICDs that both support @@ -5412,8 +5379,7 @@ TEST(LoaderInstPhysDevExts, GetDrmDisplayEXTMixed) { for (uint32_t dev = 0; dev < dev_counts[icd]; ++dev) { uint32_t device_version = VK_API_VERSION_1_0; - cur_icd.physical_devices.push_back({}); - auto& cur_dev = cur_icd.physical_devices.back(); + auto& cur_dev = cur_icd.add_and_get_physical_device({}); cur_dev.extensions.push_back({VK_KHR_DISPLAY_EXTENSION_NAME, 0}); // 2nd device in ICD 0 and the one device in ICD 3 support the extension and 1.1 @@ -5447,8 +5413,7 @@ TEST(LoaderInstPhysDevExts, GetDrmDisplayEXTMixed) { for (uint32_t icd = 0; icd < max_icd_count; ++icd) { auto& cur_icd = env.get_test_icd(icd); bool found = false; - for (uint32_t pd = 0; pd < dev_counts[icd]; ++pd) { - auto& cur_dev = cur_icd.physical_devices[pd]; + for (auto const& [physical_device_handle, cur_dev] : cur_icd.physical_devices) { // Find the ICD device matching the physical device we're looking at info for so we can compare the // physical devices info with the returned info. if (cur_dev.properties.apiVersion == pd_props.apiVersion && cur_dev.properties.deviceID == pd_props.deviceID && @@ -5481,18 +5446,18 @@ TEST(LoaderInstPhysDevExts, DifferentInstanceExtensions) { // Add 3 drivers each of which supports a different instance extension env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(0).add_instance_extension({VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({"pd0"}); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 0}); + env.get_test_icd(0).add_and_get_physical_device({"pd0"}).extensions.push_back( + {VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(1).add_instance_extension({VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(1).physical_devices.push_back({"pd1"}); - env.get_test_icd(1).physical_devices.back().extensions.push_back({VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 0}); + env.get_test_icd(1).add_and_get_physical_device({"pd1"}).extensions.push_back( + {VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(2).add_instance_extension({VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME}); - env.get_test_icd(2).physical_devices.push_back({"pd2"}); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 0}); + env.get_test_icd(2).add_and_get_physical_device({"pd2"}).extensions.push_back( + {VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, 0}); DebugUtilsLogger log{VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT}; InstWrapper inst{env.vulkan_functions}; @@ -5535,16 +5500,13 @@ TEST(LoaderInstPhysDevExts, DifferentPhysicalDeviceExtensions) { // Add 3 drivers each of which supports a different physical device extension env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); - env.get_test_icd(0).physical_devices.push_back("pd0"); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME, 0}); + env.get_test_icd(0).add_and_get_physical_device("pd0").extensions.push_back({VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); - env.get_test_icd(1).physical_devices.push_back("pd1"); - env.get_test_icd(1).physical_devices.back().extensions.push_back({VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME, 0}); + env.get_test_icd(1).add_and_get_physical_device("pd1").extensions.push_back({VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); - env.get_test_icd(2).physical_devices.push_back("pd2"); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, 0}); + env.get_test_icd(2).add_and_get_physical_device("pd2").extensions.push_back({VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, 0}); DebugUtilsLogger log{VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT}; InstWrapper inst{env.vulkan_functions}; diff --git a/tests/loader_regression_tests.cpp b/tests/loader_regression_tests.cpp index 60b108812..dab31af3d 100644 --- a/tests/loader_regression_tests.cpp +++ b/tests/loader_regression_tests.cpp @@ -335,11 +335,12 @@ TEST(EnumerateDeviceLayerProperties, LayersMatch) { TEST(EnumerateDeviceExtensionProperties, DeviceExtensionEnumerated) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device("physical_device_0"); + auto& test_physical_device = + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_and_get_physical_device("physical_device_0"); std::array device_extensions = {Extension{"MyExtension0", 4}, Extension{"MyExtension1", 7}}; for (auto& ext : device_extensions) { - driver.physical_devices.front().extensions.push_back(ext); + test_physical_device.extensions.push_back(ext); } InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -362,11 +363,12 @@ TEST(EnumerateDeviceExtensionProperties, DeviceExtensionEnumerated) { TEST(EnumerateDeviceExtensionProperties, PropertyCountLessThanAvailable) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device("physical_device_0"); + auto& test_physical_device = + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_and_get_physical_device("physical_device_0"); std::array device_extensions = {Extension{"MyExtension0", 4}, Extension{"MyExtension1", 7}}; for (auto& ext : device_extensions) { - driver.physical_devices.front().extensions.push_back(ext); + test_physical_device.extensions.push_back(ext); } InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -482,10 +484,7 @@ TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentNoExtensions) { std::vector exts = {Extension{"MyDriverExtension0", 4}, Extension{"MyDriverExtension1", 7}, Extension{"MyDriverExtension2", 6}, Extension{"MyDriverExtension3", 10}}; - env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) - .add_physical_device("physical_device_0") - .physical_devices.at(0) - .add_extensions(exts); + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_and_get_physical_device("physical_device_0").add_extensions(exts); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("implicit_layer_name") @@ -501,7 +500,7 @@ TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentNoExtensions) { TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithExtensions) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); + auto& test_physical_device = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_and_get_physical_device({}); std::vector exts; std::vector layer_exts; @@ -518,10 +517,10 @@ TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithExtensions) { auto& layer = env.get_test_layer(); layer.device_extensions = exts; - driver.physical_devices.front().extensions.emplace_back("MyDriverExtension0", 4); - driver.physical_devices.front().extensions.emplace_back("MyDriverExtension1", 7); + test_physical_device.extensions.emplace_back("MyDriverExtension0", 4); + test_physical_device.extensions.emplace_back("MyDriverExtension1", 7); - exts.insert(exts.begin(), driver.physical_devices.front().extensions.begin(), driver.physical_devices.front().extensions.end()); + exts.insert(exts.begin(), test_physical_device.extensions.begin(), test_physical_device.extensions.end()); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -532,7 +531,7 @@ TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithExtensions) { TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithLotsOfExtensions) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); + auto& test_physical_device = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_and_get_physical_device({}); std::vector exts; std::vector layer_exts; @@ -549,12 +548,12 @@ TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithLotsOfExtension auto& layer = env.get_test_layer(); layer.device_extensions = exts; - driver.physical_devices.front().extensions.emplace_back("MyDriverExtension0", 4); - driver.physical_devices.front().extensions.emplace_back("MyDriverExtension1", 7); - driver.physical_devices.front().extensions.emplace_back("MyDriverExtension2", 6); - driver.physical_devices.front().extensions.emplace_back("MyDriverExtension3", 9); + test_physical_device.extensions.emplace_back("MyDriverExtension0", 4); + test_physical_device.extensions.emplace_back("MyDriverExtension1", 7); + test_physical_device.extensions.emplace_back("MyDriverExtension2", 6); + test_physical_device.extensions.emplace_back("MyDriverExtension3", 9); - exts.insert(exts.begin(), driver.physical_devices.front().extensions.begin(), driver.physical_devices.front().extensions.end()); + exts.insert(exts.begin(), test_physical_device.extensions.begin(), test_physical_device.extensions.end()); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -617,7 +616,7 @@ TEST(EnumerateDeviceExtensionProperties, NoDriverExtensionsImplicitLayerPresentW TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithDuplicateExtensions) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); + auto& test_physical_device = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_and_get_physical_device({}); std::vector exts; std::vector layer_exts; @@ -634,14 +633,14 @@ TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithDuplicateExtens auto& layer = env.get_test_layer(); layer.device_extensions = exts; - driver.physical_devices.front().extensions.emplace_back("MyDriverExtension0", 4); - driver.physical_devices.front().extensions.emplace_back("MyDriverExtension1", 7); + test_physical_device.extensions.emplace_back("MyDriverExtension0", 4); + test_physical_device.extensions.emplace_back("MyDriverExtension1", 7); - driver.physical_devices.front().extensions.insert(driver.physical_devices.front().extensions.end(), exts.begin(), exts.end()); + test_physical_device.extensions.insert(test_physical_device.extensions.end(), exts.begin(), exts.end()); exts.emplace_back("MyDriverExtension0", 4); exts.emplace_back("MyDriverExtension1", 7); - driver.physical_devices.front().extensions = exts; + test_physical_device.extensions = exts; InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -652,7 +651,7 @@ TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithDuplicateExtens TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithOnlyDuplicateExtensions) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); + auto& test_physical_device = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_and_get_physical_device({}); std::vector exts; std::vector layer_exts; @@ -669,7 +668,7 @@ TEST(EnumerateDeviceExtensionProperties, ImplicitLayerPresentWithOnlyDuplicateEx auto& layer = env.get_test_layer(); layer.device_extensions = exts; - driver.physical_devices.front().extensions = exts; + test_physical_device.extensions = exts; InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -705,8 +704,8 @@ TEST(EnumeratePhysicalDevices, TwoCall) { const uint32_t real_device_count = 2; for (uint32_t i = 0; i < real_device_count; i++) { - driver.add_physical_device(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)) + .extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); } InstWrapper inst{env.vulkan_functions}; @@ -731,8 +730,8 @@ TEST(EnumeratePhysicalDevices, MatchOneAndTwoCallNumbers) { const uint32_t real_device_count = 3; for (uint32_t i = 0; i < real_device_count; i++) { - driver.add_physical_device(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)) + .extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); } InstWrapper inst1{env.vulkan_functions}; @@ -767,8 +766,8 @@ TEST(EnumeratePhysicalDevices, TwoCallIncomplete) { const uint32_t real_device_count = 2; for (uint32_t i = 0; i < real_device_count; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)) + .extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); } InstWrapper inst{env.vulkan_functions}; @@ -844,7 +843,7 @@ TEST(EnumeratePhysicalDevices, CallTwiceNormal) { auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_min_icd_interface_version(5); for (size_t i = 0; i < 4; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); } InstWrapper inst{env.vulkan_functions}; @@ -870,7 +869,7 @@ TEST(EnumeratePhysicalDevices, CallTwiceIncompleteOnceNormal) { auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_min_icd_interface_version(5); for (size_t i = 0; i < 8; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); } InstWrapper inst{env.vulkan_functions}; @@ -906,7 +905,7 @@ TEST(EnumeratePhysicalDevices, CallThriceSuccessReduce) { auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_min_icd_interface_version(5); for (size_t i = 0; i < 8; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); + driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); } InstWrapper inst{env.vulkan_functions}; @@ -941,8 +940,8 @@ TEST(EnumeratePhysicalDevices, CallThriceAddInBetween) { FrameworkEnvironment env{}; auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_min_icd_interface_version(5); - driver.physical_devices.emplace_back("physical_device_0"); - driver.physical_devices.emplace_back("physical_device_1"); + driver.add_physical_device("physical_device_0"); + driver.add_physical_device("physical_device_1"); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -953,8 +952,8 @@ TEST(EnumeratePhysicalDevices, CallThriceAddInBetween) { ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles_1.data())); ASSERT_EQ(physical_count, returned_physical_count); - driver.physical_devices.emplace_back("physical_device_2"); - driver.physical_devices.emplace_back("physical_device_3"); + driver.add_physical_device("physical_device_2"); + driver.add_physical_device("physical_device_3"); std::vector physical_device_handles_2 = std::vector(returned_physical_count); ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles_2.data())); @@ -981,7 +980,7 @@ TEST(EnumeratePhysicalDevices, CallThriceRemoveInBetween) { auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_min_icd_interface_version(5); for (size_t i = 0; i < 4; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); + driver.add_physical_device(std::string("physical_device_") + std::to_string(i)); } InstWrapper inst{env.vulkan_functions}; @@ -1035,10 +1034,11 @@ TEST(EnumeratePhysicalDevices, CallThriceRemoveInBetween) { TEST(EnumeratePhysicalDevices, MultipleAddRemoves) { FrameworkEnvironment env{}; auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_min_icd_interface_version(5); + auto phys_dev_handle_0 = driver.add_and_get_physical_device("physical_device_0").vk_physical_device.handle; + auto phys_dev_handle_1 = driver.add_and_get_physical_device("physical_device_1").vk_physical_device.handle; + auto phys_dev_handle_2 = driver.add_and_get_physical_device("physical_device_2").vk_physical_device.handle; + auto phys_dev_handle_3 = driver.add_and_get_physical_device("physical_device_3").vk_physical_device.handle; - for (size_t i = 0; i < 4; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - } std::array, 8> physical_dev_handles; InstWrapper inst{env.vulkan_functions}; @@ -1051,7 +1051,7 @@ TEST(EnumeratePhysicalDevices, MultipleAddRemoves) { ASSERT_EQ(physical_count, returned_physical_count); // Delete the 2nd physical device (0, 2, 3) - driver.physical_devices.erase(std::next(driver.physical_devices.begin())); + driver.physical_devices.erase(phys_dev_handle_1); // Query using old number from last call (4), but it should only return 3 physical_count = static_cast(driver.physical_devices.size()); @@ -1061,8 +1061,8 @@ TEST(EnumeratePhysicalDevices, MultipleAddRemoves) { physical_dev_handles[1].resize(returned_physical_count); // Add two new physical devices to the front (A, B, 0, 2, 3) - driver.physical_devices.emplace(driver.physical_devices.begin(), "physical_device_B"); - driver.physical_devices.emplace(driver.physical_devices.begin(), "physical_device_A"); + auto phys_dev_handle_a = driver.add_physical_device_at_index(0, "physical_device_B").vk_physical_device.handle; + auto phys_dev_handle_b = driver.add_physical_device_at_index(1, "physical_device_A").vk_physical_device.handle; // Query using old number from last call (3), but it should be 5 physical_count = static_cast(driver.physical_devices.size()); @@ -1078,7 +1078,7 @@ TEST(EnumeratePhysicalDevices, MultipleAddRemoves) { ASSERT_EQ(physical_count, returned_physical_count); // Delete last two physical devices (A, B, 0, 2) - driver.physical_devices.pop_back(); + driver.physical_devices.erase(phys_dev_handle_3); // Query using old number from last call (5), but it should be 4 physical_count = static_cast(driver.physical_devices.size()); @@ -1091,7 +1091,7 @@ TEST(EnumeratePhysicalDevices, MultipleAddRemoves) { ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_dev_handles[5].data())); // Insert a new physical device (A, B, C, 0, 2) - driver.physical_devices.insert(driver.physical_devices.begin() + 2, "physical_device_C"); + auto phys_dev_handle_c = driver.add_physical_device_at_index(2, "physical_device_C").vk_physical_device.handle; // Query using old number from last call (4), but it should be 5 physical_count = static_cast(driver.physical_devices.size()); @@ -1351,7 +1351,7 @@ TEST(CreateDevice, ConsecutiveCreate) { auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); for (uint32_t i = 0; i < 100; i++) { - driver.physical_devices.emplace_back("physical_device_0"); + driver.add_physical_device("physical_device_0"); } InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -1368,7 +1368,7 @@ TEST(CreateDevice, ConsecutiveCreateWithoutDestruction) { auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); for (uint32_t i = 0; i < 100; i++) { - driver.physical_devices.emplace_back("physical_device_0"); + driver.add_physical_device("physical_device_0"); } InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -1686,14 +1686,16 @@ TEST(EnumeratePhysicalDeviceGroups, OneCall) { .add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); // ICD contains 3 devices in two groups + std::array phys_devices; for (size_t i = 0; i < 3; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; - } - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[1]); - driver.physical_device_groups.emplace_back(driver.physical_devices[2]); + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; + } + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.back().use_physical_device(phys_devices[1]); + driver.physical_device_groups.emplace_back(phys_devices[2]); const uint32_t max_physical_device_count = 3; // Core function @@ -1820,14 +1822,16 @@ TEST(EnumeratePhysicalDeviceGroups, TwoCall) { .add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); // ICD contains 3 devices in two groups + std::array phys_devices; for (size_t i = 0; i < 3; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; - } - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[1]); - driver.physical_device_groups.emplace_back(driver.physical_devices[2]); + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; + } + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.back().use_physical_device(phys_devices[1]); + driver.physical_device_groups.emplace_back(phys_devices[2]); const uint32_t max_physical_device_count = 3; // Core function @@ -1937,14 +1941,16 @@ TEST(EnumeratePhysicalDeviceGroups, TwoCallIncomplete) { .add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); // ICD contains 3 devices in two groups + std::array phys_devices; for (size_t i = 0; i < 3; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; } - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[1]); - driver.physical_device_groups.emplace_back(driver.physical_devices[2]); + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.back().use_physical_device(phys_devices[1]); + driver.physical_device_groups.emplace_back(phys_devices[2]); // Core function { @@ -2038,19 +2044,19 @@ TEST(EnumeratePhysicalDeviceGroups, TestCoreVersusExtensionSameReturns) { .add_instance_extension({VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME}); // Generate the devices + std::array phys_devices; for (size_t i = 0; i < 6; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; } // Generate the starting groups - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.emplace_back(driver.physical_devices[1]); - driver.physical_device_groups.back() - .use_physical_device(driver.physical_devices[2]) - .use_physical_device(driver.physical_devices[3]); - driver.physical_device_groups.emplace_back(driver.physical_devices[4]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[5]); + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.emplace_back(phys_devices[1]); + driver.physical_device_groups.back().use_physical_device(phys_devices[2]).use_physical_device(phys_devices[3]); + driver.physical_device_groups.emplace_back(phys_devices[4]); + driver.physical_device_groups.back().use_physical_device(phys_devices[5]); uint32_t expected_counts[3] = {1, 3, 2}; uint32_t core_group_count = 0; @@ -2126,19 +2132,19 @@ TEST(EnumeratePhysicalDeviceGroups, CallThriceAddGroupInBetween) { .set_icd_api_version(VK_API_VERSION_1_1); // Generate the devices + std::array phys_devices; for (size_t i = 0; i < 7; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; } // Generate the starting groups - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.emplace_back(driver.physical_devices[1]); - driver.physical_device_groups.back() - .use_physical_device(driver.physical_devices[2]) - .use_physical_device(driver.physical_devices[3]); - driver.physical_device_groups.emplace_back(driver.physical_devices[4]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[5]); + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.emplace_back(phys_devices[1]); + driver.physical_device_groups.back().use_physical_device(phys_devices[2]).use_physical_device(phys_devices[3]); + driver.physical_device_groups.emplace_back(phys_devices[4]); + driver.physical_device_groups.back().use_physical_device(phys_devices[5]); uint32_t before_expected_counts[3] = {1, 3, 2}; uint32_t after_expected_counts[4] = {1, 3, 1, 2}; @@ -2161,7 +2167,7 @@ TEST(EnumeratePhysicalDeviceGroups, CallThriceAddGroupInBetween) { } // Insert new group after first two - driver.physical_device_groups.insert(driver.physical_device_groups.begin() + 2, driver.physical_devices[6]); + driver.physical_device_groups.insert(driver.physical_device_groups.begin() + 2, phys_devices[6]); std::vector group_props_after{}; group_props_after.resize(before_group_count, @@ -2221,20 +2227,20 @@ TEST(EnumeratePhysicalDeviceGroups, CallTwiceRemoveGroupInBetween) { .set_icd_api_version(VK_API_VERSION_1_1); // Generate the devices + std::array phys_devices; for (size_t i = 0; i < 7; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; } // Generate the starting groups - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.emplace_back(driver.physical_devices[1]); - driver.physical_device_groups.back() - .use_physical_device(driver.physical_devices[2]) - .use_physical_device(driver.physical_devices[3]); - driver.physical_device_groups.emplace_back(driver.physical_devices[4]); - driver.physical_device_groups.emplace_back(driver.physical_devices[5]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[6]); + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.emplace_back(phys_devices[1]); + driver.physical_device_groups.back().use_physical_device(phys_devices[2]).use_physical_device(phys_devices[3]); + driver.physical_device_groups.emplace_back(phys_devices[4]); + driver.physical_device_groups.emplace_back(phys_devices[5]); + driver.physical_device_groups.back().use_physical_device(phys_devices[6]); uint32_t before_expected_counts[4] = {1, 3, 1, 2}; uint32_t after_expected_counts[3] = {1, 3, 2}; @@ -2308,19 +2314,19 @@ TEST(EnumeratePhysicalDeviceGroups, CallTwiceAddDeviceInBetween) { .set_icd_api_version(VK_API_VERSION_1_1); // Generate the devices + std::array phys_devices; for (size_t i = 0; i < 7; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; } // Generate the starting groups - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.emplace_back(driver.physical_devices[1]); - driver.physical_device_groups.back() - .use_physical_device(driver.physical_devices[2]) - .use_physical_device(driver.physical_devices[3]); - driver.physical_device_groups.emplace_back(driver.physical_devices[4]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[5]); + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.emplace_back(phys_devices[1]); + driver.physical_device_groups.back().use_physical_device(phys_devices[2]).use_physical_device(phys_devices[3]); + driver.physical_device_groups.emplace_back(phys_devices[4]); + driver.physical_device_groups.back().use_physical_device(phys_devices[5]); uint32_t expected_group_count = 3; uint32_t before_expected_counts[3] = {1, 3, 2}; @@ -2342,7 +2348,7 @@ TEST(EnumeratePhysicalDeviceGroups, CallTwiceAddDeviceInBetween) { } // Insert new device to 2nd group - driver.physical_device_groups[1].use_physical_device(driver.physical_devices[6]); + driver.physical_device_groups[1].use_physical_device(phys_devices[6]); std::vector group_props_after{}; group_props_after.resize(expected_group_count, @@ -2394,19 +2400,19 @@ TEST(EnumeratePhysicalDeviceGroups, CallTwiceRemoveDeviceInBetween) { .set_icd_api_version(VK_API_VERSION_1_1); // Generate the devices + std::array phys_devices; for (size_t i = 0; i < 6; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; } // Generate the starting groups - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.emplace_back(driver.physical_devices[1]); - driver.physical_device_groups.back() - .use_physical_device(driver.physical_devices[2]) - .use_physical_device(driver.physical_devices[3]); - driver.physical_device_groups.emplace_back(driver.physical_devices[4]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[5]); + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.emplace_back(phys_devices[1]); + driver.physical_device_groups.back().use_physical_device(phys_devices[2]).use_physical_device(phys_devices[3]); + driver.physical_device_groups.emplace_back(phys_devices[4]); + driver.physical_device_groups.back().use_physical_device(phys_devices[5]); uint32_t before_expected_counts[3] = {1, 3, 2}; uint32_t after_expected_counts[3] = {1, 2, 2}; @@ -2491,19 +2497,19 @@ TEST(EnumeratePhysicalDeviceGroups, MultipleAddRemoves) { .set_icd_api_version(VK_API_VERSION_1_1); // Generate the devices + std::array phys_devices; for (size_t i = 0; i < 9; i++) { - driver.physical_devices.emplace_back(std::string("physical_device_") + std::to_string(i)); - driver.physical_devices.back().properties.apiVersion = VK_API_VERSION_1_1; + auto& test_physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + test_physical_device.properties.apiVersion = VK_API_VERSION_1_1; + phys_devices[i] = &test_physical_device; } // Generate the starting groups - driver.physical_device_groups.emplace_back(driver.physical_devices[0]); - driver.physical_device_groups.emplace_back(driver.physical_devices[1]); - driver.physical_device_groups.back() - .use_physical_device(driver.physical_devices[2]) - .use_physical_device(driver.physical_devices[3]); - driver.physical_device_groups.emplace_back(driver.physical_devices[4]); - driver.physical_device_groups.back().use_physical_device(driver.physical_devices[5]); + driver.physical_device_groups.emplace_back(phys_devices[0]); + driver.physical_device_groups.emplace_back(phys_devices[1]); + driver.physical_device_groups.back().use_physical_device(phys_devices[2]).use_physical_device(phys_devices[3]); + driver.physical_device_groups.emplace_back(phys_devices[4]); + driver.physical_device_groups.back().use_physical_device(phys_devices[5]); uint32_t before_expected_counts[3] = {1, 3, 2}; uint32_t after_add_group_expected_counts[4] = {1, 3, 1, 2}; @@ -2530,7 +2536,7 @@ TEST(EnumeratePhysicalDeviceGroups, MultipleAddRemoves) { } // Insert new group after first two - driver.physical_device_groups.insert(driver.physical_device_groups.begin() + 2, driver.physical_devices[6]); + driver.physical_device_groups.insert(driver.physical_device_groups.begin() + 2, phys_devices[6]); // Should be: 4 Groups { { 0 }, { 1, 2, 3 }, { 6 }, { 4, 5 } } std::vector group_props_after_add_group{}; @@ -2575,9 +2581,7 @@ TEST(EnumeratePhysicalDeviceGroups, MultipleAddRemoves) { } // Add two devices to last group - driver.physical_device_groups.back() - .use_physical_device(driver.physical_devices[7]) - .use_physical_device(driver.physical_devices[8]); + driver.physical_device_groups.back().use_physical_device(phys_devices[7]).use_physical_device(phys_devices[8]); // Should be: 3 Groups { { 2, 3 }, { 6 }, { 4, 5, 7, 8 } } std::vector group_props_after_add_device{}; @@ -2631,44 +2635,40 @@ TEST(EnumeratePhysicalDeviceGroups, FakePNext) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_1)); auto& cur_icd_0 = env.get_test_icd(0); cur_icd_0.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_0.physical_devices.push_back({"pd0"}); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 888, 0xAAA001); - cur_icd_0.physical_devices.push_back({"pd1"}); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, - VK_API_VERSION_1_1, 888, 0xAAA002); - cur_icd_0.physical_devices.push_back({"pd2"}); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 888, 0xAAA003); + auto& test_physical_device_0 = cur_icd_0.add_and_get_physical_device({"pd0"}); + test_physical_device_0.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_0.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 888, + 0xAAA001); + auto& test_physical_device_1 = cur_icd_0.add_and_get_physical_device({"pd1"}); + test_physical_device_1.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_1.properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_API_VERSION_1_1, 888, + 0xAAA002); + auto& test_physical_device_2 = cur_icd_0.add_and_get_physical_device({"pd2"}); + test_physical_device_2.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_2.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 888, + 0xAAA003); cur_icd_0.physical_device_groups.push_back({}); - cur_icd_0.physical_device_groups.back() - .use_physical_device(cur_icd_0.physical_devices[0]) - .use_physical_device(cur_icd_0.physical_devices[2]); - cur_icd_0.physical_device_groups.push_back({cur_icd_0.physical_devices[1]}); + cur_icd_0.physical_device_groups.back().use_physical_device(test_physical_device_0).use_physical_device(test_physical_device_2); + cur_icd_0.physical_device_groups.push_back({test_physical_device_1}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_1)); auto& cur_icd_1 = env.get_test_icd(1); cur_icd_1.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_1.physical_devices.push_back({"pd4"}); - cur_icd_1.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_1.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC001); - cur_icd_1.physical_devices.push_back({"pd5"}); - cur_icd_1.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_1.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC002); - cur_icd_1.physical_devices.push_back({"pd6"}); - cur_icd_1.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_1.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC003); + auto& test_physical_device_4 = cur_icd_1.add_and_get_physical_device({"pd4"}); + test_physical_device_4.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_4.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC001); + auto& test_physical_device_5 = cur_icd_1.add_and_get_physical_device({"pd5"}); + test_physical_device_5.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_5.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC002); + auto& test_physical_device_6 = cur_icd_1.add_and_get_physical_device({"pd6"}); + test_physical_device_6.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_6.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC003); cur_icd_1.physical_device_groups.push_back({}); - cur_icd_1.physical_device_groups.back() - .use_physical_device(cur_icd_1.physical_devices[1]) - .use_physical_device(cur_icd_1.physical_devices[2]); - cur_icd_1.physical_device_groups.push_back({cur_icd_1.physical_devices[0]}); + cur_icd_1.physical_device_groups.back().use_physical_device(test_physical_device_5).use_physical_device(test_physical_device_6); + cur_icd_1.physical_device_groups.push_back({test_physical_device_4}); InstWrapper inst(env.vulkan_functions); inst.create_info.set_api_version(VK_API_VERSION_1_1); @@ -2755,10 +2755,9 @@ TEST(ExtensionManual, ToolingProperties) { { // core FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA, VK_API_VERSION_1_3)) - .add_physical_device({}) .set_supports_tooling_info_core(true) .add_tooling_property(icd_tool_props) - .physical_devices.back() + .add_and_get_physical_device({}) .properties.apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0); InstWrapper inst{env.vulkan_functions}; @@ -2804,45 +2803,44 @@ TEST(SortedPhysicalDevices, DevicesSortEnabled10NoAppExt) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({"pd0"}); - env.get_test_icd(0).physical_devices.back().set_pci_bus(7); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_1, 888, 0xAAA001); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - env.get_test_icd(0).physical_devices.push_back({"pd1"}); - env.get_test_icd(0).physical_devices.back().set_pci_bus(3); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, - VK_API_VERSION_1_1, 888, 0xAAA002); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_0 = env.get_test_icd(0).add_and_get_physical_device({"pd0"}); + test_physical_device_0.set_pci_bus(7); + FillInRandomDeviceProps(test_physical_device_0.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 888, + 0xAAA001); + test_physical_device_0.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_1 = env.get_test_icd(0).add_and_get_physical_device({"pd1"}); + test_physical_device_1.set_pci_bus(3); + FillInRandomDeviceProps(test_physical_device_1.properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_API_VERSION_1_1, 888, + 0xAAA002); + test_physical_device_1.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(1).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(1).physical_devices.push_back({"pd2"}); - env.get_test_icd(1).physical_devices.back().set_pci_bus(0); - FillInRandomDeviceProps(env.get_test_icd(1).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_0, - 1, 0xBBBB001); - env.get_test_icd(1).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_2 = env.get_test_icd(1).add_and_get_physical_device({"pd2"}); + test_physical_device_2.set_pci_bus(0); + FillInRandomDeviceProps(test_physical_device_2.properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_0, 1, 0xBBBB001); + test_physical_device_2.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(2).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(2).physical_devices.push_back({"pd3"}); - env.get_test_icd(2).physical_devices.back().set_pci_bus(1); - FillInRandomDeviceProps(env.get_test_icd(2).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_1, 75, 0xCCCC001); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - env.get_test_icd(2).physical_devices.push_back({"pd4"}); - env.get_test_icd(2).physical_devices.back().set_pci_bus(4); - FillInRandomDeviceProps(env.get_test_icd(2).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_0, 75, 0xCCCC002); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_3 = env.get_test_icd(2).add_and_get_physical_device({"pd3"}); + test_physical_device_3.set_pci_bus(1); + FillInRandomDeviceProps(test_physical_device_3.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC001); + test_physical_device_3.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_4 = env.get_test_icd(2).add_and_get_physical_device({"pd4"}); + test_physical_device_4.set_pci_bus(4); + FillInRandomDeviceProps(test_physical_device_4.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_0, 75, + 0xCCCC002); + test_physical_device_4.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(3).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(3).physical_devices.push_back({"pd5"}); - env.get_test_icd(3).physical_devices.back().set_pci_bus(0); - FillInRandomDeviceProps(env.get_test_icd(3).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, - VK_API_VERSION_1_1, 6940, 0xDDDD001); - env.get_test_icd(3).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_5 = env.get_test_icd(3).add_and_get_physical_device({"pd5"}); + test_physical_device_5.set_pci_bus(0); + FillInRandomDeviceProps(test_physical_device_5.properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, VK_API_VERSION_1_1, 6940, + 0xDDDD001); + test_physical_device_5.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); InstWrapper instance(env.vulkan_functions); instance.CheckCreate(); @@ -2912,45 +2910,44 @@ TEST(SortedPhysicalDevices, DevicesSortEnabled10AppExt) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({"pd0"}); - env.get_test_icd(0).physical_devices.back().set_pci_bus(7); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_1, 888, 0xAAA001); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - env.get_test_icd(0).physical_devices.push_back({"pd1"}); - env.get_test_icd(0).physical_devices.back().set_pci_bus(3); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, - VK_API_VERSION_1_1, 888, 0xAAA002); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_0 = env.get_test_icd(0).add_and_get_physical_device({"pd0"}); + test_physical_device_0.set_pci_bus(7); + FillInRandomDeviceProps(test_physical_device_0.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 888, + 0xAAA001); + test_physical_device_0.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_1 = env.get_test_icd(0).add_and_get_physical_device({"pd1"}); + test_physical_device_1.set_pci_bus(3); + FillInRandomDeviceProps(test_physical_device_1.properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_API_VERSION_1_1, 888, + 0xAAA002); + test_physical_device_1.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(1).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(1).physical_devices.push_back({"pd2"}); - env.get_test_icd(1).physical_devices.back().set_pci_bus(0); - FillInRandomDeviceProps(env.get_test_icd(1).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_0, - 1, 0xBBBB001); - env.get_test_icd(1).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_2 = env.get_test_icd(1).add_and_get_physical_device({"pd2"}); + test_physical_device_2.set_pci_bus(0); + FillInRandomDeviceProps(test_physical_device_2.properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_0, 1, 0xBBBB001); + test_physical_device_2.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(2).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(2).physical_devices.push_back({"pd3"}); - env.get_test_icd(2).physical_devices.back().set_pci_bus(1); - FillInRandomDeviceProps(env.get_test_icd(2).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_1, 75, 0xCCCC001); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - env.get_test_icd(2).physical_devices.push_back({"pd4"}); - env.get_test_icd(2).physical_devices.back().set_pci_bus(4); - FillInRandomDeviceProps(env.get_test_icd(2).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_0, 75, 0xCCCC002); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_3 = env.get_test_icd(2).add_and_get_physical_device({"pd3"}); + test_physical_device_3.set_pci_bus(1); + FillInRandomDeviceProps(test_physical_device_3.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC001); + test_physical_device_3.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_4 = env.get_test_icd(2).add_and_get_physical_device({"pd4"}); + test_physical_device_4.set_pci_bus(4); + FillInRandomDeviceProps(test_physical_device_4.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_0, 75, + 0xCCCC002); + test_physical_device_4.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(3).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(3).physical_devices.push_back({"pd5"}); - env.get_test_icd(3).physical_devices.back().set_pci_bus(0); - FillInRandomDeviceProps(env.get_test_icd(3).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, - VK_API_VERSION_1_1, 6940, 0xDDDD001); - env.get_test_icd(3).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_5 = env.get_test_icd(3).add_and_get_physical_device({"pd5"}); + test_physical_device_5.set_pci_bus(0); + FillInRandomDeviceProps(test_physical_device_5.properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, VK_API_VERSION_1_1, 6940, + 0xDDDD001); + test_physical_device_5.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -3035,48 +3032,47 @@ TEST(SortedPhysicalDevices, DevicesSortEnabled11) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(0).set_icd_api_version(VK_API_VERSION_1_1); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({"pd0"}); - env.get_test_icd(0).physical_devices.back().set_pci_bus(7); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_0, 888, 0xAAA001); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - env.get_test_icd(0).physical_devices.push_back({"pd1"}); - env.get_test_icd(0).physical_devices.back().set_pci_bus(3); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, - VK_API_VERSION_1_0, 888, 0xAAA002); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_0 = env.get_test_icd(0).add_and_get_physical_device({"pd0"}); + test_physical_device_0.set_pci_bus(7); + FillInRandomDeviceProps(test_physical_device_0.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_0, 888, + 0xAAA001); + test_physical_device_0.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_1 = env.get_test_icd(0).add_and_get_physical_device({"pd1"}); + test_physical_device_1.set_pci_bus(3); + FillInRandomDeviceProps(test_physical_device_1.properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_API_VERSION_1_0, 888, + 0xAAA002); + test_physical_device_1.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(1).set_icd_api_version(VK_API_VERSION_1_1); env.get_test_icd(1).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(1).physical_devices.push_back({"pd2"}); - env.get_test_icd(1).physical_devices.back().set_pci_bus(0); - FillInRandomDeviceProps(env.get_test_icd(1).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_0, - 1, 0xBBBB001); - env.get_test_icd(1).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_2 = env.get_test_icd(1).add_and_get_physical_device({"pd2"}); + test_physical_device_2.set_pci_bus(0); + FillInRandomDeviceProps(test_physical_device_2.properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_0, 1, 0xBBBB001); + test_physical_device_2.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(2).set_icd_api_version(VK_API_VERSION_1_1); env.get_test_icd(2).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(2).physical_devices.push_back({"pd3"}); - env.get_test_icd(2).physical_devices.back().set_pci_bus(1); - FillInRandomDeviceProps(env.get_test_icd(2).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_1, 75, 0xCCCC001); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - env.get_test_icd(2).physical_devices.push_back({"pd4"}); - env.get_test_icd(2).physical_devices.back().set_pci_bus(4); - FillInRandomDeviceProps(env.get_test_icd(2).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_1, 75, 0xCCCC002); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_3 = env.get_test_icd(2).add_and_get_physical_device({"pd3"}); + test_physical_device_3.set_pci_bus(1); + FillInRandomDeviceProps(test_physical_device_3.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC001); + test_physical_device_3.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_4 = env.get_test_icd(2).add_and_get_physical_device({"pd4"}); + test_physical_device_4.set_pci_bus(4); + FillInRandomDeviceProps(test_physical_device_4.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC002); + test_physical_device_4.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); env.get_test_icd(3).set_icd_api_version(VK_API_VERSION_1_1); env.get_test_icd(3).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(3).physical_devices.push_back({"pd5"}); - env.get_test_icd(3).physical_devices.back().set_pci_bus(0); - FillInRandomDeviceProps(env.get_test_icd(3).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, - VK_API_VERSION_1_1, 6940, 0xDDDD001); - env.get_test_icd(3).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_5 = env.get_test_icd(3).add_and_get_physical_device({"pd5"}); + test_physical_device_5.set_pci_bus(0); + FillInRandomDeviceProps(test_physical_device_5.properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, VK_API_VERSION_1_1, 6940, + 0xDDDD001); + test_physical_device_5.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); InstWrapper instance(env.vulkan_functions); instance.create_info.set_api_version(VK_API_VERSION_1_1); @@ -3163,39 +3159,38 @@ TEST(SortedPhysicalDevices, DevicesSortedDisabled) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(0).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(0).physical_devices.push_back({"pd0"}); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_0, 888, 0xAAA001); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - env.get_test_icd(0).physical_devices.push_back({"pd1"}); - FillInRandomDeviceProps(env.get_test_icd(0).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, - VK_API_VERSION_1_0, 888, 0xAAA002); - env.get_test_icd(0).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_0 = env.get_test_icd(0).add_and_get_physical_device({"pd0"}); + FillInRandomDeviceProps(test_physical_device_0.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_0, 888, + 0xAAA001); + test_physical_device_0.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_1 = env.get_test_icd(0).add_and_get_physical_device({"pd1"}); + FillInRandomDeviceProps(test_physical_device_1.properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_API_VERSION_1_0, 888, + 0xAAA002); + test_physical_device_1.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(1).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(1).physical_devices.push_back({"pd2"}); - FillInRandomDeviceProps(env.get_test_icd(1).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_0, - 1, 0xBBBB001); - env.get_test_icd(1).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_2 = env.get_test_icd(1).add_and_get_physical_device({"pd2"}); + FillInRandomDeviceProps(test_physical_device_2.properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_0, 1, 0xBBBB001); + test_physical_device_2.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(2).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(2).physical_devices.push_back({"pd3"}); - FillInRandomDeviceProps(env.get_test_icd(2).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_0, 75, 0xCCCC001); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - env.get_test_icd(2).physical_devices.push_back({"pd4"}); - FillInRandomDeviceProps(env.get_test_icd(2).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, - VK_API_VERSION_1_0, 75, 0xCCCC002); - env.get_test_icd(2).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_3 = env.get_test_icd(2).add_and_get_physical_device({"pd3"}); + FillInRandomDeviceProps(test_physical_device_3.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_0, 75, + 0xCCCC001); + test_physical_device_3.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_4 = env.get_test_icd(2).add_and_get_physical_device({"pd4"}); + FillInRandomDeviceProps(test_physical_device_4.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_0, 75, + 0xCCCC002); + test_physical_device_4.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_0)); env.get_test_icd(3).add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); - env.get_test_icd(3).physical_devices.push_back({"pd5"}); - FillInRandomDeviceProps(env.get_test_icd(3).physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, - VK_API_VERSION_1_0, 6940, 0xDDDD001); - env.get_test_icd(3).physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + auto& test_physical_device_5 = env.get_test_icd(3).add_and_get_physical_device({"pd5"}); + FillInRandomDeviceProps(test_physical_device_5.properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, VK_API_VERSION_1_0, 6940, + 0xDDDD001); + test_physical_device_5.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); InstWrapper instance(env.vulkan_functions); instance.create_info.add_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); @@ -3265,68 +3260,63 @@ TEST(SortedPhysicalDevices, DeviceGroupsSortedEnabled) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); auto& cur_icd_0 = env.get_test_icd(0); cur_icd_0.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_0.physical_devices.push_back({"pd0"}); - cur_icd_0.physical_devices.back().set_pci_bus(7); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 888, 0xAAA001); - cur_icd_0.physical_devices.push_back({"pd1"}); - cur_icd_0.physical_devices.back().set_pci_bus(3); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, - VK_API_VERSION_1_1, 888, 0xAAA002); - cur_icd_0.physical_devices.push_back({"pd2"}); - cur_icd_0.physical_devices.back().set_pci_bus(6); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 888, 0xAAA003); + auto& test_physical_device_0 = cur_icd_0.add_and_get_physical_device({"pd0"}); + test_physical_device_0.set_pci_bus(7); + test_physical_device_0.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_0.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 888, + 0xAAA001); + auto& test_physical_device_1 = cur_icd_0.add_and_get_physical_device({"pd1"}); + test_physical_device_1.set_pci_bus(3); + test_physical_device_1.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_1.properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_API_VERSION_1_1, 888, + 0xAAA002); + auto& test_physical_device_2 = cur_icd_0.add_and_get_physical_device({"pd2"}); + test_physical_device_2.set_pci_bus(6); + test_physical_device_2.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_2.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 888, + 0xAAA003); cur_icd_0.physical_device_groups.push_back({}); - cur_icd_0.physical_device_groups.back() - .use_physical_device(cur_icd_0.physical_devices[0]) - .use_physical_device(cur_icd_0.physical_devices[2]); - cur_icd_0.physical_device_groups.push_back({cur_icd_0.physical_devices[1]}); + cur_icd_0.physical_device_groups.back().use_physical_device(test_physical_device_0).use_physical_device(test_physical_device_2); + cur_icd_0.physical_device_groups.push_back({test_physical_device_1}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); auto& cur_icd_1 = env.get_test_icd(1); cur_icd_1.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_1.physical_devices.push_back({"pd3"}); - cur_icd_1.physical_devices.back().set_pci_bus(0); - cur_icd_1.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_1.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_1, 1, - 0xBBBB001); + auto& test_physical_device_3 = cur_icd_1.add_and_get_physical_device({"pd3"}); + test_physical_device_3.set_pci_bus(0); + test_physical_device_3.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_3.properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_1, 1, 0xBBBB001); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); auto& cur_icd_2 = env.get_test_icd(2); cur_icd_2.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_2.physical_devices.push_back({"pd4"}); - cur_icd_2.physical_devices.back().set_pci_bus(1); - cur_icd_2.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_2.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC001); - cur_icd_2.physical_devices.push_back({"pd5"}); - cur_icd_2.physical_devices.back().set_pci_bus(4); - cur_icd_2.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_2.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC002); - cur_icd_2.physical_devices.push_back({"pd6"}); - cur_icd_2.physical_devices.back().set_pci_bus(2); - cur_icd_2.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_2.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC003); + auto& test_physical_device_4 = cur_icd_2.add_and_get_physical_device({"pd4"}); + test_physical_device_4.set_pci_bus(1); + test_physical_device_4.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_4.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC001); + auto& test_physical_device_5 = cur_icd_2.add_and_get_physical_device({"pd5"}); + test_physical_device_5.set_pci_bus(4); + test_physical_device_5.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_5.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC002); + auto& test_physical_device_6 = cur_icd_2.add_and_get_physical_device({"pd6"}); + test_physical_device_6.set_pci_bus(2); + test_physical_device_6.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_6.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC003); cur_icd_2.physical_device_groups.push_back({}); - cur_icd_2.physical_device_groups.back() - .use_physical_device(cur_icd_2.physical_devices[1]) - .use_physical_device(cur_icd_2.physical_devices[2]); - cur_icd_2.physical_device_groups.push_back({cur_icd_2.physical_devices[0]}); + cur_icd_2.physical_device_groups.back().use_physical_device(test_physical_device_5).use_physical_device(test_physical_device_6); + cur_icd_2.physical_device_groups.push_back({test_physical_device_4}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); auto& cur_icd_3 = env.get_test_icd(3); cur_icd_3.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_3.physical_devices.push_back({"pd7"}); - cur_icd_3.physical_devices.back().set_pci_bus(0); - cur_icd_3.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_3.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, VK_API_VERSION_1_1, - 6940, 0xDDDD001); + auto& test_physical_device_7 = cur_icd_3.add_and_get_physical_device({"pd7"}); + test_physical_device_7.set_pci_bus(0); + test_physical_device_7.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_7.properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, VK_API_VERSION_1_1, 6940, + 0xDDDD001); InstWrapper inst(env.vulkan_functions); inst.create_info.set_api_version(VK_API_VERSION_1_1); @@ -3458,60 +3448,55 @@ TEST(SortedPhysicalDevices, DeviceGroupsSortedDisabled) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); auto& cur_icd_0 = env.get_test_icd(0); cur_icd_0.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_0.physical_devices.push_back({"pd0"}); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 888, 0xAAA001); - cur_icd_0.physical_devices.push_back({"pd1"}); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, - VK_API_VERSION_1_1, 888, 0xAAA002); - cur_icd_0.physical_devices.push_back({"pd2"}); - cur_icd_0.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_0.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 888, 0xAAA003); + auto& test_physical_device_0 = cur_icd_0.add_and_get_physical_device({"pd0"}); + test_physical_device_0.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_0.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 888, + 0xAAA001); + auto& test_physical_device_1 = cur_icd_0.add_and_get_physical_device({"pd1"}); + test_physical_device_1.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_1.properties, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_API_VERSION_1_1, 888, + 0xAAA002); + auto& test_physical_device_2 = cur_icd_0.add_and_get_physical_device({"pd2"}); + test_physical_device_2.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_2.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 888, + 0xAAA003); cur_icd_0.physical_device_groups.push_back({}); - cur_icd_0.physical_device_groups.back() - .use_physical_device(cur_icd_0.physical_devices[0]) - .use_physical_device(cur_icd_0.physical_devices[2]); - cur_icd_0.physical_device_groups.push_back({cur_icd_0.physical_devices[1]}); + cur_icd_0.physical_device_groups.back().use_physical_device(test_physical_device_0).use_physical_device(test_physical_device_2); + cur_icd_0.physical_device_groups.push_back({test_physical_device_1}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); auto& cur_icd_1 = env.get_test_icd(1); cur_icd_1.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_1.physical_devices.push_back({"pd3"}); - cur_icd_1.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_1.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_1, 1, - 0xBBBB001); + auto& test_physical_device_3 = cur_icd_1.add_and_get_physical_device({"pd3"}); + test_physical_device_3.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_3.properties, VK_PHYSICAL_DEVICE_TYPE_CPU, VK_API_VERSION_1_1, 1, 0xBBBB001); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); auto& cur_icd_2 = env.get_test_icd(2); cur_icd_2.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_2.physical_devices.push_back({"pd4"}); - cur_icd_2.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_2.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC001); - cur_icd_2.physical_devices.push_back({"pd5"}); - cur_icd_2.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_2.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC002); - cur_icd_2.physical_devices.push_back({"pd6"}); - cur_icd_2.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_2.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, - 75, 0xCCCC003); + auto& test_physical_device_4 = cur_icd_2.add_and_get_physical_device({"pd4"}); + test_physical_device_4.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_4.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC001); + auto& test_physical_device_5 = cur_icd_2.add_and_get_physical_device({"pd5"}); + test_physical_device_5.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_5.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC002); + auto& test_physical_device_6 = cur_icd_2.add_and_get_physical_device({"pd6"}); + test_physical_device_6.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_6.properties, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_API_VERSION_1_1, 75, + 0xCCCC003); cur_icd_2.physical_device_groups.push_back({}); - cur_icd_2.physical_device_groups.back() - .use_physical_device(cur_icd_2.physical_devices[1]) - .use_physical_device(cur_icd_2.physical_devices[2]); - cur_icd_2.physical_device_groups.push_back({cur_icd_2.physical_devices[0]}); + cur_icd_2.physical_device_groups.back().use_physical_device(test_physical_device_5).use_physical_device(test_physical_device_6); + cur_icd_2.physical_device_groups.push_back({test_physical_device_4}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)); auto& cur_icd_3 = env.get_test_icd(3); cur_icd_3.set_icd_api_version(VK_API_VERSION_1_1); - cur_icd_3.physical_devices.push_back({"pd7"}); - cur_icd_3.physical_devices.back().extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); - FillInRandomDeviceProps(cur_icd_3.physical_devices.back().properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, VK_API_VERSION_1_1, - 6940, 0xDDDD001); + auto& test_physical_device_7 = cur_icd_3.add_and_get_physical_device({"pd7"}); + test_physical_device_7.extensions.push_back({VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, 0}); + FillInRandomDeviceProps(test_physical_device_7.properties, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, VK_API_VERSION_1_1, 6940, + 0xDDDD001); InstWrapper inst(env.vulkan_functions); inst.create_info.set_api_version(VK_API_VERSION_1_1); @@ -3665,10 +3650,10 @@ TEST(PortabilityICDConfiguration, PortabilityAndRegularICD) { auto& driver0 = env.get_test_icd(0); auto& driver1 = env.get_test_icd(1); - driver0.physical_devices.emplace_back("physical_device_0"); + driver0.add_and_get_physical_device("physical_device_0"); driver0.max_icd_interface_version = 1; - driver1.physical_devices.emplace_back("portability_physical_device_1"); + driver1.add_and_get_physical_device("portability_physical_device_1"); driver1.max_icd_interface_version = 1; { // enable portability extension and flag InstWrapper inst{env.vulkan_functions}; @@ -3746,10 +3731,10 @@ TEST(PortabilityICDConfiguration, PortabilityAndRegularICDCheckFlagsPassedIntoIC auto& driver0 = env.get_test_icd(0); auto& driver1 = env.get_test_icd(1); - driver0.physical_devices.emplace_back("physical_device_0"); + driver0.add_and_get_physical_device("physical_device_0"); driver0.max_icd_interface_version = 1; - driver1.physical_devices.emplace_back("portability_physical_device_1"); + driver1.add_and_get_physical_device("portability_physical_device_1"); driver1.add_instance_extension("VK_KHR_portability_enumeration"); driver1.max_icd_interface_version = 1; @@ -3909,10 +3894,11 @@ TEST(DuplicateRegistryEntries, Drivers) { TEST(LibraryLoading, SystemLocations) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_library_path_type(LibraryPathType::default_search_paths)) - .add_physical_device({}); + auto& test_physical_device = + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_library_path_type(LibraryPathType::default_search_paths)) + .add_and_get_physical_device({}); const char* fake_ext_name = "VK_FAKE_extension"; - driver.physical_devices.back().add_extension(fake_ext_name); + test_physical_device.add_extension(fake_ext_name); const char* layer_name = "TestLayer"; env.add_explicit_layer( @@ -3996,11 +3982,11 @@ TEST(ManifestDiscovery, AppleBundles) { FrameworkEnvironment env{}; env.setup_macos_bundle(); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA).set_discovery_type(ManifestDiscoveryType::macos_bundle)); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.at(0).properties.deviceID = 1337; + auto& test_physical_device_0 = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device_0.properties.deviceID = 1337; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); - env.get_test_icd(1).physical_devices.push_back({}); - env.get_test_icd(1).physical_devices.at(0).properties.deviceID = 9999; + auto& test_physical_device_1 = env.get_test_icd(1).add_and_get_physical_device({}); + test_physical_device_1.properties.deviceID = 9999; InstWrapper inst{env.vulkan_functions}; ASSERT_NO_FATAL_FAILURE(inst.CheckCreate()); @@ -4010,7 +3996,7 @@ TEST(ManifestDiscovery, AppleBundles) { // Verify that this is the 'right' GPU, aka the one from the bundle VkPhysicalDeviceProperties props{}; inst->vkGetPhysicalDeviceProperties(physical_devices[0], &props); - ASSERT_EQ(env.get_test_icd(0).physical_devices.at(0).properties.deviceID, props.deviceID); + ASSERT_EQ(test_physical_device_0.properties.deviceID, props.deviceID); } // Add two drivers, one to the bundle and one using the driver env-var @@ -4018,11 +4004,13 @@ TEST(ManifestDiscovery, AppleBundlesEnvVarActive) { FrameworkEnvironment env{}; env.setup_macos_bundle(); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA).set_discovery_type(ManifestDiscoveryType::macos_bundle)); - env.get_test_icd(0).physical_devices.push_back({}); - env.get_test_icd(0).physical_devices.at(0).properties.deviceID = 1337; + + auto& test_physical_device_0 = env.get_test_icd(0).add_and_get_physical_device({}); + test_physical_device_0.properties.deviceID = 1337; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA).set_discovery_type(ManifestDiscoveryType::env_var)); - env.get_test_icd(1).physical_devices.push_back({}); - env.get_test_icd(1).physical_devices.at(0).properties.deviceID = 9999; + auto& test_physical_device_1 = env.get_test_icd(1).add_and_get_physical_device({}); + test_physical_device_1.properties.deviceID = 9999; InstWrapper inst{env.vulkan_functions}; ASSERT_NO_FATAL_FAILURE(inst.CheckCreate()); @@ -4032,7 +4020,7 @@ TEST(ManifestDiscovery, AppleBundlesEnvVarActive) { // Verify that this is the 'right' GPU, aka the one from the env-var VkPhysicalDeviceProperties props{}; inst->vkGetPhysicalDeviceProperties(physical_devices[0], &props); - ASSERT_EQ(env.get_test_icd(1).physical_devices.at(0).properties.deviceID, props.deviceID); + ASSERT_EQ(test_physical_device_1.properties.deviceID, props.deviceID); } #endif @@ -4064,9 +4052,9 @@ TEST(LayerCreatesDevice, Basic) { TEST(LayerCreatesDevice, DifferentPhysicalDevice) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); - env.get_test_icd(0).physical_devices.emplace_back("Device0"); + env.get_test_icd(0).add_and_get_physical_device("Device0"); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); - env.get_test_icd(1).physical_devices.emplace_back("Device1"); + env.get_test_icd(1).add_and_get_physical_device("Device1"); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("implicit_layer_name") @@ -4242,14 +4230,13 @@ TEST(InvalidManifest, Layer) { inst.CheckCreate(); } #if defined(WIN32) -void add_dxgi_adapter(FrameworkEnvironment& env, std::filesystem::path const& name, LUID luid, uint32_t vendor_id) { +VkPhysicalDevice add_dxgi_adapter(FrameworkEnvironment& env, std::filesystem::path const& name, LUID luid, uint32_t vendor_id) { auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_6).set_discovery_type(ManifestDiscoveryType::null_dir)); driver.set_min_icd_interface_version(5); driver.set_max_icd_interface_version(6); driver.setup_WSI(); driver.set_icd_api_version(VK_API_VERSION_1_1); - driver.physical_devices.emplace_back(name.string()); - auto& pd0 = driver.physical_devices.back(); + auto& pd0 = driver.add_and_get_physical_device(name.string()); pd0.properties.apiVersion = VK_API_VERSION_1_1; driver.set_adapterLUID(luid); @@ -4276,6 +4263,7 @@ void add_dxgi_adapter(FrameworkEnvironment& env, std::filesystem::path const& na } else { pAdapter->add_driver_manifest_path(env.get_icd_manifest_path(env.icds.size() - 1)); } + return pd0.vk_physical_device.handle; } TEST(EnumerateAdapterPhysicalDevices, SameAdapterLUID_reordered) { @@ -4288,7 +4276,7 @@ TEST(EnumerateAdapterPhysicalDevices, SameAdapterLUID_reordered) { // b) then in the reverse order to the drivers insertion into the test framework add_dxgi_adapter(env, "physical_device_2", LUID{10, 100}, 2); add_dxgi_adapter(env, "physical_device_1", LUID{20, 200}, 1); - add_dxgi_adapter(env, "physical_device_0", LUID{10, 100}, 2); + auto phys_dev_handle = add_dxgi_adapter(env, "physical_device_0", LUID{10, 100}, 2); { uint32_t returned_physical_count = 0; @@ -4332,7 +4320,8 @@ TEST(EnumerateAdapterPhysicalDevices, SameAdapterLUID_reordered) { } // Set the first physical device that is enumerated to be a 'layered' driver so it should be swapped with the first physical // device - env.get_test_icd(2).physical_devices.back().layered_driver_underlying_api = VK_LAYERED_DRIVER_UNDERLYING_API_D3D12_MSFT; + env.get_test_icd(2).physical_devices.at(phys_dev_handle).layered_driver_underlying_api = + VK_LAYERED_DRIVER_UNDERLYING_API_D3D12_MSFT; { uint32_t returned_physical_count = 0; InstWrapper inst{env.vulkan_functions}; @@ -4384,12 +4373,13 @@ TEST(EnumerateAdapterPhysicalDevices, SameAdapterLUID_same_order) { // Physical devices are enumerated: // a) first in the order of LUIDs showing up in DXGIAdapter list // b) then in the reverse order to the drivers insertion into the test framework - add_dxgi_adapter(env, "physical_device_2", LUID{10, 100}, 2); + auto d3d12_physical_device = add_dxgi_adapter(env, "physical_device_2", LUID{10, 100}, 2); add_dxgi_adapter(env, "physical_device_1", LUID{20, 200}, 1); add_dxgi_adapter(env, "physical_device_0", LUID{10, 100}, 2); - // Set the last physical device that is enumerated last to be a 'layered' physical device - no swapping should occur - env.get_test_icd(0).physical_devices.back().layered_driver_underlying_api = VK_LAYERED_DRIVER_UNDERLYING_API_D3D12_MSFT; + // Set the physical device that is enumerated last to be a 'layered' physical device - no swapping should occur + env.get_test_icd(0).physical_devices.at(d3d12_physical_device).layered_driver_underlying_api = + VK_LAYERED_DRIVER_UNDERLYING_API_D3D12_MSFT; uint32_t returned_physical_count = 0; InstWrapper inst{env.vulkan_functions}; diff --git a/tests/loader_threading_tests.cpp b/tests/loader_threading_tests.cpp index 482bd5fb6..91ce4e20d 100644 --- a/tests/loader_threading_tests.cpp +++ b/tests/loader_threading_tests.cpp @@ -88,7 +88,7 @@ TEST(Threading, InstanceCreateDestroyLoop) { uint32_t num_loops_try_get_instance_proc_addr = 5; uint32_t num_loops_try_get_device_proc_addr = 100; - driver.physical_devices.emplace_back("physical_device_0") + driver.add_and_get_physical_device("physical_device_0") .known_device_functions.push_back({"vkCmdBindPipeline", to_vkVoidFunction(test_vkCmdBindPipeline)}); std::vector instance_creation_threads; @@ -112,7 +112,7 @@ TEST(Threading, DeviceCreateDestroyLoop) { uint32_t num_loops_create_destroy_device = 1000; uint32_t num_loops_try_get_device_proc_addr = 5; - driver.physical_devices.emplace_back("physical_device_0").known_device_functions = { + driver.add_and_get_physical_device("physical_device_0").known_device_functions = { {"vkCmdBindPipeline", to_vkVoidFunction(test_vkCmdBindPipeline)}, {"vkCmdBindDescriptorSets", to_vkVoidFunction(test_vkCmdBindDescriptorSets)}, {"vkCmdBindVertexBuffers", to_vkVoidFunction(test_vkCmdBindVertexBuffers)}, diff --git a/tests/loader_unknown_ext_tests.cpp b/tests/loader_unknown_ext_tests.cpp index 162ceabd3..08fbe6cbb 100644 --- a/tests/loader_unknown_ext_tests.cpp +++ b/tests/loader_unknown_ext_tests.cpp @@ -335,12 +335,13 @@ using layer_implementation_physical_device_functions = layer_implementation_func TEST(UnknownFunction, PhysicalDeviceFunction) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); + auto& test_physical_device = + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_and_get_physical_device({}); uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; std::vector function_names; add_function_names(function_names, function_count); - fill_implementation_functions(driver.physical_devices.at(0).custom_physical_device_functions, function_names, + fill_implementation_functions(test_physical_device.custom_physical_device_functions, function_names, custom_physical_device_functions{}, function_count); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -359,13 +360,15 @@ TEST(UnknownFunction, PhysicalDeviceFunctionMultipleDriverSupport) { add_function_names(function_names, function_count); // used to identify the GPUs - driver_0.physical_devices.emplace_back("physical_device_0").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; - driver_1.physical_devices.emplace_back("physical_device_1").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + auto& test_physical_driver_0 = driver_0.add_and_get_physical_device("physical_device_0"); + test_physical_driver_0.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + auto& test_physical_driver_1 = driver_1.add_and_get_physical_device("physical_device_1"); + test_physical_driver_1.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; for (uint32_t i = 0; i < function_count / 10; i++) { - fill_implementation_functions(driver_0.physical_devices.at(0).custom_physical_device_functions, function_names, + fill_implementation_functions(test_physical_driver_0.custom_physical_device_functions, function_names, custom_physical_device_functions{}, 5, i * 10); - fill_implementation_functions(driver_1.physical_devices.at(0).custom_physical_device_functions, function_names, + fill_implementation_functions(test_physical_driver_1.custom_physical_device_functions, function_names, custom_physical_device_functions{}, 5, i * 10 + 5); } InstWrapper inst{env.vulkan_functions}; @@ -397,12 +400,14 @@ TEST(UnknownFunctionDeathTests, PhysicalDeviceFunctionErrorPath) { add_function_names(function_names, 1); // used to identify the GPUs - driver_0.physical_devices.emplace_back("physical_device_0").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; - driver_1.physical_devices.emplace_back("physical_device_1").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + auto& test_physical_driver_0 = driver_0.add_and_get_physical_device("physical_device_0"); + test_physical_driver_0.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + auto& test_physical_driver_1 = driver_1.add_and_get_physical_device("physical_device_1"); + test_physical_driver_1.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; function_names.push_back(std::string("vkNotIntRealFuncTEST_0")); custom_physical_device_functions funcs{}; - driver_0.physical_devices.at(0).custom_physical_device_functions.push_back( + test_physical_driver_0.custom_physical_device_functions.push_back( VulkanFunction{function_names.back(), to_vkVoidFunction(funcs.func_zero)}); InstWrapper inst{env.vulkan_functions}; @@ -454,12 +459,14 @@ TEST(UnknownFunction, PhysicalDeviceFunctionMultipleDriverSupportWithImplicitLay add_function_names(function_names, function_count); // used to identify the GPUs - driver_0.physical_devices.emplace_back("physical_device_0").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; - driver_1.physical_devices.emplace_back("physical_device_1").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + auto& test_physical_device_0 = driver_0.add_and_get_physical_device("physical_device_0"); + test_physical_device_0.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + auto& test_physical_device_1 = driver_1.add_and_get_physical_device("physical_device_1"); + test_physical_device_1.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; for (uint32_t i = 0; i < function_count / 10; i++) { - fill_implementation_functions(driver_0.physical_devices.at(0).custom_physical_device_functions, function_names, + fill_implementation_functions(test_physical_device_0.custom_physical_device_functions, function_names, custom_physical_device_functions{}, 5, i * 10); - fill_implementation_functions(driver_1.physical_devices.at(0).custom_physical_device_functions, function_names, + fill_implementation_functions(test_physical_device_1.custom_physical_device_functions, function_names, custom_physical_device_functions{}, 5, i * 10 + 5); } @@ -515,11 +522,12 @@ TEST(UnknownFunction, PhysicalDeviceFunctionWithImplicitLayerInterception) { TEST(UnknownFunction, PhysicalDeviceFunctionDriverSupportWithImplicitLayerInterception) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); + auto& test_physical_device = + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_and_get_physical_device({}); uint32_t function_count = 100; std::vector function_names; add_function_names(function_names, function_count); - fill_implementation_functions(driver.physical_devices.at(0).custom_physical_device_functions, function_names, + fill_implementation_functions(test_physical_device.custom_physical_device_functions, function_names, layer_implementation_physical_device_functions{}, function_count); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept") @@ -543,7 +551,7 @@ TEST(UnknownFunction, PhysicalDeviceFunctionWithMultipleImplicitLayersIntercepti std::vector function_names; uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; add_function_names(function_names, function_count); - driver.physical_devices.emplace_back("physical_device_0"); + auto& test_physical_device = driver.add_and_get_physical_device("physical_device_0"); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept_0") @@ -560,7 +568,7 @@ TEST(UnknownFunction, PhysicalDeviceFunctionWithMultipleImplicitLayersIntercepti auto& layer_1 = env.get_test_layer(1); layer_1.set_use_gipa_GetPhysicalDeviceProcAddr(false); for (uint32_t i = 0; i < function_count / 10; i++) { - fill_implementation_functions(driver.physical_devices.at(0).custom_physical_device_functions, function_names, + fill_implementation_functions(test_physical_device.custom_physical_device_functions, function_names, layer_implementation_physical_device_functions{}, 5, i * 10); fill_phys_dev_intercept_functions(layer_0, function_names, layer_intercept_physical_device_functions{}, 5, i * 10); fill_phys_dev_intercept_functions(layer_1, function_names, layer_intercept_physical_device_functions{}, 5, i * 10 + 5); @@ -628,18 +636,18 @@ void unknown_function_test_impl(std::vector const& flags) { using layer_intercept_functions_type = layer_intercept_functions; FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); + auto& pd = driver.add_and_get_physical_device({}); uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; std::vector function_names; add_function_names(function_names, function_count); if (has_flag(flags, TestConfig::add_layer_interception)) { - fill_implementation_functions(driver.physical_devices.back().known_device_functions, function_names, - layer_implementation_functions_type{}, function_count); + fill_implementation_functions(pd.known_device_functions, function_names, layer_implementation_functions_type{}, + function_count); } else { - fill_implementation_functions(driver.physical_devices.back().known_device_functions, function_names, - custom_functions_type{}, function_count); + fill_implementation_functions(pd.known_device_functions, function_names, custom_functions_type{}, function_count); } TestLayer* layer_ptr = nullptr; if (has_flag(flags, TestConfig::add_layer_implementation) || has_flag(flags, TestConfig::add_layer_interception)) { @@ -1089,8 +1097,7 @@ struct D {}; TEST(UnknownFunction, PhysicalDeviceFunctionTwoLayerInterception) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); - PhysicalDevice& pd = driver.physical_devices.back(); + PhysicalDevice& pd = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_and_get_physical_device({}); UnknownFunction f{"vkFunc1"}; Functions::three::physical_device::add_to_driver(f, pd); @@ -1122,8 +1129,8 @@ TEST(UnknownFunction, PhysicalDeviceFunctionTwoLayerInterception) { TEST(UnknownFunction, ManyCombinations) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); - PhysicalDevice& physical_device = driver.physical_devices.back(); + PhysicalDevice& physical_device = + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_and_get_physical_device({}); std::vector unknown_funcs; unknown_funcs.emplace_back("vkZero_uint32_uint32_0"); diff --git a/tests/loader_version_tests.cpp b/tests/loader_version_tests.cpp index e097ff3ce..a0f0c1c73 100644 --- a/tests/loader_version_tests.cpp +++ b/tests/loader_version_tests.cpp @@ -101,8 +101,8 @@ TEST(ICDInterfaceVersion2Plus, l5_icd5) { TEST(ICDInterfaceVersion2PlusEnumerateAdapterPhysicalDevices, version_6_in_drivers_registry) { FrameworkEnvironment env{}; auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_ENUMERATE_ADAPTER_PHYSICAL_DEVICES)); - driver.physical_devices.emplace_back("physical_device_1"); - driver.physical_devices.emplace_back("physical_device_0"); + driver.add_and_get_physical_device("physical_device_1"); + driver.add_and_get_physical_device("physical_device_0"); uint32_t physical_count = static_cast(driver.physical_devices.size()); uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); std::vector physical_device_handles = std::vector(physical_count); @@ -132,8 +132,8 @@ TEST(ICDInterfaceVersion2PlusEnumerateAdapterPhysicalDevices, version_6) { // The loader will only attempt to sort physical devices on an ICD if version 6 of the interface is supported. // This version provides the vk_icdEnumerateAdapterPhysicalDevices function. auto& driver = env.get_test_icd(0); - driver.physical_devices.emplace_back("physical_device_1"); - driver.physical_devices.emplace_back("physical_device_0"); + driver.add_and_get_physical_device("physical_device_1"); + driver.add_and_get_physical_device("physical_device_0"); uint32_t physical_count = 2; uint32_t returned_physical_count = physical_count; std::vector physical_device_handles{physical_count}; @@ -183,8 +183,8 @@ TEST(ICDInterfaceVersion2, EnumAdapters2) { auto& driver = env.add_icd(TestICDDetails{TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA}.set_discovery_type(ManifestDiscoveryType::null_dir)); InstWrapper inst{env.vulkan_functions}; - driver.physical_devices.emplace_back("physical_device_1"); - driver.physical_devices.emplace_back("physical_device_0"); + driver.add_and_get_physical_device("physical_device_1"); + driver.add_and_get_physical_device("physical_device_0"); uint32_t physical_count = static_cast(driver.physical_devices.size()); uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); std::vector physical_device_handles = std::vector(physical_count); @@ -212,7 +212,7 @@ TEST(ICDInterfaceVersion2PlusEnumerateAdapterPhysicalDevices, VerifyPhysDevResul .set_icd_api_version(VK_API_VERSION_1_1); const std::vector physical_device_names = {"physical_device_4", "physical_device_3", "physical_device_2", "physical_device_1", "physical_device_0"}; - for (const auto& dev_name : physical_device_names) driver.physical_devices.push_back(dev_name); + for (const auto& dev_name : physical_device_names) driver.add_physical_device(dev_name); auto& known_driver = known_driver_list.at(2); // which drive this test pretends to be DXGI_ADAPTER_DESC1 desc1{}; @@ -256,13 +256,16 @@ TEST(ICDInterfaceVersion2PlusEnumerateAdapterPhysicalDevices, VerifyGroupResults .set_icd_api_version(VK_API_VERSION_1_1); const std::vector physical_device_names = {"physical_device_4", "physical_device_3", "physical_device_2", "physical_device_1", "physical_device_0"}; - for (const auto& dev_name : physical_device_names) { - driver.physical_devices.push_back(dev_name); - } - driver.physical_device_groups.emplace_back(driver.physical_devices[0]).use_physical_device(driver.physical_devices[1]); - driver.physical_device_groups.emplace_back(driver.physical_devices[2]); - driver.physical_device_groups.emplace_back(driver.physical_devices[3]).use_physical_device(driver.physical_devices[4]); + auto& test_physical_device_0 = driver.add_and_get_physical_device(physical_device_names[0]); + auto& test_physical_device_1 = driver.add_and_get_physical_device(physical_device_names[1]); + auto& test_physical_device_2 = driver.add_and_get_physical_device(physical_device_names[2]); + auto& test_physical_device_3 = driver.add_and_get_physical_device(physical_device_names[3]); + auto& test_physical_device_4 = driver.add_and_get_physical_device(physical_device_names[4]); + + driver.physical_device_groups.emplace_back(test_physical_device_0).use_physical_device(test_physical_device_1); + driver.physical_device_groups.emplace_back(test_physical_device_2); + driver.physical_device_groups.emplace_back(test_physical_device_3).use_physical_device(test_physical_device_4); auto& known_driver = known_driver_list.at(2); // which driver this test pretends to be DXGI_ADAPTER_DESC1 desc1{}; @@ -330,17 +333,17 @@ TEST(MultipleICDConfig, Basic) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); - env.get_test_icd(0).physical_devices.emplace_back("physical_device_0"); - env.get_test_icd(1).physical_devices.emplace_back("physical_device_1"); - env.get_test_icd(2).physical_devices.emplace_back("physical_device_2"); + auto& phys_dev_0 = env.get_test_icd(0).add_and_get_physical_device("physical_device_0"); + auto& phys_dev_1 = env.get_test_icd(1).add_and_get_physical_device("physical_device_1"); + auto& phys_dev_2 = env.get_test_icd(2).add_and_get_physical_device("physical_device_2"); - env.get_test_icd(0).physical_devices.at(0).properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; - env.get_test_icd(1).physical_devices.at(0).properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; - env.get_test_icd(2).physical_devices.at(0).properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_CPU; + phys_dev_0.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + phys_dev_1.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + phys_dev_2.properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_CPU; - std::string("dev0").copy(env.get_test_icd(0).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); - std::string("dev1").copy(env.get_test_icd(1).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); - std::string("dev2").copy(env.get_test_icd(2).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev0").copy(phys_dev_0.properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev1").copy(phys_dev_1.properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev2").copy(phys_dev_2.properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -349,9 +352,9 @@ TEST(MultipleICDConfig, Basic) { uint32_t phys_dev_count = 3; ASSERT_EQ(env.vulkan_functions.vkEnumeratePhysicalDevices(inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); ASSERT_EQ(phys_dev_count, 3U); - ASSERT_EQ(env.get_test_icd(0).physical_devices.at(0).properties.deviceType, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU); - ASSERT_EQ(env.get_test_icd(1).physical_devices.at(0).properties.deviceType, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU); - ASSERT_EQ(env.get_test_icd(2).physical_devices.at(0).properties.deviceType, VK_PHYSICAL_DEVICE_TYPE_CPU); + ASSERT_EQ(phys_dev_0.properties.deviceType, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU); + ASSERT_EQ(phys_dev_1.properties.deviceType, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU); + ASSERT_EQ(phys_dev_2.properties.deviceType, VK_PHYSICAL_DEVICE_TYPE_CPU); } TEST(MultipleDriverConfig, DifferentICDInterfaceVersions) { @@ -361,11 +364,11 @@ TEST(MultipleDriverConfig, DifferentICDInterfaceVersions) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); TestICD& icd0 = env.get_test_icd(0); - icd0.physical_devices.emplace_back("physical_device_0"); + icd0.add_and_get_physical_device("physical_device_0"); icd0.max_icd_interface_version = 1; TestICD& icd1 = env.get_test_icd(1); - icd1.physical_devices.emplace_back("physical_device_1"); + icd1.add_and_get_physical_device("physical_device_1"); icd1.min_icd_interface_version = 2; icd1.max_icd_interface_version = 5; @@ -388,18 +391,18 @@ TEST(MultipleDriverConfig, DifferentICDsWithDevices) { // tests add multiple devices to a single ICD, this just makes sure the loader combines // device info across multiple drivers properly. TestICD& icd0 = env.get_test_icd(0); - icd0.physical_devices.emplace_back("physical_device_0"); + icd0.add_and_get_physical_device("physical_device_0"); icd0.min_icd_interface_version = 5; icd0.max_icd_interface_version = 5; TestICD& icd1 = env.get_test_icd(1); - icd1.physical_devices.emplace_back("physical_device_1"); - icd1.physical_devices.emplace_back("physical_device_2"); + icd1.add_and_get_physical_device("physical_device_1"); + icd1.add_and_get_physical_device("physical_device_2"); icd1.min_icd_interface_version = 5; icd1.max_icd_interface_version = 5; TestICD& icd2 = env.get_test_icd(2); - icd2.physical_devices.emplace_back("physical_device_3"); + icd2.add_and_get_physical_device("physical_device_3"); icd2.min_icd_interface_version = 5; icd2.max_icd_interface_version = 5; @@ -426,26 +429,26 @@ TEST(MultipleDriverConfig, DifferentICDsWithDevicesAndGroups) { // ICD 0 : No 1.1 support (so 1 device will become 1 group in loader) TestICD& icd0 = env.get_test_icd(0); - icd0.physical_devices.emplace_back("physical_device_0"); + icd0.add_and_get_physical_device("physical_device_0"); icd0.min_icd_interface_version = 5; icd0.max_icd_interface_version = 5; icd0.set_icd_api_version(VK_API_VERSION_1_0); // ICD 1 : 1.1 support (with 1 group with 2 devices) TestICD& icd1 = env.get_test_icd(1); - icd1.physical_devices.emplace_back("physical_device_1").set_api_version(VK_API_VERSION_1_1); - icd1.physical_devices.emplace_back("physical_device_2").set_api_version(VK_API_VERSION_1_1); - icd1.physical_device_groups.emplace_back(icd1.physical_devices[0]); - icd1.physical_device_groups.back().use_physical_device(icd1.physical_devices[1]); + auto& pd1 = icd1.add_and_get_physical_device("physical_device_1").set_api_version(VK_API_VERSION_1_1); + auto& pd2 = icd1.add_and_get_physical_device("physical_device_2").set_api_version(VK_API_VERSION_1_1); + icd1.physical_device_groups.emplace_back(pd1); + icd1.physical_device_groups.back().use_physical_device(pd2); icd1.min_icd_interface_version = 5; icd1.max_icd_interface_version = 5; icd1.set_icd_api_version(VK_API_VERSION_1_1); // ICD 2 : No 1.1 support (so 3 devices will become 3 groups in loader) TestICD& icd2 = env.get_test_icd(2); - icd2.physical_devices.emplace_back("physical_device_3"); - icd2.physical_devices.emplace_back("physical_device_4"); - icd2.physical_devices.emplace_back("physical_device_5"); + icd2.add_and_get_physical_device("physical_device_3"); + icd2.add_and_get_physical_device("physical_device_4"); + icd2.add_and_get_physical_device("physical_device_5"); icd2.min_icd_interface_version = 5; icd2.max_icd_interface_version = 5; icd2.set_icd_api_version(VK_API_VERSION_1_0); @@ -489,20 +492,15 @@ TEST(MultipleICDConfig, version_5_and_version_6) { driver_5.set_max_icd_interface_version(5); driver_5.set_min_icd_interface_version(5); driver_5.setup_WSI(); - driver_5.physical_devices.push_back({}); - driver_5.physical_devices.back().queue_family_properties.push_back(family_props); - driver_5.physical_devices.push_back({}); - driver_5.physical_devices.back().queue_family_properties.push_back(family_props); - driver_5.physical_devices.push_back({}); - driver_5.physical_devices.back().queue_family_properties.push_back(family_props); + driver_5.add_and_get_physical_device({}).queue_family_properties.push_back(family_props); + driver_5.add_and_get_physical_device({}).queue_family_properties.push_back(family_props); + driver_5.add_and_get_physical_device({}).queue_family_properties.push_back(family_props); physical_count += static_cast(driver_5.physical_devices.size()); auto& driver_6 = env.get_test_icd(i * 2); driver_6.setup_WSI(); - driver_6.physical_devices.emplace_back("physical_device_0"); - driver_6.physical_devices.back().queue_family_properties.push_back(family_props); - driver_6.physical_devices.emplace_back("physical_device_1"); - driver_6.physical_devices.back().queue_family_properties.push_back(family_props); + driver_6.add_and_get_physical_device("physical_device_0").queue_family_properties.push_back(family_props); + driver_6.add_and_get_physical_device("physical_device_1").queue_family_properties.push_back(family_props); physical_count += static_cast(driver_6.physical_devices.size()); driver_6.set_max_icd_interface_version(6); @@ -589,8 +587,9 @@ VkResult test_vkSetPrivateData(VkDevice, VkObjectType, uint64_t, VkPrivateDataSl TEST(MinorVersionUpdate, Version1_3) { FrameworkEnvironment env{}; - auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); - driver.physical_devices.back().known_device_functions = { + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); + auto& pd = driver.add_and_get_physical_device({}); + pd.known_device_functions = { VulkanFunction{"vkCmdBeginRendering", to_vkVoidFunction(test_vkCmdBeginRendering)}, VulkanFunction{"vkCmdBindVertexBuffers2", to_vkVoidFunction(test_vkCmdBindVertexBuffers2)}, VulkanFunction{"vkCmdBlitImage2", to_vkVoidFunction(test_vkCmdBlitImage2)}, @@ -629,7 +628,7 @@ TEST(MinorVersionUpdate, Version1_3) { VulkanFunction{"vkQueueSubmit2", to_vkVoidFunction(test_vkQueueSubmit2)}, VulkanFunction{"vkSetPrivateData", to_vkVoidFunction(test_vkSetPrivateData)}, }; - driver.physical_devices.back().add_extension({"VK_SOME_EXT_haha"}); + pd.add_extension({"VK_SOME_EXT_haha"}); InstWrapper inst{env.vulkan_functions}; inst.create_info.set_api_version(1, 3, 0); inst.CheckCreate(); @@ -937,8 +936,7 @@ void CheckDirectDriverLoading(FrameworkEnvironment& env, std::vector for (auto const& driver : direct_drivers) { auto& direct_driver_icd = env.add_icd(driver.icd_details); - direct_driver_icd.physical_devices.push_back({}); - direct_driver_icd.physical_devices.at(0).properties.driverVersion = driver.driver_version; + direct_driver_icd.add_and_get_physical_device({}).properties.driverVersion = driver.driver_version; VkDirectDriverLoadingInfoLUNARG ddl_info{}; ddl_info.sType = VK_STRUCTURE_TYPE_DIRECT_DRIVER_LOADING_INFO_LUNARG; ddl_info.pfnGetInstanceProcAddr = env.icds.back().icd_library.get_symbol("vk_icdGetInstanceProcAddr"); @@ -950,8 +948,7 @@ void CheckDirectDriverLoading(FrameworkEnvironment& env, std::vector for (auto const& driver : normal_drivers) { auto& direct_driver_icd = env.add_icd(driver.icd_details); - direct_driver_icd.physical_devices.push_back({}); - direct_driver_icd.physical_devices.at(0).properties.driverVersion = driver.driver_version; + direct_driver_icd.add_and_get_physical_device({}).properties.driverVersion = driver.driver_version; if (!exclusive && driver.expect_to_find) { expected_driver_count++; } @@ -1148,7 +1145,7 @@ TEST(DirectDriverLoading, ExtensionNotEnabled) { FrameworkEnvironment env{}; auto& direct_driver_icd = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_7).set_discovery_type(ManifestDiscoveryType::none)); - direct_driver_icd.physical_devices.push_back({}); + direct_driver_icd.add_physical_device({}); VkDirectDriverLoadingInfoLUNARG ddl_info{}; ddl_info.sType = VK_STRUCTURE_TYPE_DIRECT_DRIVER_LOADING_INFO_LUNARG; diff --git a/tests/loader_wsi_tests.cpp b/tests/loader_wsi_tests.cpp index 623460591..057b5ea56 100644 --- a/tests/loader_wsi_tests.cpp +++ b/tests/loader_wsi_tests.cpp @@ -127,7 +127,7 @@ TEST(WsiTests, GetPhysicalDeviceWin32PresentNoICDSupport) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extension({VK_KHR_SURFACE_EXTENSION_NAME}); cur_icd.add_instance_extension({VK_KHR_WIN32_SURFACE_EXTENSION_NAME}); - cur_icd.physical_devices.emplace_back("physical_device_0"); + cur_icd.add_and_get_physical_device("physical_device_0"); cur_icd.enable_icd_wsi = false; InstWrapper inst{env.vulkan_functions}; @@ -154,7 +154,7 @@ TEST(WsiTests, GetPhysicalDeviceWin32PresentICDSupport) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extension({VK_KHR_SURFACE_EXTENSION_NAME}); cur_icd.add_instance_extension({VK_KHR_WIN32_SURFACE_EXTENSION_NAME}); - cur_icd.physical_devices.emplace_back("physical_device_0"); + cur_icd.add_and_get_physical_device("physical_device_0"); cur_icd.enable_icd_wsi = true; InstWrapper inst{env.vulkan_functions}; @@ -181,8 +181,8 @@ TEST(WsiTests, Win32GetPhysicalDeviceSurfaceSupportKHR) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extensions({first_ext, second_ext}); std::string dev_name = "phys_dev_" + std::to_string(icd); - cur_icd.physical_devices.emplace_back(dev_name.c_str()); - cur_icd.physical_devices.back().add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, true}); + cur_icd.add_and_get_physical_device(dev_name.c_str()) + .add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, true}); cur_icd.enable_icd_wsi = true; } @@ -311,7 +311,7 @@ TEST(WsiTests, GetPhysicalDeviceXcbPresentNoICDSupport) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extension({VK_KHR_SURFACE_EXTENSION_NAME}); cur_icd.add_instance_extension({VK_KHR_XCB_SURFACE_EXTENSION_NAME}); - cur_icd.physical_devices.emplace_back("physical_device_0"); + cur_icd.add_and_get_physical_device("physical_device_0"); cur_icd.enable_icd_wsi = false; InstWrapper inst{env.vulkan_functions}; @@ -338,7 +338,7 @@ TEST(WsiTests, GetPhysicalDeviceXcbPresentICDSupport) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extension({VK_KHR_SURFACE_EXTENSION_NAME}); cur_icd.add_instance_extension({VK_KHR_XCB_SURFACE_EXTENSION_NAME}); - cur_icd.physical_devices.emplace_back("physical_device_0"); + cur_icd.add_and_get_physical_device("physical_device_0"); cur_icd.enable_icd_wsi = true; InstWrapper inst{env.vulkan_functions}; @@ -365,8 +365,8 @@ TEST(WsiTests, XcbGetPhysicalDeviceSurfaceSupportKHR) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extensions({first_ext, second_ext}); std::string dev_name = "phys_dev_" + std::to_string(icd); - cur_icd.physical_devices.emplace_back(dev_name.c_str()); - cur_icd.physical_devices.back().add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, true}); + cur_icd.add_and_get_physical_device(dev_name.c_str()) + .add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, true}); cur_icd.enable_icd_wsi = true; } @@ -495,7 +495,7 @@ TEST(WsiTests, GetPhysicalDeviceXlibPresentNoICDSupport) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extension({VK_KHR_SURFACE_EXTENSION_NAME}); cur_icd.add_instance_extension({VK_KHR_XLIB_SURFACE_EXTENSION_NAME}); - cur_icd.physical_devices.emplace_back("physical_device_0"); + cur_icd.add_and_get_physical_device("physical_device_0"); cur_icd.enable_icd_wsi = false; InstWrapper inst{env.vulkan_functions}; @@ -522,7 +522,7 @@ TEST(WsiTests, GetPhysicalDeviceXlibPresentICDSupport) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extension({VK_KHR_SURFACE_EXTENSION_NAME}); cur_icd.add_instance_extension({VK_KHR_XLIB_SURFACE_EXTENSION_NAME}); - cur_icd.physical_devices.emplace_back("physical_device_0"); + cur_icd.add_and_get_physical_device("physical_device_0"); cur_icd.enable_icd_wsi = true; InstWrapper inst{env.vulkan_functions}; @@ -549,8 +549,8 @@ TEST(WsiTests, XlibGetPhysicalDeviceSurfaceSupportKHR) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extensions({first_ext, second_ext}); std::string dev_name = "phys_dev_" + std::to_string(icd); - cur_icd.physical_devices.emplace_back(dev_name.c_str()); - cur_icd.physical_devices.back().add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, true}); + cur_icd.add_and_get_physical_device(dev_name.c_str()) + .add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, true}); cur_icd.enable_icd_wsi = true; } @@ -679,7 +679,7 @@ TEST(WsiTests, GetPhysicalDeviceWaylandPresentNoICDSupport) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extension({VK_KHR_SURFACE_EXTENSION_NAME}); cur_icd.add_instance_extension({VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME}); - cur_icd.physical_devices.emplace_back("physical_device_0"); + cur_icd.add_and_get_physical_device("physical_device_0"); cur_icd.enable_icd_wsi = false; InstWrapper inst{env.vulkan_functions}; @@ -706,7 +706,7 @@ TEST(WsiTests, GetPhysicalDeviceWaylandPresentICDSupport) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extension({VK_KHR_SURFACE_EXTENSION_NAME}); cur_icd.add_instance_extension({VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME}); - cur_icd.physical_devices.emplace_back("physical_device_0"); + cur_icd.add_and_get_physical_device("physical_device_0"); cur_icd.enable_icd_wsi = true; InstWrapper inst{env.vulkan_functions}; @@ -733,8 +733,8 @@ TEST(WsiTests, WaylandGetPhysicalDeviceSurfaceSupportKHR) { cur_icd.set_min_icd_interface_version(5); cur_icd.add_instance_extensions({first_ext, second_ext}); std::string dev_name = "phys_dev_" + std::to_string(icd); - cur_icd.physical_devices.emplace_back(dev_name.c_str()); - cur_icd.physical_devices.back().add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, true}); + cur_icd.add_and_get_physical_device(dev_name.c_str()) + .add_queue_family_properties({{VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}, true}); cur_icd.enable_icd_wsi = true; } @@ -925,15 +925,15 @@ TEST(WsiTests, EXTSurfaceMaintenance1) { VkSurfaceCapabilitiesKHR surface_caps{}; surface_caps.maxImageExtent = VkExtent2D{300, 300}; surface_caps.minImageExtent = VkExtent2D{100, 100}; - env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) - .setup_WSI() - .add_instance_extension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME) - .add_physical_device(PhysicalDevice{} - .add_extension("VK_KHR_swapchain") - .set_deviceName("no") - .set_surface_capabilities(surface_caps) - .add_surface_present_modes(present_modes) - .finish()); + auto& test_physical_device_0 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) + .setup_WSI() + .add_instance_extension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME) + .add_and_get_physical_device(PhysicalDevice{} + .add_extension("VK_KHR_swapchain") + .set_deviceName("no") + .set_surface_capabilities(surface_caps) + .add_surface_present_modes(present_modes) + .finish()); VkSurfacePresentScalingCapabilitiesEXT scaling_capabilities{}; scaling_capabilities.supportedPresentScaling = VK_PRESENT_SCALING_ONE_TO_ONE_BIT_EXT; scaling_capabilities.supportedPresentGravityX = VK_PRESENT_SCALING_ASPECT_RATIO_STRETCH_BIT_EXT; @@ -943,21 +943,22 @@ TEST(WsiTests, EXTSurfaceMaintenance1) { auto& icd2 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) .setup_WSI() .add_instance_extension(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME) - .add_instance_extension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME) - .add_physical_device(PhysicalDevice{} - .add_extension("VK_KHR_swapchain") - .set_deviceName("yes") - .set_surface_capabilities(surface_caps) - .add_surface_present_modes(present_modes) - .set_surface_present_scaling_capabilities(scaling_capabilities) - .finish()); + .add_instance_extension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME); + auto& test_physical_device_1 = + icd2.add_and_get_physical_device(PhysicalDevice{} + .add_extension("VK_KHR_swapchain") + .set_deviceName("yes") + .set_surface_capabilities(surface_caps) + .add_surface_present_modes(present_modes) + .set_surface_present_scaling_capabilities(scaling_capabilities) + .finish()); std::vector> compatible_present_modes{ {VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR}, {VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR}, {VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR}, {VK_PRESENT_MODE_FIFO_RELAXED_KHR, VK_PRESENT_MODE_FIFO_KHR}, }; - icd2.physical_devices[0].surface_present_mode_compatibility = compatible_present_modes; + test_physical_device_1.surface_present_mode_compatibility = compatible_present_modes; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)) .setup_WSI() .add_physical_device(PhysicalDevice{} From be3fe40144f269d0e834693f966443c6c24a6962 Mon Sep 17 00:00:00 2001 From: ziga-lunarg Date: Fri, 8 Aug 2025 17:53:41 +0300 Subject: [PATCH 29/65] build: Update to header 1.4.325 --- CMakeLists.txt | 2 +- loader/loader.rc | 4 ++-- scripts/known_good.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7766618d8..5e370dbc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.324 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.325 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/loader.rc b/loader/loader.rc index 0a41511b3..c5ac568aa 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 324, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.324.Dev Build" +#define VER_FILE_VERSION 1, 4, 325, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.325.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index fe506fc72..88b5ea889 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.324" + "commit": "v1.4.325" }, { "name": "googletest", From eae3da9aba4138b768e73b6197af17578571ca9d Mon Sep 17 00:00:00 2001 From: Jozsef Hapak Date: Tue, 12 Aug 2025 12:47:32 +0200 Subject: [PATCH 30/65] loader: make it possible to filter the physical devices by vendor, device and / or driver id This change introduces the VK_LOADER_DEVICE_ID_FILTER, VK_LOADER_VENDOR_ID_FILTER and VK_LOADER_DRIVER_ID_FILTER enviroment variables, where a comma separated list of id ranges can be provided to restrict the enumerated physical devices by device, vendor and/or driver id. e.g. VK_LOADER_DEVICE_ID_FILTER or VK_LOADER_VENDOR_ID_FILTER: "0x10001-0x1FFFF,0x1eb1" e.g. VK_LOADER_DRIVER_ID_FILTER: "3,7-9" --- loader/loader.c | 130 ++++++++ loader/loader.h | 14 + loader/loader_common.h | 10 + loader/loader_environment.c | 53 ++++ loader/loader_environment.h | 4 + loader/trampoline.c | 64 +++- loader/vk_loader_platform.h | 4 + tests/framework/icd/test_icd.cpp | 4 + tests/framework/icd/test_icd.h | 1 + tests/framework/test_environment.h | 3 + tests/loader_regression_tests.cpp | 467 +++++++++++++++++++++++++++++ 11 files changed, 751 insertions(+), 3 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 99daa47ea..c6ace5027 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -7986,3 +7986,133 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_EnumeratePhysicalDeviceGroups( } return res; } + +VkResult get_device_driver_id(VkPhysicalDevice physicalDevice, VkDriverId *driverId) { + VkPhysicalDeviceDriverProperties physical_device_driver_props = {0}; + physical_device_driver_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; + + VkPhysicalDeviceProperties2 props2 = {0}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &physical_device_driver_props; + + struct loader_physical_device_term *phys_dev_term = (struct loader_physical_device_term *)physicalDevice; + struct loader_icd_term *icd_term = phys_dev_term->this_icd_term; + const struct loader_instance *inst = icd_term->this_instance; + + assert(inst != NULL); + + // Get the function pointer to use to call into the ICD. This could be the core or KHR version + PFN_vkGetPhysicalDeviceProperties2 fpGetPhysicalDeviceProperties2 = NULL; + if (loader_check_version_meets_required(LOADER_VERSION_1_1_0, inst->app_api_version)) { + fpGetPhysicalDeviceProperties2 = icd_term->dispatch.GetPhysicalDeviceProperties2; + } + if (fpGetPhysicalDeviceProperties2 == NULL && inst->enabled_extensions.khr_get_physical_device_properties2) { + fpGetPhysicalDeviceProperties2 = icd_term->dispatch.GetPhysicalDeviceProperties2KHR; + } + + if (fpGetPhysicalDeviceProperties2 == NULL) { + *driverId = 0; + return VK_ERROR_UNKNOWN; + } + + fpGetPhysicalDeviceProperties2(phys_dev_term->phys_dev, &props2); + + *driverId = physical_device_driver_props.driverID; + return VK_SUCCESS; +} + +VkResult loader_filter_enumerated_physical_device(const struct loader_instance *inst, + const struct loader_envvar_id_filter *device_id_filter, + const struct loader_envvar_id_filter *vendor_id_filter, + const struct loader_envvar_id_filter *driver_id_filter, + const uint32_t in_PhysicalDeviceCount, + const VkPhysicalDevice *in_pPhysicalDevices, uint32_t *out_pPhysicalDeviceCount, + VkPhysicalDevice *out_pPhysicalDevices) { + uint32_t filtered_physical_device_count = 0; + for (uint32_t i = 0; i < in_PhysicalDeviceCount; i++) { + VkPhysicalDeviceProperties dev_props = {0}; + inst->disp->layer_inst_disp.GetPhysicalDeviceProperties(in_pPhysicalDevices[i], &dev_props); + + if ((0 != device_id_filter->count) && !check_id_matches_filter_environment_var(dev_props.deviceID, device_id_filter)) { + continue; + } + + if ((0 != vendor_id_filter->count) && !check_id_matches_filter_environment_var(dev_props.vendorID, vendor_id_filter)) { + continue; + } + + if (0 != driver_id_filter->count) { + VkDriverId driver_id; + VkResult res = get_device_driver_id(in_pPhysicalDevices[i], &driver_id); + + if ((res != VK_SUCCESS) || !check_id_matches_filter_environment_var(driver_id, driver_id_filter)) { + continue; + } + } + + if ((NULL != out_pPhysicalDevices) && (filtered_physical_device_count < *out_pPhysicalDeviceCount)) { + out_pPhysicalDevices[filtered_physical_device_count] = in_pPhysicalDevices[i]; + } + filtered_physical_device_count++; + } + + if ((NULL == out_pPhysicalDevices) || (filtered_physical_device_count < *out_pPhysicalDeviceCount)) { + *out_pPhysicalDeviceCount = filtered_physical_device_count; + } + + return (*out_pPhysicalDeviceCount < filtered_physical_device_count) ? VK_INCOMPLETE : VK_SUCCESS; +} + +VkResult loader_filter_enumerated_physical_device_groups( + const struct loader_instance *inst, const struct loader_envvar_id_filter *device_id_filter, + const struct loader_envvar_id_filter *vendor_id_filter, const struct loader_envvar_id_filter *driver_id_filter, + const uint32_t in_PhysicalDeviceGroupCount, const VkPhysicalDeviceGroupProperties *in_pPhysicalDeviceGroupProperties, + uint32_t *out_PhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties *out_pPhysicalDeviceGroupProperties) { + uint32_t filtered_physical_device_group_count = 0; + for (uint32_t i = 0; i < in_PhysicalDeviceGroupCount; i++) { + const VkPhysicalDeviceGroupProperties *device_group = &in_pPhysicalDeviceGroupProperties[i]; + + bool skip_group = false; + for (uint32_t j = 0; j < device_group->physicalDeviceCount; j++) { + VkPhysicalDeviceProperties dev_props = {0}; + inst->disp->layer_inst_disp.GetPhysicalDeviceProperties(device_group->physicalDevices[j], &dev_props); + + if ((0 != device_id_filter->count) && !check_id_matches_filter_environment_var(dev_props.deviceID, device_id_filter)) { + skip_group = true; + break; + } + + if ((0 != vendor_id_filter->count) && !check_id_matches_filter_environment_var(dev_props.vendorID, vendor_id_filter)) { + skip_group = true; + break; + } + + if (0 != driver_id_filter->count) { + VkDriverId driver_id; + VkResult res = get_device_driver_id(device_group->physicalDevices[j], &driver_id); + + if ((res != VK_SUCCESS) || !check_id_matches_filter_environment_var(driver_id, driver_id_filter)) { + skip_group = true; + break; + } + } + } + + if (skip_group) { + continue; + } + + if ((NULL != out_pPhysicalDeviceGroupProperties) && + (filtered_physical_device_group_count < *out_PhysicalDeviceGroupCount)) { + out_pPhysicalDeviceGroupProperties[filtered_physical_device_group_count] = *device_group; + } + + filtered_physical_device_group_count++; + } + + if ((NULL == out_pPhysicalDeviceGroupProperties) || (filtered_physical_device_group_count < *out_PhysicalDeviceGroupCount)) { + *out_PhysicalDeviceGroupCount = filtered_physical_device_group_count; + } + + return (*out_PhysicalDeviceGroupCount < filtered_physical_device_group_count) ? VK_INCOMPLETE : VK_SUCCESS; +} diff --git a/loader/loader.h b/loader/loader.h index 2ed309c0e..04eee0246 100644 --- a/loader/loader.h +++ b/loader/loader.h @@ -237,6 +237,20 @@ loader_api_version loader_combine_version(uint32_t major, uint32_t minor, uint32 // Helper macros for determining if a version is valid or not bool loader_check_version_meets_required(loader_api_version required, loader_api_version version); +VkResult loader_filter_enumerated_physical_device(const struct loader_instance *inst, + const struct loader_envvar_id_filter *device_id_filter, + const struct loader_envvar_id_filter *vendor_id_filter, + const struct loader_envvar_id_filter *driver_id_filter, + const uint32_t in_PhysicalDeviceCount, + const VkPhysicalDevice *in_pPhysicalDevices, uint32_t *out_pPhysicalDeviceCount, + VkPhysicalDevice *out_pPhysicalDevices); + +VkResult loader_filter_enumerated_physical_device_groups( + const struct loader_instance *inst, const struct loader_envvar_id_filter *device_id_filter, + const struct loader_envvar_id_filter *vendor_id_filter, const struct loader_envvar_id_filter *driver_id_filter, + const uint32_t in_PhysicalDeviceGroupCount, const VkPhysicalDeviceGroupProperties *in_pPhysicalDeviceGroupProperties, + uint32_t *out_PhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties *out_pPhysicalDeviceGroupProperties); + // Convenience macros for common versions #if !defined(LOADER_VERSION_1_0_0) #define LOADER_VERSION_1_0_0 loader_combine_version(1, 0, 0) diff --git a/loader/loader_common.h b/loader/loader_common.h index 865c2ccee..45baa79e9 100644 --- a/loader/loader_common.h +++ b/loader/loader_common.h @@ -521,3 +521,13 @@ struct loader_envvar_all_filters { struct loader_envvar_disable_layers_filter disable_filter; struct loader_envvar_filter allow_filter; }; + +struct loader_envvar_id_filter_value { + uint32_t begin; + uint32_t end; +}; + +struct loader_envvar_id_filter { + uint32_t count; + struct loader_envvar_id_filter_value filters[MAX_ADDITIONAL_FILTERS]; +}; diff --git a/loader/loader_environment.c b/loader/loader_environment.c index 6489efac8..77593a9d3 100644 --- a/loader/loader_environment.c +++ b/loader/loader_environment.c @@ -566,3 +566,56 @@ VkResult loader_add_environment_layers(struct loader_instance *inst, const enum return res; } + +void parse_id_filter_enviroment_var(const struct loader_instance *inst, const char *env_var_name, + struct loader_envvar_id_filter *filter_struct) { + memset(filter_struct, 0, sizeof(struct loader_envvar_id_filter)); + char *parsing_string = NULL; + char *env_var_value = loader_secure_getenv(env_var_name, inst); + if (NULL == env_var_value) { + return; + } + const size_t env_var_len = strlen(env_var_value); + if (env_var_len == 0) { + goto out; + } + // Allocate a separate string since scan_for_next_comma modifies the original string + parsing_string = loader_stack_alloc(env_var_len + 1); + for (uint32_t iii = 0; iii < env_var_len; ++iii) { + parsing_string[iii] = (char)tolower(env_var_value[iii]); + } + parsing_string[env_var_len] = '\0'; + + filter_struct->count = 0; + char *context = NULL; + char *token = thread_safe_strtok(parsing_string, ",", &context); + while (NULL != token) { + struct loader_envvar_id_filter_value *filter_value = &filter_struct->filters[filter_struct->count]; + + char *pEnd; + filter_value->begin = (uint32_t)strtoul(token, &pEnd, 0); + + if (*pEnd != '\0') { + pEnd++; + filter_value->end = (uint32_t)strtoul(pEnd, NULL, 0); + } else { + filter_value->end = filter_value->begin; + } + + filter_struct->count++; + token = thread_safe_strtok(NULL, ",", &context); + } + +out: + + loader_free_getenv(env_var_value, inst); +} + +bool check_id_matches_filter_environment_var(const uint32_t id, const struct loader_envvar_id_filter *filter_struct) { + for (uint32_t i = 0; i < filter_struct->count; i++) { + if ((filter_struct->filters[i].begin <= id) && (id <= filter_struct->filters[i].end)) { + return true; + } + } + return false; +} diff --git a/loader/loader_environment.h b/loader/loader_environment.h index d120ec95c..da35237b5 100644 --- a/loader/loader_environment.h +++ b/loader/loader_environment.h @@ -54,3 +54,7 @@ VkResult loader_add_environment_layers(struct loader_instance *inst, const enum struct loader_pointer_layer_list *target_list, struct loader_pointer_layer_list *expanded_target_list, const struct loader_layer_list *source_list); + +void parse_id_filter_enviroment_var(const struct loader_instance *inst, const char *env_var_name, + struct loader_envvar_id_filter *filter_struct); +bool check_id_matches_filter_environment_var(const uint32_t id, const struct loader_envvar_id_filter *filter_struct); diff --git a/loader/trampoline.c b/loader/trampoline.c index 0ebb4c172..73e55ef64 100644 --- a/loader/trampoline.c +++ b/loader/trampoline.c @@ -36,6 +36,7 @@ #include "loader_environment.h" #include "log.h" #include "settings.h" +#include "stack_allocation.h" #include "vk_loader_extensions.h" #include "vk_loader_platform.h" #include "wsi.h" @@ -852,8 +853,34 @@ LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstan goto out; } + struct loader_envvar_id_filter device_id_filter; + struct loader_envvar_id_filter vendor_id_filter; + struct loader_envvar_id_filter driver_id_filter; + + parse_id_filter_enviroment_var(inst, VK_DEVICE_ID_FILTER_ENV_VAR, &device_id_filter); + parse_id_filter_enviroment_var(inst, VK_VENDOR_ID_FILTER_ENV_VAR, &vendor_id_filter); + parse_id_filter_enviroment_var(inst, VK_DRIVER_ID_FILTER_ENV_VAR, &driver_id_filter); + // Call down the chain to get the physical device info - res = inst->disp->layer_inst_disp.EnumeratePhysicalDevices(inst->instance, pPhysicalDeviceCount, pPhysicalDevices); + if ((0 == device_id_filter.count) && (0 == vendor_id_filter.count) && (0 == driver_id_filter.count)) { + res = inst->disp->layer_inst_disp.EnumeratePhysicalDevices(inst->instance, pPhysicalDeviceCount, pPhysicalDevices); + } else { + uint32_t physical_device_count = 0; + res = inst->disp->layer_inst_disp.EnumeratePhysicalDevices(inst->instance, &physical_device_count, NULL); + if (res != VK_SUCCESS) { + goto out; + } + + VkPhysicalDevice *physical_devices = loader_stack_alloc(physical_device_count * sizeof(VkPhysicalDevice)); + res = inst->disp->layer_inst_disp.EnumeratePhysicalDevices(inst->instance, &physical_device_count, physical_devices); + if (res != VK_SUCCESS) { + goto out; + } + + res = loader_filter_enumerated_physical_device(inst, &device_id_filter, &vendor_id_filter, &driver_id_filter, + physical_device_count, physical_devices, pPhysicalDeviceCount, + pPhysicalDevices); + } if (NULL != pPhysicalDevices && (VK_SUCCESS == res || VK_INCOMPLETE == res)) { // Wrap the PhysDev object for loader usage, return wrapped objects @@ -2586,9 +2613,40 @@ LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups( goto out; } + struct loader_envvar_id_filter device_id_filter; + struct loader_envvar_id_filter vendor_id_filter; + struct loader_envvar_id_filter driver_id_filter; + + parse_id_filter_enviroment_var(inst, VK_DEVICE_ID_FILTER_ENV_VAR, &device_id_filter); + parse_id_filter_enviroment_var(inst, VK_VENDOR_ID_FILTER_ENV_VAR, &vendor_id_filter); + parse_id_filter_enviroment_var(inst, VK_DRIVER_ID_FILTER_ENV_VAR, &driver_id_filter); + // Call down the chain to get the physical device group info. - res = inst->disp->layer_inst_disp.EnumeratePhysicalDeviceGroups(inst->instance, pPhysicalDeviceGroupCount, - pPhysicalDeviceGroupProperties); + if ((0 == device_id_filter.count) && (0 == vendor_id_filter.count) && (0 == driver_id_filter.count)) { + res = inst->disp->layer_inst_disp.EnumeratePhysicalDeviceGroups(inst->instance, pPhysicalDeviceGroupCount, + pPhysicalDeviceGroupProperties); + } else { + uint32_t physical_device_group_count = 0; + res = inst->disp->layer_inst_disp.EnumeratePhysicalDeviceGroups(inst->instance, &physical_device_group_count, NULL); + if (res != VK_SUCCESS) { + goto out; + } + + VkPhysicalDeviceGroupProperties *physical_device_group_properties = + loader_stack_alloc(physical_device_group_count * sizeof(VkPhysicalDeviceGroupProperties)); + memset(physical_device_group_properties, 0, physical_device_group_count * sizeof(VkPhysicalDeviceGroupProperties)); + + res = inst->disp->layer_inst_disp.EnumeratePhysicalDeviceGroups(inst->instance, &physical_device_group_count, + physical_device_group_properties); + if (res != VK_SUCCESS) { + goto out; + } + + res = loader_filter_enumerated_physical_device_groups(inst, &device_id_filter, &vendor_id_filter, &driver_id_filter, + physical_device_group_count, physical_device_group_properties, + pPhysicalDeviceGroupCount, pPhysicalDeviceGroupProperties); + } + if (NULL != pPhysicalDeviceGroupProperties && (VK_SUCCESS == res || VK_INCOMPLETE == res)) { // Wrap the PhysDev object for loader usage, return wrapped objects VkResult update_res = setup_loader_tramp_phys_dev_groups(inst, *pPhysicalDeviceGroupCount, pPhysicalDeviceGroupProperties); diff --git a/loader/vk_loader_platform.h b/loader/vk_loader_platform.h index 272f6f8ec..987337c93 100644 --- a/loader/vk_loader_platform.h +++ b/loader/vk_loader_platform.h @@ -130,6 +130,10 @@ #define VK_IMPLICIT_LAYER_PATH_ENV_VAR "VK_IMPLICIT_LAYER_PATH" #define VK_ADDITIONAL_IMPLICIT_LAYER_PATH_ENV_VAR "VK_ADD_IMPLICIT_LAYER_PATH" +#define VK_DEVICE_ID_FILTER_ENV_VAR "VK_LOADER_DEVICE_ID_FILTER" +#define VK_VENDOR_ID_FILTER_ENV_VAR "VK_LOADER_VENDOR_ID_FILTER" +#define VK_DRIVER_ID_FILTER_ENV_VAR "VK_LOADER_DRIVER_ID_FILTER" + // Override layer information #define VK_OVERRIDE_LAYER_NAME "VK_LAYER_LUNARG_override" diff --git a/tests/framework/icd/test_icd.cpp b/tests/framework/icd/test_icd.cpp index 01bf1dd36..466293a55 100644 --- a/tests/framework/icd/test_icd.cpp +++ b/tests/framework/icd/test_icd.cpp @@ -1232,6 +1232,10 @@ VKAPI_ATTR void VKAPI_CALL test_vkGetPhysicalDeviceProperties2(VkPhysicalDevice auto* vulkan_11_props = reinterpret_cast(pNext); memcpy(vulkan_11_props->deviceUUID, phys_dev.deviceUUID.data(), VK_UUID_SIZE); } + if (pNext->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES) { + auto* device_driver_props = reinterpret_cast(pNext); + *device_driver_props = phys_dev.driver_properties; + } pNext = reinterpret_cast(const_cast(pNext->pNext)); } } diff --git a/tests/framework/icd/test_icd.h b/tests/framework/icd/test_icd.h index d84f4f8a3..24309f410 100644 --- a/tests/framework/icd/test_icd.h +++ b/tests/framework/icd/test_icd.h @@ -94,6 +94,7 @@ struct PhysicalDevice { BUILDER_VALUE(VkPhysicalDeviceProperties, properties) BUILDER_VALUE(VkPhysicalDeviceFeatures, features) BUILDER_VALUE(VkPhysicalDeviceMemoryProperties, memory_properties) + BUILDER_VALUE(VkPhysicalDeviceDriverProperties, driver_properties) BUILDER_VALUE(VkImageFormatProperties, image_format_properties) BUILDER_VALUE(VkExternalMemoryProperties, external_memory_properties) BUILDER_VALUE(VkExternalSemaphoreProperties, external_semaphore_properties) diff --git a/tests/framework/test_environment.h b/tests/framework/test_environment.h index 0a1f89e0d..678e88e99 100644 --- a/tests/framework/test_environment.h +++ b/tests/framework/test_environment.h @@ -561,6 +561,9 @@ struct FrameworkEnvironment { EnvVarWrapper add_env_var_vk_layer_paths{"VK_ADD_LAYER_PATH"}; EnvVarWrapper env_var_vk_implicit_layer_paths{"VK_IMPLICIT_LAYER_PATH"}; EnvVarWrapper add_env_var_vk_implicit_layer_paths{"VK_ADD_IMPLICIT_LAYER_PATH"}; + EnvVarWrapper env_var_vk_loader_device_id_filter{"VK_LOADER_DEVICE_ID_FILTER"}; + EnvVarWrapper env_var_vk_loader_vendor_id_filter{"VK_LOADER_VENDOR_ID_FILTER"}; + EnvVarWrapper env_var_vk_loader_driver_id_filter{"VK_LOADER_DRIVER_ID_FILTER"}; #if TESTING_COMMON_UNIX_PLATFORMS EnvVarWrapper env_var_home{"HOME", "/home/fake_home"}; diff --git a/tests/loader_regression_tests.cpp b/tests/loader_regression_tests.cpp index dab31af3d..cae8c211b 100644 --- a/tests/loader_regression_tests.cpp +++ b/tests/loader_regression_tests.cpp @@ -1223,6 +1223,213 @@ TEST(EnumeratePhysicalDevices, TwoDriversOneWithWrongErrorCodes) { } } +TEST(EnumeratePhysicalDevices, DeviceFiltering) { + FrameworkEnvironment env{}; + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_min_icd_interface_version(5); + + for (uint32_t i = 0; i < 10; i++) { + auto& physical_device = driver.add_and_get_physical_device("physical_device_" + std::to_string(i)); + physical_device.properties.deviceID = 0x1000 + i; + physical_device.properties.vendorID = 0x2000 + i; + } + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + + uint32_t physical_count = static_cast(driver.physical_devices.size()); + + // filter multiple device by device id + { + env.env_var_vk_loader_device_id_filter.set_new_value("0x1001-0x1003,0x1006"); + + uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(4, returned_physical_count); + + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // filter multiple device by vendor id + { + env.env_var_vk_loader_vendor_id_filter.set_new_value("0x2001-0x2003,0x2006"); + + uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(4, returned_physical_count); + + env.env_var_vk_loader_vendor_id_filter.remove_value(); + } + + // filter multiple device by vendor id and device id + { + env.env_var_vk_loader_device_id_filter.set_new_value("0x1002-0x1004"); + env.env_var_vk_loader_vendor_id_filter.set_new_value("0x2003-0x2007"); + + uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(2, returned_physical_count); + + env.env_var_vk_loader_vendor_id_filter.remove_value(); + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // return value change by device filter + { + uint32_t returned_physical_count = 5; + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(5, returned_physical_count); + + env.env_var_vk_loader_device_id_filter.set_new_value("0x1003-0x1004"); + + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(2, returned_physical_count); + + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // return value change by vendor filter + { + uint32_t returned_physical_count = 5; + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(5, returned_physical_count); + + env.env_var_vk_loader_vendor_id_filter.set_new_value("0x2005-0x2006"); + + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(2, returned_physical_count); + + env.env_var_vk_loader_vendor_id_filter.remove_value(); + } + + // incomplete result with device filter + { + env.env_var_vk_loader_device_id_filter.set_new_value("0x1001-0x1006"); + + uint32_t returned_physical_count = 3; + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(3, returned_physical_count); + + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // filter all device + { + env.env_var_vk_loader_device_id_filter.set_new_value("0x2002-0x2003"); + + uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(0, returned_physical_count); + + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // no device filtering + { + env.env_var_vk_loader_vendor_id_filter.remove_value(); + env.env_var_vk_loader_device_id_filter.remove_value(); + + uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(physical_count, returned_physical_count); + } +} + +TEST(EnumeratePhysicalDevices, DeviceFilteringByDriverId) { + FrameworkEnvironment env{}; + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)) + .set_min_icd_interface_version(5) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); + + for (uint32_t i = 0; i < 10; i++) { + auto& physical_device = driver.add_and_get_physical_device("physical_device_" + std::to_string(i)); + physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + physical_device.driver_properties.driverID = VkDriverId(100 + i); + } + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(VK_API_VERSION_1_1); + inst.CheckCreate(); + + uint32_t physical_count = static_cast(driver.physical_devices.size()); + + // filter multiple device by driver id + { + env.env_var_vk_loader_driver_id_filter.set_new_value("103-105,107"); + + uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(4, returned_physical_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } + + // return value change by driver filter + { + uint32_t returned_physical_count = 5; + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(5, returned_physical_count); + + env.env_var_vk_loader_driver_id_filter.set_new_value("103-104"); + + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(2, returned_physical_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } + + // incomplete result with driver filter + { + env.env_var_vk_loader_driver_id_filter.set_new_value("102-105"); + + uint32_t returned_physical_count = 3; + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(3, returned_physical_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } + + // filter all device + { + env.env_var_vk_loader_driver_id_filter.set_new_value("50"); + + uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDevices(inst, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(0, returned_physical_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } + + InstWrapper inst_1_0{env.vulkan_functions}; + inst_1_0.create_info.set_api_version(VK_API_VERSION_1_0); + inst_1_0.CheckCreate(); + + // Unsupported device filtering by driver id + { + env.env_var_vk_loader_driver_id_filter.set_new_value("103-105,107"); + + uint32_t returned_physical_count = static_cast(driver.physical_devices.size()); + std::vector physical_device_handles = std::vector(physical_count); + ASSERT_EQ(VK_SUCCESS, + inst_1_0->vkEnumeratePhysicalDevices(inst_1_0, &returned_physical_count, physical_device_handles.data())); + ASSERT_EQ(0, returned_physical_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } +} + TEST(CreateDevice, ExtensionNotPresent) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device("physical_device_0"); @@ -2706,6 +2913,266 @@ TEST(EnumeratePhysicalDeviceGroups, FakePNext) { } } +TEST(EnumeratePhysicalDeviceGroups, DeviceFiltering) { + FrameworkEnvironment env{}; + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)) + .set_min_icd_interface_version(5) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); + + // ICD contains 10 devices in 5 groups + std::array phys_devices; + for (size_t i = 0; i < phys_devices.size(); i++) { + auto& physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + physical_device.properties.apiVersion = VK_API_VERSION_1_1; + + physical_device.properties.deviceID = 0x1000 + i; + physical_device.properties.vendorID = 0x2000 + i; + + phys_devices[i] = &physical_device; + } + + for (size_t i = 0; i < 5; i++) { + driver.physical_device_groups.emplace_back(phys_devices[2 * i]); + driver.physical_device_groups.back().use_physical_device(phys_devices[2 * i + 1]); + } + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + + uint32_t physical_device_group_count = static_cast(driver.physical_device_groups.size()); + + // filter multiple device by device id + { + env.env_var_vk_loader_device_id_filter.set_new_value("0x1000-0x1002,0x1006-0x1010"); + + uint32_t returned_physical_device_group_count = static_cast(driver.physical_device_groups.size()); + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_device_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(3, returned_physical_device_group_count); + + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // filter multiple device by vendor id + { + env.env_var_vk_loader_vendor_id_filter.set_new_value("0x200-0x2002,0x2006-0x2010"); + + uint32_t returned_physical_device_group_count = static_cast(driver.physical_device_groups.size()); + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_device_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(3, returned_physical_device_group_count); + + env.env_var_vk_loader_vendor_id_filter.remove_value(); + } + + // filter multiple device by vendor id and device id + { + env.env_var_vk_loader_device_id_filter.set_new_value("0x1000-0x1007"); + env.env_var_vk_loader_vendor_id_filter.set_new_value("0x2004-0x2009"); + + uint32_t returned_physical_device_group_count = static_cast(driver.physical_device_groups.size()); + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_device_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(2, returned_physical_device_group_count); + + env.env_var_vk_loader_vendor_id_filter.remove_value(); + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // return value change by device filter + { + uint32_t returned_physical_group_count = 3; + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(3, returned_physical_group_count); + + env.env_var_vk_loader_device_id_filter.set_new_value("0x1000-0x1001,0x1004-0x1005"); + + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(2, returned_physical_group_count); + + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // return value change by vendor filter + { + uint32_t returned_physical_group_count = 3; + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(3, returned_physical_group_count); + + env.env_var_vk_loader_vendor_id_filter.set_new_value("0x2002-0x2005"); + + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(2, returned_physical_group_count); + + env.env_var_vk_loader_vendor_id_filter.remove_value(); + } + + // incomplete result with device filter + { + env.env_var_vk_loader_device_id_filter.set_new_value("0x1000-0x1007"); + + uint32_t returned_physical_group_count = 2; + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(2, returned_physical_group_count); + + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // filter all device + { + env.env_var_vk_loader_device_id_filter.set_new_value("0x2002-0x2003"); + + uint32_t returned_physical_group_count = static_cast(driver.physical_device_groups.size()); + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(0, returned_physical_group_count); + + env.env_var_vk_loader_device_id_filter.remove_value(); + } + + // no device filtering + { + env.env_var_vk_loader_vendor_id_filter.remove_value(); + env.env_var_vk_loader_device_id_filter.remove_value(); + + uint32_t returned_physical_group_count = static_cast(driver.physical_device_groups.size()); + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(physical_device_group_count, returned_physical_group_count); + } +} + +TEST(EnumeratePhysicalDeviceGroups, DeviceFilteringByDriverId) { + FrameworkEnvironment env{}; + auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_API_VERSION_1_1)) + .set_min_icd_interface_version(5) + .set_icd_api_version(VK_API_VERSION_1_1) + .add_instance_extension({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}); + + // ICD contains 10 devices in 5 groups + std::array phys_devices; + for (size_t i = 0; i < phys_devices.size(); i++) { + auto& physical_device = driver.add_and_get_physical_device(std::string("physical_device_") + std::to_string(i)); + physical_device.properties.apiVersion = VK_API_VERSION_1_1; + + physical_device.extensions.push_back({VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 0}); + physical_device.driver_properties.driverID = VkDriverId(100 + i); + + phys_devices[i] = &physical_device; + } + + for (size_t i = 0; i < 5; i++) { + driver.physical_device_groups.emplace_back(phys_devices[2 * i]); + driver.physical_device_groups.back().use_physical_device(phys_devices[2 * i + 1]); + } + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(VK_API_VERSION_1_1); + inst.CheckCreate(); + + uint32_t physical_device_group_count = static_cast(driver.physical_device_groups.size()); + + // filter multiple device by device id + { + env.env_var_vk_loader_driver_id_filter.set_new_value("102-105,108-109"); + + uint32_t returned_physical_device_group_count = static_cast(driver.physical_device_groups.size()); + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_device_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(3, returned_physical_device_group_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } + + // return value change by driver filter + { + uint32_t returned_physical_group_count = 3; + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(3, returned_physical_group_count); + + env.env_var_vk_loader_driver_id_filter.set_new_value("102-103,106-107"); + + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(2, returned_physical_group_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } + + // incomplete result with driver filter + { + env.env_var_vk_loader_driver_id_filter.set_new_value("102-109"); + + uint32_t returned_physical_group_count = 2; + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_INCOMPLETE, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(2, returned_physical_group_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } + + // filter all device + { + env.env_var_vk_loader_driver_id_filter.set_new_value("50"); + + uint32_t returned_physical_group_count = static_cast(driver.physical_device_groups.size()); + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_SUCCESS, inst->vkEnumeratePhysicalDeviceGroups(inst, &returned_physical_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(0, returned_physical_group_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } + + InstWrapper inst_1_0{env.vulkan_functions}; + inst_1_0.create_info.set_api_version(VK_API_VERSION_1_0); + inst_1_0.CheckCreate(); + + // Unsupported device filtering by driver id + { + env.env_var_vk_loader_driver_id_filter.set_new_value("102-105,108-109"); + + uint32_t returned_physical_device_group_count = static_cast(driver.physical_device_groups.size()); + std::vector physical_device_group_properties = + std::vector(physical_device_group_count); + ASSERT_EQ(VK_SUCCESS, inst_1_0->vkEnumeratePhysicalDeviceGroups(inst_1_0, &returned_physical_device_group_count, + physical_device_group_properties.data())); + ASSERT_EQ(0, returned_physical_device_group_count); + + env.env_var_vk_loader_driver_id_filter.remove_value(); + } +} + TEST(ExtensionManual, ToolingProperties) { VkPhysicalDeviceToolPropertiesEXT icd_tool_props{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT, nullptr, From 4f5b66d805069a788e255a96db62cb1a076301fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 00:11:02 +0000 Subject: [PATCH 31/65] build(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 34 +++++++++++++++++----------------- .github/workflows/codeql.yml | 2 +- .github/workflows/format.yml | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41142b398..7c4ed3796 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: config: [ Debug, Release ] os: [ ubuntu-22.04, ubuntu-24.04 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: scripts/update_deps.py --dir ext --no-build - run: scripts/generate_source.py --verify ext/Vulkan-Headers/registry/ @@ -86,7 +86,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: sudo apt update - run: sudo apt install --yes --no-install-recommends libwayland-dev libxrandr-dev - run: | @@ -110,7 +110,7 @@ jobs: matrix: config: [ Debug, Release ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -146,7 +146,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -181,7 +181,7 @@ jobs: runs-on: ubuntu-24.04-arm timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -212,7 +212,7 @@ jobs: arch: [ Win32, x64 ] config: [ Debug, Release ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: | cmake -S. -B build ` -D BUILD_TESTS=ON ` @@ -233,7 +233,7 @@ jobs: matrix: arch: [ Win32, x64 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: | cmake -S. -B build ` -D BUILD_TESTS=ON ` @@ -256,7 +256,7 @@ jobs: compiler: [ clang, clang-cl ] config: [ Debug, Release ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ilammy/msvc-dev-cmd@v1 - run: | cmake -S. -B build ` @@ -281,7 +281,7 @@ jobs: config: [ Debug, Release ] static_build: [ APPLE_STATIC_LOADER=ON, APPLE_STATIC_LOADER=OFF ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -311,7 +311,7 @@ jobs: matrix: CMAKE_SYSTEM_NAME: [ iOS, tvOS ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -345,7 +345,7 @@ jobs: static: [ 'ON', 'OFF' ] generator: [ Ninja, Xcode ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -374,7 +374,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: scripts/gn/gn.py mingw: @@ -387,7 +387,7 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -415,7 +415,7 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -438,7 +438,7 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -461,7 +461,7 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: | cmake -S. -B build \ -D UPDATE_DEPS=ON \ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 559f86236..1ae1ac2f5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,7 +49,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 0c6dc2cf7..b49674c9d 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -35,7 +35,7 @@ jobs: - 'loader' - 'tests' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run clang-format uses: jidicula/clang-format-action@v4.15.0 with: From 022c002293038e482dbc0f61b20664d0b25d3a24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 00:33:58 +0000 Subject: [PATCH 32/65] build(deps): bump github/codeql-action from 3.29.7 to 3.29.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.7 to 3.29.8. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/51f77329afa6477de8c49fc9c7046c15b9a4e79d...76621b61decf072c1cee8dd1ce2d2a82d33c17ed) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1ae1ac2f5..dcb8f2768 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/autobuild@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 - uses: actions/setup-python@v5 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 with: category: "/language:${{matrix.language}}" From 362c295adcdea737c6973a7b09b1b7fe771cf19d Mon Sep 17 00:00:00 2001 From: BtbN Date: Sun, 17 Aug 2025 23:39:37 +0200 Subject: [PATCH 33/65] Fix stdcall assembly symbol naming on win32 --- loader/unknown_ext_chain_gas_x86.S | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/loader/unknown_ext_chain_gas_x86.S b/loader/unknown_ext_chain_gas_x86.S index 5ee020512..2a9bb81b3 100644 --- a/loader/unknown_ext_chain_gas_x86.S +++ b/loader/unknown_ext_chain_gas_x86.S @@ -88,11 +88,16 @@ vkdev_ext\num: .else .macro PhysDevExtTramp num +#if defined(_WIN32) +.global "_vkPhysDevExtTramp\num\()@4" +"_vkPhysDevExtTramp\num\()@4": +#else .global vkPhysDevExtTramp\num #if defined(__ELF__) .hidden vkPhysDevExtTramp\num #endif vkPhysDevExtTramp\num: +#endif _CET_ENDBR mov eax, [esp + 4] # Load the wrapped VkPhysicalDevice into eax mov ecx, [eax + PHYS_DEV_OFFSET_PHYS_DEV_TRAMP] # Load the unwrapped VkPhysicalDevice into ecx @@ -102,11 +107,16 @@ vkPhysDevExtTramp\num: .endm .macro PhysDevExtTermin num +#if defined(_WIN32) +.global "_vkPhysDevExtTermin\num\()@4" +"_vkPhysDevExtTermin\num\()@4": +#else .global vkPhysDevExtTermin\num #if defined(__ELF__) .hidden vkPhysDevExtTermin\num #endif vkPhysDevExtTermin\num: +#endif _CET_ENDBR mov ecx, [esp + 4] # Move the wrapped VkPhysicalDevice into ecx mov eax, [ecx + ICD_TERM_OFFSET_PHYS_DEV_TERM] # Store the loader_icd_term* in eax @@ -121,18 +131,27 @@ terminError\num: push 0 # Push zero (third arg) push VULKAN_LOADER_ERROR_BIT # Push the error logging bit (second arg) push eax # Push the loader_instance (first arg) +#if defined(_WIN32) + call _loader_log_asm_function_not_supported # Log the error message before we crash +#else call loader_log_asm_function_not_supported # Log the error message before we crash +#endif add esp, 20 # Clean up the args mov eax, 0 jmp eax # Crash intentionally by jumping to address zero .endm .macro DevExtTramp num +#if defined(_WIN32) +.global "_vkdev_ext\num\()@4" +"_vkdev_ext\num\()@4": +#else .global vkdev_ext\num #if defined(__ELF__) .hidden vkdev_ext\num #endif vkdev_ext\num: +#endif _CET_ENDBR mov eax, dword ptr [esp + 4] # Dereference the handle to get the dispatch table mov eax, dword ptr [eax] # Dereference the chain_device to get the loader_dispatch From 484f3cd7dfb13f63a8b8930cb0397e9b849ab076 Mon Sep 17 00:00:00 2001 From: BtbN Date: Mon, 18 Aug 2025 00:07:43 +0200 Subject: [PATCH 34/65] Silence stdcall fixup warnings on 32bit mingw builds --- loader/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt index 1b51c8309..4d5488ef6 100644 --- a/loader/CMakeLists.txt +++ b/loader/CMakeLists.txt @@ -393,6 +393,10 @@ if(WIN32) set_target_properties(vulkan PROPERTIES PREFIX "") + # let the linker just fix up the stdcall mangling, like on msvc + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + target_link_options(vulkan PRIVATE "-Wl,--enable-stdcall-fixup") + endif() endif() if(MSVC AND ENABLE_WIN10_ONECORE) From 5e056ffdff8998022b4ebb5b6526703899a0670b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 00:27:19 +0000 Subject: [PATCH 35/65] build(deps): bump github/codeql-action from 3.29.8 to 3.29.10 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.8 to 3.29.10. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/76621b61decf072c1cee8dd1ce2d2a82d33c17ed...96f518a34f7a870018057716cc4d7a5c014bd61c) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dcb8f2768..c1e0ca251 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/init@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/autobuild@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 - uses: actions/setup-python@v5 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/analyze@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 with: category: "/language:${{matrix.language}}" From e753770bae19802e47cc963dd52083cf63997ce2 Mon Sep 17 00:00:00 2001 From: Johannes Nixdorf Date: Thu, 14 Aug 2025 12:24:14 +0200 Subject: [PATCH 36/65] loader: Include limits.h for PATH_MAX This fixes compiling against musl libc: loader/loader.c: In function 'normalize_pat': loader/loader.c:405:52: error: 'PATH_MAX' undeclared (first use in this function) 405 | char *path = loader_instance_heap_calloc(inst, PATH_MAX, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); | ^~~~~~~~ --- loader/loader.c | 1 + 1 file changed, 1 insertion(+) diff --git a/loader/loader.c b/loader/loader.c index c6ace5027..0b76b7afb 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -32,6 +32,7 @@ #include #include +#include #include #include #include From 5c5cfd4f2bfcf30019854e27a9d8a45383035580 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 8 Aug 2025 00:09:29 -0500 Subject: [PATCH 37/65] Allow VK_INSTANCE_LAYERS to order implicit layers Implicit layers previously were always loaded closest to the application regardless of whether they appeared in any environment variables. This commit allows users to order implicit layers using VK_INSTANCE_LAYERS, so that if a user has put an implicit layer in that environment variable, that layer will be loaded respecting its order relative to other layers in the env-var. If VK_INSTANCE_LAYERS contains just a single implicit layer, this will cause that implicit layer to be loaded after all other implicit layers, and before any app-enabled layers. This commit also updates the documentation to reflect the un-deprecated nature of VK_INSTANCE_LAYERS as well as other fixes related to this change. --- docs/LoaderInterfaceArchitecture.md | 7 +- docs/LoaderLayerInterface.md | 2 +- docs/images/loader_layer_order.png | Bin 101536 -> 97431 bytes docs/images/loader_layer_order_calls.png | Bin 105522 -> 92065 bytes docs/images/svgs/loader_layer_order.svg | 200 +++++----- docs/images/svgs/loader_layer_order_calls.svg | 375 ++++++------------ loader/loader.c | 40 +- loader/loader_environment.c | 14 +- loader/loader_environment.h | 2 +- tests/loader_layer_tests.cpp | 49 +++ 10 files changed, 308 insertions(+), 381 deletions(-) diff --git a/docs/LoaderInterfaceArchitecture.md b/docs/LoaderInterfaceArchitecture.md index a1bf4c8fa..99615a0c0 100644 --- a/docs/LoaderInterfaceArchitecture.md +++ b/docs/LoaderInterfaceArchitecture.md @@ -888,9 +888,7 @@ discovery. Known layers are those which are found by the loader taking into account default search paths and other environment variables (like VK_LAYER_PATH). -
- This has replaced the older deprecated environment variable - VK_INSTANCE_LAYERS +
JSON NodeDescription and NotesRestrictionsParentIntrospection Query
This functionality is only available with Loaders built with version @@ -1045,8 +1043,7 @@ may be removed in a future loader release. ppEnabledLayerNames. - This has been deprecated by VK_LOADER_LAYERS_ENABLE. - It also overrides any layers disabled with + It overrides any layers disabled with VK_LOADER_LAYERS_DISABLE. diff --git a/docs/LoaderLayerInterface.md b/docs/LoaderLayerInterface.md index ae640c95e..ea175e132 100644 --- a/docs/LoaderLayerInterface.md +++ b/docs/LoaderLayerInterface.md @@ -563,7 +563,7 @@ i.e. they do not require any external context to be enabled, will be enabled. ##### `VK_INSTANCE_LAYERS` -The original `VK_INSTANCE_LAYERS` can be viewed as a special case of the new +The original `VK_INSTANCE_LAYERS` can be viewed as a special case of `VK_LOADER_LAYERS_ENABLE`. Because of this, any layers enabled via `VK_INSTANCE_LAYERS` will be treated the same as layers enabled with `VK_LOADER_LAYERS_ENABLE` and will therefore diff --git a/docs/images/loader_layer_order.png b/docs/images/loader_layer_order.png index 46cdbd155c7e0cd23c7867caeaf41b4d11f12384..4f4efe8c1ed22022eed919cb523f2384d9ac6650 100644 GIT binary patch literal 97431 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%0A5=a7a@JtleSi3v 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

LF~wbev9 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 uSBL(Q5@L=;a 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 z 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�$&ts#L8pbD3|gS6@SF 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 + inkscape:pagecheckerboard="0" + inkscape:showpageshadow="2" + inkscape:deskcolor="#d1d1d1" /> @@ -760,7 +762,8 @@ inkscape:groupmode="layer" id="layer2" inkscape:label="Background" - transform="translate(11.848806,7.6125679)"> + transform="translate(11.848806,7.6125679)" + style="display:inline"> + + + + + + + + + + + + + transform="translate(141.49609,-63.082703)"> + style="fill:url(#linearGradient4620-0-16-5-1-9);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.33939;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + ry="20.9611" /> EnabledEnabledExplicitLayers + + transform="translate(153.49609,-63.082664)"> ImplicitLayers - - - - - - - - - - - - diff --git a/docs/images/svgs/loader_layer_order_calls.svg b/docs/images/svgs/loader_layer_order_calls.svg index 51e2e8e03..098fe892a 100644 --- a/docs/images/svgs/loader_layer_order_calls.svg +++ b/docs/images/svgs/loader_layer_order_calls.svg @@ -1,11 +1,11 @@ - - - - - + gradientTransform="matrix(1.3793103,0,0,1.9195479,1067.8858,-1869.7148)" /> + inkscape:pagecheckerboard="0" + inkscape:showpageshadow="2" + inkscape:deskcolor="#d1d1d1" /> @@ -576,25 +542,25 @@ inkscape:label="Background" transform="translate(0.62160975,46.138115)"> Driver Driver Driver LoaderTerminator @@ -1077,7 +1043,7 @@ + style="fill:url(#linearGradient4620-0-16-5-3-2);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.35222;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + height="139.37927" + x="640" + y="891.2583" + ry="22.649132" /> EnvironmentEnabledEnabledExplicitLayers ApplicationEnabledExplicitLayers - - - - OverrideLayer - - - - - - - - - - - - - - - - - - - - - - diff --git a/loader/loader.c b/loader/loader.c index 0b76b7afb..8e8c3f2ba 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -4642,15 +4642,20 @@ loader_platform_dl_handle loader_open_layer_file(const struct loader_instance *i // Go through the search_list and find any layers which match type. If layer // type match is found in then add it to ext_list. -VkResult loader_add_implicit_layers(const struct loader_instance *inst, const struct loader_envvar_all_filters *filters, - struct loader_pointer_layer_list *target_list, +// If the layer name is in enabled_layers_env, do not add it to the list, that way it can be ordered alongside the other env-var +// enabled layers +VkResult loader_add_implicit_layers(const struct loader_instance *inst, const char *enabled_layers_env, + const struct loader_envvar_all_filters *filters, struct loader_pointer_layer_list *target_list, struct loader_pointer_layer_list *expanded_target_list, const struct loader_layer_list *source_list) { for (uint32_t src_layer = 0; src_layer < source_list->count; src_layer++) { struct loader_layer_properties *prop = &source_list->list[src_layer]; if (0 == (prop->type_flags & VK_LAYER_TYPE_FLAG_EXPLICIT_LAYER)) { - VkResult result = loader_add_implicit_layer(inst, prop, filters, target_list, expanded_target_list, source_list); - if (result == VK_ERROR_OUT_OF_HOST_MEMORY) return result; + // If this layer appears in the enabled_layers_env, don't add it. We will let loader_add_environment_layers handle it + if (NULL == enabled_layers_env || NULL == strstr(enabled_layers_env, prop->info.layerName)) { + VkResult result = loader_add_implicit_layer(inst, prop, filters, target_list, expanded_target_list, source_list); + if (result == VK_ERROR_OUT_OF_HOST_MEMORY) return result; + } } } return VK_SUCCESS; @@ -4676,6 +4681,7 @@ VkResult loader_enable_instance_layers(struct loader_instance *inst, const VkIns const struct loader_layer_list *instance_layers, const struct loader_envvar_all_filters *layer_filters) { VkResult res = VK_SUCCESS; + char *enabled_layers_env = NULL; assert(inst && "Cannot have null instance"); @@ -4702,15 +4708,17 @@ VkResult loader_enable_instance_layers(struct loader_instance *inst, const VkIns goto out; } + enabled_layers_env = loader_getenv(ENABLED_LAYERS_ENV, inst); + // Add any implicit layers first - res = loader_add_implicit_layers(inst, layer_filters, &inst->app_activated_layer_list, &inst->expanded_activated_layer_list, - instance_layers); + res = loader_add_implicit_layers(inst, enabled_layers_env, layer_filters, &inst->app_activated_layer_list, + &inst->expanded_activated_layer_list, instance_layers); if (res != VK_SUCCESS) { goto out; } // Add any layers specified via environment variable next - res = loader_add_environment_layers(inst, VK_LAYER_TYPE_FLAG_EXPLICIT_LAYER, layer_filters, &inst->app_activated_layer_list, + res = loader_add_environment_layers(inst, enabled_layers_env, layer_filters, &inst->app_activated_layer_list, &inst->expanded_activated_layer_list, instance_layers); if (res != VK_SUCCESS) { goto out; @@ -4722,6 +4730,10 @@ VkResult loader_enable_instance_layers(struct loader_instance *inst, const VkIns warn_if_layers_are_older_than_application(inst); out: + if (enabled_layers_env != NULL) { + loader_free_getenv(enabled_layers_env, inst); + } + return res; } @@ -5565,6 +5577,7 @@ VkResult loader_validate_instance_extensions(struct loader_instance *inst, const const VkInstanceCreateInfo *pCreateInfo) { VkExtensionProperties *extension_prop; char *env_value; + char *enabled_layers_env = NULL; bool check_if_known = true; VkResult res = VK_SUCCESS; @@ -5594,14 +5607,17 @@ VkResult loader_validate_instance_extensions(struct loader_instance *inst, const goto out; } } else { + enabled_layers_env = loader_getenv(ENABLED_LAYERS_ENV, inst); + // Build the lists of active layers (including meta layers) and expanded layers (with meta layers resolved to their // components) - res = loader_add_implicit_layers(inst, layer_filters, &active_layers, &expanded_layers, instance_layers); + res = + loader_add_implicit_layers(inst, enabled_layers_env, layer_filters, &active_layers, &expanded_layers, instance_layers); if (res != VK_SUCCESS) { goto out; } - res = loader_add_environment_layers(inst, VK_LAYER_TYPE_FLAG_EXPLICIT_LAYER, layer_filters, &active_layers, - &expanded_layers, instance_layers); + res = loader_add_environment_layers(inst, enabled_layers_env, layer_filters, &active_layers, &expanded_layers, + instance_layers); if (res != VK_SUCCESS) { goto out; } @@ -5687,6 +5703,10 @@ VkResult loader_validate_instance_extensions(struct loader_instance *inst, const out: loader_destroy_pointer_layer_list(inst, &active_layers); loader_destroy_pointer_layer_list(inst, &expanded_layers); + if (enabled_layers_env != NULL) { + loader_free_getenv(enabled_layers_env, inst); + } + return res; } diff --git a/loader/loader_environment.c b/loader/loader_environment.c index 77593a9d3..330c3598f 100644 --- a/loader/loader_environment.c +++ b/loader/loader_environment.c @@ -448,20 +448,20 @@ bool check_name_matches_filter_environment_var(const char *name, const struct lo // Get the layer name(s) from the env_name environment variable. If layer is found in // search_list then add it to layer_list. But only add it to layer_list if type_flags matches. -VkResult loader_add_environment_layers(struct loader_instance *inst, const enum layer_type_flags type_flags, +VkResult loader_add_environment_layers(struct loader_instance *inst, const char *enabled_layers_env, const struct loader_envvar_all_filters *filters, struct loader_pointer_layer_list *target_list, struct loader_pointer_layer_list *expanded_target_list, const struct loader_layer_list *source_list) { VkResult res = VK_SUCCESS; - char *layer_env = loader_getenv(ENABLED_LAYERS_ENV, inst); + const enum layer_type_flags type_flags = VK_LAYER_TYPE_FLAG_EXPLICIT_LAYER; // If the layer environment variable is present (i.e. VK_INSTANCE_LAYERS), we will always add it to the layer list. - if (layer_env != NULL) { - size_t layer_env_len = strlen(layer_env) + 1; + if (enabled_layers_env != NULL) { + size_t layer_env_len = strlen(enabled_layers_env) + 1; char *name = loader_stack_alloc(layer_env_len); if (name != NULL) { - loader_strncpy(name, layer_env_len, layer_env, layer_env_len); + loader_strncpy(name, layer_env_len, enabled_layers_env, layer_env_len); loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0, "env var \'%s\' defined and adding layers \"%s\"", ENABLED_LAYERS_ENV, name); @@ -560,10 +560,6 @@ VkResult loader_add_environment_layers(struct loader_instance *inst, const enum out: - if (layer_env != NULL) { - loader_free_getenv(layer_env, inst); - } - return res; } diff --git a/loader/loader_environment.h b/loader/loader_environment.h index da35237b5..ec9367dd3 100644 --- a/loader/loader_environment.h +++ b/loader/loader_environment.h @@ -49,7 +49,7 @@ VkResult parse_layers_disable_filter_environment_var(const struct loader_instanc struct loader_envvar_disable_layers_filter *disable_struct); VkResult parse_layer_environment_var_filters(const struct loader_instance *inst, struct loader_envvar_all_filters *layer_filters); bool check_name_matches_filter_environment_var(const char *name, const struct loader_envvar_filter *filter_struct); -VkResult loader_add_environment_layers(struct loader_instance *inst, const enum layer_type_flags type_flags, +VkResult loader_add_environment_layers(struct loader_instance *inst, const char *enabled_layers_env, const struct loader_envvar_all_filters *filters, struct loader_pointer_layer_list *target_list, struct loader_pointer_layer_list *expanded_target_list, diff --git a/tests/loader_layer_tests.cpp b/tests/loader_layer_tests.cpp index a98850996..c21d847e8 100644 --- a/tests/loader_layer_tests.cpp +++ b/tests/loader_layer_tests.cpp @@ -1176,6 +1176,55 @@ TEST(ImplicitLayers, DuplicateLayersInVK_ADD_IMPLICIT_LAYER_PATH) { ASSERT_FALSE(env.debug_log.find("actually_layer_2")); } +TEST(ImplicitLayers, OrderedByVK_INSTANCE_LAYERS) { + FrameworkEnvironment env; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); + const char* implicit_layer_name = "VK_LAYER_implicit"; + env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} + .set_name(implicit_layer_name) + + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_disable_environment("foo")), + "implicit_layer.json"); + const char* explicit_layer_name = "VK_LAYER_explicit"; + env.add_explicit_layer( + ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name(explicit_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "explicit_layer.json"); + // Only enable the explicit layer through the env-var + { + EnvVarWrapper env_var("VK_INSTANCE_LAYERS"); + env_var.add_to_list(explicit_layer_name); + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 2); + ASSERT_TRUE(string_eq(active_layers.at(0).layerName, implicit_layer_name)); + ASSERT_TRUE(string_eq(active_layers.at(1).layerName, explicit_layer_name)); + } + // Enable both layers, implicit then explicit + { + EnvVarWrapper env_var("VK_INSTANCE_LAYERS"); + env_var.add_to_list(implicit_layer_name); + env_var.add_to_list(explicit_layer_name); + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 2); + ASSERT_TRUE(string_eq(active_layers.at(0).layerName, implicit_layer_name)); + ASSERT_TRUE(string_eq(active_layers.at(1).layerName, explicit_layer_name)); + } + // Enable both layers, explicit then implicit + { + EnvVarWrapper env_var("VK_INSTANCE_LAYERS"); + env_var.add_to_list(explicit_layer_name); + env_var.add_to_list(implicit_layer_name); + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 2); + ASSERT_TRUE(string_eq(active_layers.at(0).layerName, explicit_layer_name)); + ASSERT_TRUE(string_eq(active_layers.at(1).layerName, implicit_layer_name)); + } +} + // Meta layer which contains component layers that do not exist. TEST(MetaLayers, InvalidComponentLayer) { FrameworkEnvironment env; From ade310ac6b62af86e6c7bdbe08a2fab189ed76b6 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 25 Aug 2025 16:54:08 -0500 Subject: [PATCH 38/65] Fix spelling mistakes --- BUILD.md | 2 +- CMakeLists.txt | 8 ++++---- CONTRIBUTING.md | 2 +- loader/CMakeLists.txt | 2 +- loader/cJSON.h | 2 +- loader/generated/vk_object_types.h | 2 +- loader/loader.c | 10 +++++----- loader/loader_environment.c | 4 ++-- loader/loader_environment.h | 4 ++-- loader/loader_windows.c | 2 +- loader/settings.c | 2 +- loader/trampoline.c | 12 ++++++------ scripts/generate_source.py | 2 +- scripts/generators/helper_file_generator.py | 2 +- tests/loader_regression_tests.cpp | 2 +- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/BUILD.md b/BUILD.md index 8c37b70ba..d5623ddfa 100644 --- a/BUILD.md +++ b/BUILD.md @@ -233,7 +233,7 @@ cmake --build . --target loader_codegen `clang-format` is run on generated code files so that the generator scripts do not need format their output manually. If `clang-format` is not available when running code generation, a warning will -be issued but does not stop code generation from occuring. +be issued but does not stop code generation from occurring. ### Build Options diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e370dbc3..75dca4ad6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,7 +230,7 @@ if(CMAKE_C_COMPILER_ID MATCHES "MSVC" OR (CMAKE_C_COMPILER_ID STREQUAL "Clang" A # Prevent from polluting the code. guards against things like MIN and MAX target_compile_definitions(loader_common_options INTERFACE WIN32_LEAN_AND_MEAN) - # For some reason Advapi32.lib needs to be explicitely linked to when building for Arm (32 bit) on Windows, but isn't required on any other architecture + # For some reason Advapi32.lib needs to be explicitly linked to when building for Arm (32 bit) on Windows, but isn't required on any other architecture if (SYSTEM_PROCESSOR MATCHES "arm" AND CMAKE_SIZEOF_VOID_P EQUAL 4) target_link_libraries(loader_common_options INTERFACE Advapi32) endif() @@ -241,7 +241,7 @@ endif() target_compile_definitions(loader_common_options INTERFACE $<$:DEBUG;GIT_BRANCH_NAME="${GIT_BRANCH_NAME}";GIT_TAG_INFO="${GIT_TAG_INFO}">) if (NOT (WIN32 OR APPLE)) - # Check for the existance of the secure_getenv or __secure_getenv commands + # Check for the existence of the secure_getenv or __secure_getenv commands include(CheckFunctionExists) check_function_exists(secure_getenv HAVE_SECURE_GETENV) @@ -254,12 +254,12 @@ if (NOT (WIN32 OR APPLE)) target_compile_definitions(loader_common_options INTERFACE HAVE___SECURE_GETENV) endif() if (NOT (HAVE_SECURE_GETENV OR HAVE___SECURE_GETENV)) - message(WARNING "Using non-secure environmental lookups. This loader will not properly disable environent variables when run with elevated permissions.") + message(WARNING "Using non-secure environmental lookups. This loader will not properly disable environment variables when run with elevated permissions.") endif() endif() if (NOT (WIN32)) - # Check for the existance of the realpath() command + # Check for the existence of the realpath() command include(CheckFunctionExists) check_function_exists(realpath HAVE_REALPATH) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b826a02a..e3c717b09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,7 @@ current assignee. * Run **clang-format** on your changes to maintain consistent formatting * There are `.clang-format` files present in the repository to define clang-format settings which are found and used automatically by clang-format. - * **clang-format** binaries are available from the LLVM orginization, here: + * **clang-format** binaries are available from the LLVM organization, here: [LLVM](https://clang.llvm.org/). Our CI system currently uses clang-format version 16 to check that the lines of code you have changed are formatted properly. diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt index 4d5488ef6..008b71da4 100644 --- a/loader/CMakeLists.txt +++ b/loader/CMakeLists.txt @@ -312,7 +312,7 @@ elseif(UNIX OR MINGW OR (WIN32 AND USE_GAS)) # i.e.: Linux & Apple & MinGW & Win if(ASSEMBLER_WORKS) add_executable(asm_offset asm_offset.c) target_link_libraries(asm_offset loader_specific_options) - # If not cross compiling, run asm_offset to generage gen_defines.asm + # If not cross compiling, run asm_offset to generate gen_defines.asm if (NOT CMAKE_CROSSCOMPILING) add_custom_command(OUTPUT gen_defines.asm DEPENDS asm_offset COMMAND asm_offset GAS) else() diff --git a/loader/cJSON.h b/loader/cJSON.h index 457d669a5..0bba4995f 100644 --- a/loader/cJSON.h +++ b/loader/cJSON.h @@ -146,7 +146,7 @@ typedef int cJSON_bool; #define CJSON_CIRCULAR_LIMIT 10000 #endif -/* Memory Management: the caller is always responsible to free instthe results from all variants of loader_cJSON_Parse (with +/* Memory Management: the caller is always responsible to free the results from all variants of loader_cJSON_Parse (with * loader_cJSON_Delete) and loader_loader_cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The * exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ /* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ diff --git a/loader/generated/vk_object_types.h b/loader/generated/vk_object_types.h index 6d30aef0a..30a5bb10c 100644 --- a/loader/generated/vk_object_types.h +++ b/loader/generated/vk_object_types.h @@ -97,7 +97,7 @@ typedef enum VulkanObjectType { kVulkanObjectTypeIndirectExecutionSetEXT = 56, kVulkanObjectTypeIndirectCommandsLayoutEXT = 57, kVulkanObjectTypeMax = 58, - // Aliases for backwards compatibilty of "promoted" types + // Aliases for backwards compatibility of "promoted" types kVulkanObjectTypeSamplerYcbcrConversionKHR = kVulkanObjectTypeSamplerYcbcrConversion, kVulkanObjectTypeDescriptorUpdateTemplateKHR = kVulkanObjectTypeDescriptorUpdateTemplate, kVulkanObjectTypePrivateDataSlotEXT = kVulkanObjectTypePrivateDataSlot, diff --git a/loader/loader.c b/loader/loader.c index 8e8c3f2ba..3d11c3322 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -306,7 +306,7 @@ VkResult create_string_list(const struct loader_instance *inst, uint32_t allocat return VK_SUCCESS; } -VkResult incrase_str_capacity_by_at_least_one(const struct loader_instance *inst, struct loader_string_list *string_list) { +VkResult increase_str_capacity_by_at_least_one(const struct loader_instance *inst, struct loader_string_list *string_list) { assert(string_list); if (string_list->allocated_count == 0) { string_list->allocated_count = 32; @@ -329,7 +329,7 @@ VkResult incrase_str_capacity_by_at_least_one(const struct loader_instance *inst VkResult append_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, char *str) { assert(string_list && str); - VkResult res = incrase_str_capacity_by_at_least_one(inst, string_list); + VkResult res = increase_str_capacity_by_at_least_one(inst, string_list); if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { loader_instance_heap_free(inst, str); // Must clean up in case of failure return res; @@ -340,7 +340,7 @@ VkResult append_str_to_string_list(const struct loader_instance *inst, struct lo VkResult prepend_str_to_string_list(const struct loader_instance *inst, struct loader_string_list *string_list, char *str) { assert(string_list && str); - VkResult res = incrase_str_capacity_by_at_least_one(inst, string_list); + VkResult res = increase_str_capacity_by_at_least_one(inst, string_list); if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { loader_instance_heap_free(inst, str); // Must clean up in case of failure return res; @@ -546,7 +546,7 @@ VkResult normalize_path(const struct loader_instance *inst, char **passed_in_pat #endif } -// Queries the path to the library that lib_handle & gipa are assoicated with, allocating a string to hold it and returning it in +// Queries the path to the library that lib_handle & gipa are associated with, allocating a string to hold it and returning it in // out_path VkResult get_library_path_of_dl_handle(const struct loader_instance *inst, loader_platform_dl_handle lib_handle, PFN_vkGetInstanceProcAddr gipa, char **out_path) { @@ -2539,7 +2539,7 @@ bool verify_meta_layer_component_layers(const struct loader_instance *inst, size } if (comp_prop_index != INT32_MAX && already_checked_meta_layers[comp_prop_index]) { loader_log(inst, VULKAN_LOADER_WARN_BIT, 0, - "verify_meta_layer_component_layers: Recursive depedency between Meta-layer %s and Meta-layer %s. " + "verify_meta_layer_component_layers: Recursive dependency between Meta-layer %s and Meta-layer %s. " "Skipping this layer.", instance_layers->list[prop_index].info.layerName, comp_prop->info.layerName); return false; diff --git a/loader/loader_environment.c b/loader/loader_environment.c index 330c3598f..8dd8493bb 100644 --- a/loader/loader_environment.c +++ b/loader/loader_environment.c @@ -563,8 +563,8 @@ VkResult loader_add_environment_layers(struct loader_instance *inst, const char return res; } -void parse_id_filter_enviroment_var(const struct loader_instance *inst, const char *env_var_name, - struct loader_envvar_id_filter *filter_struct) { +void parse_id_filter_environment_var(const struct loader_instance *inst, const char *env_var_name, + struct loader_envvar_id_filter *filter_struct) { memset(filter_struct, 0, sizeof(struct loader_envvar_id_filter)); char *parsing_string = NULL; char *env_var_value = loader_secure_getenv(env_var_name, inst); diff --git a/loader/loader_environment.h b/loader/loader_environment.h index ec9367dd3..ddbc29f10 100644 --- a/loader/loader_environment.h +++ b/loader/loader_environment.h @@ -55,6 +55,6 @@ VkResult loader_add_environment_layers(struct loader_instance *inst, const char struct loader_pointer_layer_list *expanded_target_list, const struct loader_layer_list *source_list); -void parse_id_filter_enviroment_var(const struct loader_instance *inst, const char *env_var_name, - struct loader_envvar_id_filter *filter_struct); +void parse_id_filter_environment_var(const struct loader_instance *inst, const char *env_var_name, + struct loader_envvar_id_filter *filter_struct); bool check_id_matches_filter_environment_var(const uint32_t id, const struct loader_envvar_id_filter *filter_struct); diff --git a/loader/loader_windows.c b/loader/loader_windows.c index f73659c09..a3d5d5d3d 100644 --- a/loader/loader_windows.c +++ b/loader/loader_windows.c @@ -1112,7 +1112,7 @@ char *windows_get_app_package_manifest_path(const struct loader_instance *inst) UINT32 numPackages = 0, bufferLength = 0; // This literal string identifies the Microsoft-published OpenCL, OpenGL, and Vulkan Compatibility Pack, which contains - // OpenGLOn12, OpenCLOn12, and VulkanOn12 (aka Dozen) mappinglayers + // OpenGLOn12, OpenCLOn12, and VulkanOn12 (aka Dozen) mapping layers PCWSTR familyName = L"Microsoft.D3DMappingLayers_8wekyb3d8bbwe"; if (ERROR_INSUFFICIENT_BUFFER != fpGetPackagesByPackageFamily(familyName, &numPackages, NULL, &bufferLength, NULL) || numPackages == 0 || bufferLength == 0) { diff --git a/loader/settings.c b/loader/settings.c index 5d15e4964..0e5c8681a 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -397,7 +397,7 @@ VkResult parse_device_configurations(const struct loader_instance* inst, cJSON* #if COMMON_UNIX_PLATFORMS // Given a base and suffix path, determine if a file at that location exists, and if it is return success. -// Since base may contain multiple paths seperated by PATH_SEPARATOR, we must extract each segment and check segment + suffix +// Since base may contain multiple paths separated by PATH_SEPARATOR, we must extract each segment and check segment + suffix // individually VkResult check_if_settings_path_exists(const struct loader_instance* inst, const char* base, const char* suffix, char** settings_file_path) { diff --git a/loader/trampoline.c b/loader/trampoline.c index 73e55ef64..a1e72147d 100644 --- a/loader/trampoline.c +++ b/loader/trampoline.c @@ -857,9 +857,9 @@ LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstan struct loader_envvar_id_filter vendor_id_filter; struct loader_envvar_id_filter driver_id_filter; - parse_id_filter_enviroment_var(inst, VK_DEVICE_ID_FILTER_ENV_VAR, &device_id_filter); - parse_id_filter_enviroment_var(inst, VK_VENDOR_ID_FILTER_ENV_VAR, &vendor_id_filter); - parse_id_filter_enviroment_var(inst, VK_DRIVER_ID_FILTER_ENV_VAR, &driver_id_filter); + parse_id_filter_environment_var(inst, VK_DEVICE_ID_FILTER_ENV_VAR, &device_id_filter); + parse_id_filter_environment_var(inst, VK_VENDOR_ID_FILTER_ENV_VAR, &vendor_id_filter); + parse_id_filter_environment_var(inst, VK_DRIVER_ID_FILTER_ENV_VAR, &driver_id_filter); // Call down the chain to get the physical device info if ((0 == device_id_filter.count) && (0 == vendor_id_filter.count) && (0 == driver_id_filter.count)) { @@ -2617,9 +2617,9 @@ LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups( struct loader_envvar_id_filter vendor_id_filter; struct loader_envvar_id_filter driver_id_filter; - parse_id_filter_enviroment_var(inst, VK_DEVICE_ID_FILTER_ENV_VAR, &device_id_filter); - parse_id_filter_enviroment_var(inst, VK_VENDOR_ID_FILTER_ENV_VAR, &vendor_id_filter); - parse_id_filter_enviroment_var(inst, VK_DRIVER_ID_FILTER_ENV_VAR, &driver_id_filter); + parse_id_filter_environment_var(inst, VK_DEVICE_ID_FILTER_ENV_VAR, &device_id_filter); + parse_id_filter_environment_var(inst, VK_VENDOR_ID_FILTER_ENV_VAR, &vendor_id_filter); + parse_id_filter_environment_var(inst, VK_DRIVER_ID_FILTER_ENV_VAR, &driver_id_filter); // Call down the chain to get the physical device group info. if ((0 == device_id_filter.count) && (0 == vendor_id_filter.count) && (0 == driver_id_filter.count)) { diff --git a/scripts/generate_source.py b/scripts/generate_source.py index 65661152b..7af0479bf 100755 --- a/scripts/generate_source.py +++ b/scripts/generate_source.py @@ -122,7 +122,7 @@ def RunGenerators(api: str, registry: str, directory: str, styleFile: str, targe for index, target in enumerate(targets, start=1): print(f'[{index}|{len(targets)}] Generating {target}') - # First grab a class contructor object and create an instance + # First grab a class constructor object and create an instance generator = generators[target]['generator'] gen = generator() diff --git a/scripts/generators/helper_file_generator.py b/scripts/generators/helper_file_generator.py index e3c756a06..7cea3dff6 100644 --- a/scripts/generators/helper_file_generator.py +++ b/scripts/generators/helper_file_generator.py @@ -137,7 +137,7 @@ def generate(self): out.append(f' kVulkanObjectType{name} = {number},\n') out.append(f' kVulkanObjectTypeMax = {enum_num},\n') - out.append(' // Aliases for backwards compatibilty of "promoted" types\n') + out.append(' // Aliases for backwards compatibility of "promoted" types\n') for name, aliases in object_type_aliases.items(): for alias in aliases: out.append(f' kVulkanObjectType{alias[2:]} = kVulkanObjectType{name[2:]},\n') diff --git a/tests/loader_regression_tests.cpp b/tests/loader_regression_tests.cpp index cae8c211b..4d0292efa 100644 --- a/tests/loader_regression_tests.cpp +++ b/tests/loader_regression_tests.cpp @@ -5280,7 +5280,7 @@ TEST(DriverUnloadingFromZeroPhysDevs, HandleRecreation) { surfaces.emplace_back(surface, inst.inst, env.vulkan_functions.vkDestroySurfaceKHR); } // Remove some elements arbitrarily - remove 15 of each - // Do it backwards so the indexes are 'corect' + // Do it backwards so the indexes are 'correct' for (uint32_t i = 31; i > 2; i -= 2) { messengers.erase(messengers.begin() + i); surfaces.erase(surfaces.begin() + i); From 35a62d3181eb9c238299a62618b49e521f3fe7b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 06:37:42 +0000 Subject: [PATCH 39/65] build(deps): bump github/codeql-action from 3.29.10 to 3.29.11 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.10 to 3.29.11. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/96f518a34f7a870018057716cc4d7a5c014bd61c...3c3833e0f8c1c83d449a7478aa59c036a9165498) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.11 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c1e0ca251..cea673f83 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 + uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 + uses: github/codeql-action/autobuild@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 - uses: actions/setup-python@v5 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 + uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 with: category: "/language:${{matrix.language}}" From 42e255d626c4c9981fccf6ae047fce155fc2c3cf Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 26 Aug 2025 08:55:15 -0500 Subject: [PATCH 40/65] Fix invalid iteration of empty override path Found by OSS-Fuzz --- loader/loader.c | 8 ++++++-- ...zed-instance_enumerate_fuzzer-6740380288876544 | Bin 0 -> 616 bytes tests/loader_fuzz_tests.cpp | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6740380288876544 diff --git a/loader/loader.c b/loader/loader.c index 3d11c3322..821872174 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -4250,9 +4250,13 @@ VkResult get_override_layer_override_paths(struct loader_instance *inst, struct for (uint32_t j = 0; j < prop->override_paths.count; j++) { copy_data_file_info(prop->override_paths.list[j], NULL, 0, &cur_write_ptr); } + + // Subtract one from cur_write_ptr only if something was written so we can set the null terminator + if (*override_paths < cur_write_ptr) { + --cur_write_ptr; + assert(cur_write_ptr - (*override_paths) < (ptrdiff_t)override_path_size); + } // Remove the last path separator - --cur_write_ptr; - assert(cur_write_ptr - (*override_paths) < (ptrdiff_t)override_path_size); *cur_write_ptr = '\0'; loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0, "Override layer has override paths set to %s", *override_paths); diff --git a/tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6740380288876544 b/tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6740380288876544 new file mode 100644 index 0000000000000000000000000000000000000000..c5a8b7b2393466d4074c87d34d00c54db9b60b97 GIT binary patch literal 616 zcmaJ;O;5r=5arCtvtGtI7eb1q$gLVej14A;CZ;x<6_&WMADea|A%vg6)!*RVKj6W? z;K?7O(?V-8#yPe7X5PGcZv{A$fnc*Z3Attqk)$$?u!klZ81Nc=l9gF%k)aAiC?d@@@%>8>#;LD@0SvP zA@77_bNdzpTFq0_Y`1N*)$Vky6Wg}BExXxhP;TYUe^Myga4OPCB2|`BCVfm+K=6@e mx`$wsLjA)#&o{7enJKOxDIX8-=Wj2aPVJyp6Ab+|#q|vasJo~D literal 0 HcmV?d00001 diff --git a/tests/loader_fuzz_tests.cpp b/tests/loader_fuzz_tests.cpp index 8ac252cd2..13936d316 100644 --- a/tests/loader_fuzz_tests.cpp +++ b/tests/loader_fuzz_tests.cpp @@ -212,6 +212,10 @@ TEST(BadJsonInput, ClusterFuzzTestCase_6465902356791296) { // Causes an integer overflow - instance_enumerate_fuzzer: Integer-overflow in parse_value execute_instance_enumerate_fuzzer("clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6465902356791296"); } +TEST(BadJsonInput, ClusterFuzzTestCase_6740380288876544) { + // Does crash with ASAN + execute_instance_enumerate_fuzzer("clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6740380288876544"); +} TEST(BadJsonInput, ClusterFuzzTestCase_4512865114259456) { // Does crash with UBSAN and ASAN // malloc(): invalid size (unsorted) From 272251ff5cd445cefae1e9425187329330075997 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Wed, 27 Aug 2025 12:11:44 -0500 Subject: [PATCH 41/65] Add null check before strcmp Adds a test that exercises this situation as well. --- loader/settings.c | 1 + tests/loader_settings_tests.cpp | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/loader/settings.c b/loader/settings.c index 0e5c8681a..f02a688e6 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -973,6 +973,7 @@ TEST_FUNCTION_EXPORT VkResult get_settings_layers(const struct loader_instance* if (0 == strncmp(settings_layers->list[j].info.layerName, newly_added_layer->info.layerName, VK_MAX_EXTENSION_NAME_SIZE)) { if (0 == (newly_added_layer->type_flags & VK_LAYER_TYPE_FLAG_META_LAYER) && + settings_layers->list[j].lib_name != NULL && newly_added_layer->lib_name != NULL && strcmp(settings_layers->list[j].lib_name, newly_added_layer->lib_name) == 0) { should_remove = true; break; diff --git a/tests/loader_settings_tests.cpp b/tests/loader_settings_tests.cpp index fae400367..a276bcce5 100644 --- a/tests/loader_settings_tests.cpp +++ b/tests/loader_settings_tests.cpp @@ -1001,6 +1001,55 @@ TEST(SettingsFile, MetaLayerAlsoActivates) { } } +TEST(SettingsFile, DuplicateMetaLayers) { + FrameworkEnvironment env{}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); + + const char* explicit_layer_name = "VK_LAYER_Regular_TestLayer"; + env.add_explicit_layer( + ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name(explicit_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "explicit_test_layer.json"); + // Add an implicit layer and a meta layer that share a name + const char* meta_layer_name = "VK_LAYER_meta_layer"; + env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} + .set_name(meta_layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_disable_environment("NotGonnaWork")), + "implicit_layer.json"); + env.add_implicit_layer( + ManifestLayer{}.set_file_format_version({1, 1, 2}).add_layer(ManifestLayer::LayerDescription{} + .set_name(meta_layer_name) + .add_component_layer(explicit_layer_name) + .set_disable_environment("NotGonnaWork")), + "meta_test_layer.json"); + + env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{} + .add_stderr_log_filter("all") + .add_layer_configuration(LoaderSettingsLayerConfiguration{} + .set_name(explicit_layer_name) + .set_path(env.get_shimmed_layer_manifest_path(0)) + .set_control("auto") + .set_treat_as_implicit_manifest(false)) + .add_layer_configuration(LoaderSettingsLayerConfiguration{}.set_control("unordered_layer_location")) + .add_layer_configuration( + LoaderSettingsLayerConfiguration{} + .set_name(meta_layer_name) + .set_path(env.get_folder(ManifestLocation::implicit_layer).location() / "meta_test_layer.json") + .set_control("auto") + .set_treat_as_implicit_manifest(true)) + .add_layer_configuration(LoaderSettingsLayerConfiguration{} + .set_name(meta_layer_name) + .set_path(env.get_shimmed_layer_manifest_path(1)) + .set_control("auto") + .set_treat_as_implicit_manifest(true)) + + )); + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); +} + // Layers are correctly ordered by settings file. TEST(SettingsFile, LayerOrdering) { FrameworkEnvironment env{}; From 59240af68451d5503b03c4d9d9ab8e64fb89bfc0 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Wed, 27 Aug 2025 15:55:04 -0500 Subject: [PATCH 42/65] Fix uninitialized fuzz test executor --- tests/loader_fuzz_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/loader_fuzz_tests.cpp b/tests/loader_fuzz_tests.cpp index 13936d316..e23e479a2 100644 --- a/tests/loader_fuzz_tests.cpp +++ b/tests/loader_fuzz_tests.cpp @@ -40,7 +40,7 @@ void execute_instance_enumerate_fuzzer(std::filesystem::path const& filename) { env.write_file_from_source((std::filesystem::path(CLUSTERFUZZ_TESTCASE_DIRECTORY) / filename).string().c_str(), ManifestCategory::settings, ManifestLocation::settings_location, "vk_loader_settings.json"); - uint32_t pPropertyCount; + uint32_t pPropertyCount = 1; VkExtensionProperties pProperties = {0}; env.vulkan_functions.vkEnumerateInstanceExtensionProperties("test_auto", &pPropertyCount, &pProperties); From f61591c3b3bcb65fe3dd084c285e72eaa3249bb1 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Wed, 27 Aug 2025 15:55:46 -0500 Subject: [PATCH 43/65] Add fuzz test memory leak case --- ...d-instance_enumerate_fuzzer-6470575830925312 | Bin 0 -> 64485 bytes tests/loader_fuzz_tests.cpp | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6470575830925312 diff --git a/tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6470575830925312 b/tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6470575830925312 new file mode 100644 index 0000000000000000000000000000000000000000..a38496a709bed09eb13196e8c10873e4fea694c5 GIT binary patch literal 64485 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 JeB6pR{|8XvoB;p; literal 0 HcmV?d00001 diff --git a/tests/loader_fuzz_tests.cpp b/tests/loader_fuzz_tests.cpp index e23e479a2..4298ecf33 100644 --- a/tests/loader_fuzz_tests.cpp +++ b/tests/loader_fuzz_tests.cpp @@ -135,7 +135,9 @@ TEST(BadJsonInput, ClusterFuzzTestCase_6583684169269248) { // Nullptr dereference in loader_copy_to_new_str execute_instance_enumerate_fuzzer("clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6583684169269248"); } - +TEST(BadJsonInput, ClusterFuzzTestCase_6470575830925312) { + execute_instance_enumerate_fuzzer("clusterfuzz-testcase-minimized-instance_enumerate_fuzzer-6470575830925312"); +} TEST(BadJsonInput, ClusterFuzzTestCase_5258042868105216) { // Doesn't crash with ASAN or UBSAN // Doesn't reproducibly crash - json_load_fuzzer: Abrt in loader_cJSON_Delete From 8b7e618308605235f61d78fa47a3785190440467 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Wed, 27 Aug 2025 15:55:58 -0500 Subject: [PATCH 44/65] Fix unused settings causing memory leaks --- loader/settings.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/loader/settings.c b/loader/settings.c index f02a688e6..38667edab 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -763,14 +763,14 @@ VkResult get_loader_settings(const struct loader_instance* inst, loader_settings cJSON_ArrayForEach(log_element, logs_to_use) { // bool is_valid = true; struct loader_string_list log_destinations = {0}; - res = loader_parse_json_array_of_strings(inst, log_element, "destinations", &log_destinations); - if (res != VK_SUCCESS) { + VkResult parse_dest_res = loader_parse_json_array_of_strings(inst, log_element, "destinations", &log_destinations); + if (parse_dest_res != VK_SUCCESS) { // is_valid = false; } free_string_list(inst, &log_destinations); struct loader_string_list log_filters = {0}; - res = loader_parse_json_array_of_strings(inst, log_element, "filters", &log_filters); - if (res != VK_SUCCESS) { + VkResult parse_filters_res = loader_parse_json_array_of_strings(inst, log_element, "filters", &log_filters); + if (parse_filters_res != VK_SUCCESS) { // is_valid = false; } free_string_list(inst, &log_filters); From fe92c7d7e54664b1d3f3a0d734fd6f2ffd92e485 Mon Sep 17 00:00:00 2001 From: Mike Schuchardt Date: Fri, 29 Aug 2025 14:37:57 -0700 Subject: [PATCH 45/65] build: Update to header 1.4.326 --- CMakeLists.txt | 2 +- loader/loader.rc | 4 ++-- scripts/known_good.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 75dca4ad6..5b34889c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.325 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.326 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/loader.rc b/loader/loader.rc index c5ac568aa..7f79b55cd 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 325, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.325.Dev Build" +#define VER_FILE_VERSION 1, 4, 326, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.326.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index 88b5ea889..023020974 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.325" + "commit": "v1.4.326" }, { "name": "googletest", @@ -42,4 +42,4 @@ "googletest": "GOOGLETEST_INSTALL_DIR", "detours": "DETOURS_INSTALL_DIR" } -} +} \ No newline at end of file From 3af548220a6a256fdb7e03443ce92d26b2fc3b84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:51:49 +0000 Subject: [PATCH 46/65] build(deps): bump github/codeql-action from 3.29.11 to 3.30.0 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.11 to 3.30.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/3c3833e0f8c1c83d449a7478aa59c036a9165498...2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index cea673f83..749a2f502 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 + uses: github/codeql-action/init@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 + uses: github/codeql-action/autobuild@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.29.5 - uses: actions/setup-python@v5 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 + uses: github/codeql-action/analyze@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.29.5 with: category: "/language:${{matrix.language}}" From 344e4e6888b2a901bc7cf11028f8bc34c07738a8 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Thu, 11 Sep 2025 21:54:54 -0500 Subject: [PATCH 47/65] Fix memory leak in get_loader_settings Caused by the result value containing a spurious initialization failed error code. --- loader/settings.c | 6 ++++-- ...se-minimized-settings_fuzzer-4626669072875520 | Bin 0 -> 3323 bytes tests/loader_fuzz_tests.cpp | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-settings_fuzzer-4626669072875520 diff --git a/loader/settings.c b/loader/settings.c index 38667edab..365cfb9d0 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -748,8 +748,10 @@ VkResult get_loader_settings(const struct loader_instance* inst, loader_settings cJSON* stderr_filter = loader_cJSON_GetObjectItem(settings_to_use, "stderr_log"); if (NULL != stderr_filter) { struct loader_string_list stderr_log = {0}; - res = loader_parse_json_array_of_strings(inst, settings_to_use, "stderr_log", &stderr_log); - if (VK_ERROR_OUT_OF_HOST_MEMORY == res) { + VkResult stderr_log_result = VK_SUCCESS; + stderr_log_result = loader_parse_json_array_of_strings(inst, settings_to_use, "stderr_log", &stderr_log); + if (VK_ERROR_OUT_OF_HOST_MEMORY == stderr_log_result) { + res = VK_ERROR_OUT_OF_HOST_MEMORY; goto out; } loader_settings->debug_level = parse_log_filters_from_strings(&stderr_log); diff --git a/tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-settings_fuzzer-4626669072875520 b/tests/framework/data/fuzz_test_minimized_test_cases/clusterfuzz-testcase-minimized-settings_fuzzer-4626669072875520 new file mode 100644 index 0000000000000000000000000000000000000000..ad9eccc99ff54648d886f4601bf3a0fa90a9ab0a GIT binary patch literal 3323 zcmeHK%WB*(6z#6iALyc+;jTj_Dd~f(nnE9tp#v!tN*GjSEKNmh$;h(9B!qlGq5XhD zf2{j{MPYicJytBc4l|2{5TeD{NJm%qTuH}g&K?c0fmE?z(%6JMRtTFZqhWUd+nY}} zw_u~fWWqV6XBp$x@;MG4mi;vU%)gbKBG5#va$etOf>4O{(1LX^^={Sj%EFqqTv@F>DM#X=&(r!mXxfDy+ok_SVkJXD^;( zM+6+Hg_^Vc`3OnX&aj{PuG?r`w`q{9OFbR&^rN$Yus zFmy@Uj_$#@M-I0Jb}L?!pb&&Q4|+OD61BCmn;Pw`iRmEl++hWS_&zVq6G0ep$lb=PJ||d(ls8HF+x9FTOWp2G8YgW?)peC?>`8AeX-G=v)Z0R(~NtM z-MwmCpI6|&+;Qj%le>eL8 literal 0 HcmV?d00001 diff --git a/tests/loader_fuzz_tests.cpp b/tests/loader_fuzz_tests.cpp index 4298ecf33..7e3ba0593 100644 --- a/tests/loader_fuzz_tests.cpp +++ b/tests/loader_fuzz_tests.cpp @@ -287,3 +287,6 @@ TEST(BadJsonInput, ClusterFuzzTestCase_5123849246867456) { // Causes a leak - settings_fuzzer: Direct-leak in loader_append_layer_property execute_setting_fuzzer("clusterfuzz-testcase-minimized-settings_fuzzer-5123849246867456"); } +TEST(BadJsonInput, ClusterFuzzTestCase_4626669072875520) { + execute_setting_fuzzer("clusterfuzz-testcase-minimized-settings_fuzzer-4626669072875520"); +} From 9501885e08177da2d2b474491ff066d171eb6262 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 12 Sep 2025 16:09:12 -0500 Subject: [PATCH 48/65] Prevent unnecessary allocations in test util We can use string_view's to prevent allocating a string every time get_folder_for_given_path is called. --- tests/framework/util/folder_manager.cpp | 16 +++++++--------- tests/framework/util/folder_manager.h | 3 ++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/framework/util/folder_manager.cpp b/tests/framework/util/folder_manager.cpp index c406309ed..53bbba661 100644 --- a/tests/framework/util/folder_manager.cpp +++ b/tests/framework/util/folder_manager.cpp @@ -187,20 +187,18 @@ FileSystemManager::FileSystemManager() fs::Folder& FileSystemManager::get_folder(ManifestLocation location) noexcept { return folders.at(location); } fs::Folder const& FileSystemManager::get_folder(ManifestLocation location) const noexcept { return folders.at(location); } -Folder* FileSystemManager::get_folder_for_given_path(std::filesystem::path const& given_path) noexcept { - auto normalized_path = given_path.lexically_normal(); +Folder* FileSystemManager::get_folder_for_given_path(std::string_view given_path) noexcept { for (auto const& [manifest_location, folder] : folders) { - if (folder.location() == normalized_path) { + if (folder.location() == given_path) { return &folders.at(manifest_location); } } - if (redirected_paths.count(given_path.string()) > 0) { - return &folders.at(redirected_paths.at(normalized_path.string())); - } - for (auto const& [redirected_path_str, redirected_location] : redirected_paths) { - std::filesystem::path redirected_path = redirected_path_str; + for (auto const& [redirected_path, redirected_location] : redirected_paths) { + if (redirected_path == given_path) { + return &folders.at(redirected_location); + } const auto mismatch_pair = - std::mismatch(normalized_path.begin(), normalized_path.end(), redirected_path.begin(), redirected_path.end()); + std::mismatch(given_path.begin(), given_path.end(), redirected_path.begin(), redirected_path.end()); if (mismatch_pair.second == redirected_path.end()) return &folders.at(redirected_location); } return nullptr; diff --git a/tests/framework/util/folder_manager.h b/tests/framework/util/folder_manager.h index 68e69c211..9d405cbb3 100644 --- a/tests/framework/util/folder_manager.h +++ b/tests/framework/util/folder_manager.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -87,7 +88,7 @@ class FileSystemManager { Folder const& get_folder(ManifestLocation location) const noexcept; // Gets a pointer to the folder that given_path points to. This includes redirected paths as well as the exact path of folders - Folder* get_folder_for_given_path(std::filesystem::path const& given_path) noexcept; + Folder* get_folder_for_given_path(std::string_view given_path) noexcept; bool is_folder_path(std::filesystem::path const& path) const noexcept; From f4b28d2855e587bb3c287a75c23d8cc251d9111f Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 12 Sep 2025 16:10:06 -0500 Subject: [PATCH 49/65] Initialize enabled_extensions in TestICD code Uninitialized memory triggers a runtime check that drastically slows down test execution. --- tests/framework/icd/test_icd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/framework/icd/test_icd.cpp b/tests/framework/icd/test_icd.cpp index 466293a55..1cc964c35 100644 --- a/tests/framework/icd/test_icd.cpp +++ b/tests/framework/icd/test_icd.cpp @@ -1723,7 +1723,7 @@ bool should_check(std::vector* exts, VkDevice device, const char* e } PFN_vkVoidFunction get_device_func(VkDevice device, const char* pName) { - std::vector* enabled_extensions; + std::vector* enabled_extensions = nullptr; FindDevice found_device{}; if (device != nullptr) { found_device = lookup_device(device); From 63ba87a4b5a21e24998b347c5db9938a83c05754 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:22:17 +0000 Subject: [PATCH 50/65] build(deps): bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 20 ++++++++++---------- .github/workflows/codeql.yml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c4ed3796..8b3aecd0d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,7 @@ jobs: os: [ ubuntu-22.04, ubuntu-24.04 ] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - name: Test CMake min @@ -111,7 +111,7 @@ jobs: config: [ Debug, Release ] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - uses: lukka/get-cmake@latest @@ -147,7 +147,7 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - uses: lukka/get-cmake@latest @@ -182,7 +182,7 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - name: Test CMake min @@ -282,7 +282,7 @@ jobs: static_build: [ APPLE_STATIC_LOADER=ON, APPLE_STATIC_LOADER=OFF ] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - run: | @@ -312,7 +312,7 @@ jobs: CMAKE_SYSTEM_NAME: [ iOS, tvOS ] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - run: | @@ -346,7 +346,7 @@ jobs: generator: [ Ninja, Xcode ] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - run: | @@ -388,7 +388,7 @@ jobs: shell: bash steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - name: Setup uasm @@ -416,7 +416,7 @@ jobs: shell: bash steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' - run: | @@ -439,7 +439,7 @@ jobs: shell: bash steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.11' # Make sure this doesn't fail even without explicitly setting '-D USE_MASM=OFF' and without uasm diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 749a2f502..0a420567e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -70,7 +70,7 @@ jobs: if: matrix.language == 'python' uses: github/codeql-action/autobuild@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.29.5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 if: matrix.language == 'cpp' with: python-version: '3.11' From 4ff7bc64c6881d8028251323d97a1078ebb60201 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:27:09 +0000 Subject: [PATCH 51/65] build(deps): bump github/codeql-action from 3.30.0 to 3.30.3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.0 to 3.30.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d...192325c86100d080feab897ff886c34abd4c83a3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0a420567e..fc9501c1a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.29.5 + uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.29.5 + uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 - uses: actions/setup-python@v6 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.29.5 + uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 with: category: "/language:${{matrix.language}}" From 50399a2c3011bea1941a977f5b90a241053af4bc Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 16 Sep 2025 13:10:34 -0500 Subject: [PATCH 52/65] Require a compatible VulkanHeaders version in find_package Adding this field uses the generated version in `project()` to force CMake to check that the found VulkanHeaders package's version is the same or greater than the version of VulkanHeaders this project was generated with. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b34889c5..9d9506a0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ if(WIN32) set(BUILD_DLL_VERSIONINFO "" CACHE STRING "Set the version to be used in the loader.rc file. Default value is the currently generated header version") endif() -find_package(VulkanHeaders CONFIG QUIET) +find_package(VulkanHeaders ${CMAKE_PROJECT_VERSION} CONFIG QUIET) include(GNUInstallDirs) From 6ec5a0d91c253c6374701e142a95a0d3215bf5a7 Mon Sep 17 00:00:00 2001 From: Mike Schuchardt Date: Fri, 19 Sep 2025 09:41:15 -0700 Subject: [PATCH 53/65] build: Update to header 1.4.327 --- CMakeLists.txt | 2 +- loader/loader.rc | 4 ++-- scripts/known_good.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d9506a0e..b844b22eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.326 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.327 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/loader.rc b/loader/loader.rc index 7f79b55cd..47daef3eb 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 326, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.326.Dev Build" +#define VER_FILE_VERSION 1, 4, 327, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.327.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index 023020974..38c086e97 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.326" + "commit": "v1.4.327" }, { "name": "googletest", From f703f919c30c5b67958d35d40a4297cb3823ed78 Mon Sep 17 00:00:00 2001 From: Mike Schuchardt Date: Fri, 26 Sep 2025 07:00:31 -0700 Subject: [PATCH 54/65] build: Update to header 1.4.328 --- CMakeLists.txt | 2 +- loader/generated/vk_layer_dispatch_table.h | 4 ++ loader/generated/vk_loader_extensions.c | 47 +++++++++++++++++++ loader/loader.rc | 4 +- scripts/known_good.json | 2 +- .../generated/vk_dispatch_table_helper.h | 2 + 6 files changed, 57 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b844b22eb..a42239cc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.327 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.328 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/generated/vk_layer_dispatch_table.h b/loader/generated/vk_layer_dispatch_table.h index b7c806ee4..c03cd02d9 100644 --- a/loader/generated/vk_layer_dispatch_table.h +++ b/loader/generated/vk_layer_dispatch_table.h @@ -746,6 +746,10 @@ typedef struct VkLayerDispatchTable_ { PFN_vkCmdSetDescriptorBufferOffsets2EXT CmdSetDescriptorBufferOffsets2EXT; PFN_vkCmdBindDescriptorBufferEmbeddedSamplers2EXT CmdBindDescriptorBufferEmbeddedSamplers2EXT; + // ---- VK_KHR_copy_memory_indirect extension commands + PFN_vkCmdCopyMemoryIndirectKHR CmdCopyMemoryIndirectKHR; + PFN_vkCmdCopyMemoryToImageIndirectKHR CmdCopyMemoryToImageIndirectKHR; + // ---- VK_EXT_debug_marker extension commands PFN_vkDebugMarkerSetObjectTagEXT DebugMarkerSetObjectTagEXT; PFN_vkDebugMarkerSetObjectNameEXT DebugMarkerSetObjectNameEXT; diff --git a/loader/generated/vk_loader_extensions.c b/loader/generated/vk_loader_extensions.c index 4e4b9ad76..6610c5056 100644 --- a/loader/generated/vk_loader_extensions.c +++ b/loader/generated/vk_loader_extensions.c @@ -784,6 +784,10 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_extension_dispatch_table(struct lo table->CmdSetDescriptorBufferOffsets2EXT = (PFN_vkCmdSetDescriptorBufferOffsets2EXT)gdpa(dev, "vkCmdSetDescriptorBufferOffsets2EXT"); table->CmdBindDescriptorBufferEmbeddedSamplers2EXT = (PFN_vkCmdBindDescriptorBufferEmbeddedSamplers2EXT)gdpa(dev, "vkCmdBindDescriptorBufferEmbeddedSamplers2EXT"); + // ---- VK_KHR_copy_memory_indirect extension commands + table->CmdCopyMemoryIndirectKHR = (PFN_vkCmdCopyMemoryIndirectKHR)gdpa(dev, "vkCmdCopyMemoryIndirectKHR"); + table->CmdCopyMemoryToImageIndirectKHR = (PFN_vkCmdCopyMemoryToImageIndirectKHR)gdpa(dev, "vkCmdCopyMemoryToImageIndirectKHR"); + // ---- VK_EXT_debug_marker extension commands table->DebugMarkerSetObjectTagEXT = (PFN_vkDebugMarkerSetObjectTagEXT)gdpa(dev, "vkDebugMarkerSetObjectTagEXT"); table->DebugMarkerSetObjectNameEXT = (PFN_vkDebugMarkerSetObjectNameEXT)gdpa(dev, "vkDebugMarkerSetObjectNameEXT"); @@ -2746,6 +2750,10 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (!strcmp(name, "CmdSetDescriptorBufferOffsets2EXT")) return (void *)table->CmdSetDescriptorBufferOffsets2EXT; if (!strcmp(name, "CmdBindDescriptorBufferEmbeddedSamplers2EXT")) return (void *)table->CmdBindDescriptorBufferEmbeddedSamplers2EXT; + // ---- VK_KHR_copy_memory_indirect extension commands + if (!strcmp(name, "CmdCopyMemoryIndirectKHR")) return (void *)table->CmdCopyMemoryIndirectKHR; + if (!strcmp(name, "CmdCopyMemoryToImageIndirectKHR")) return (void *)table->CmdCopyMemoryToImageIndirectKHR; + // ---- VK_EXT_debug_marker extension commands if (!strcmp(name, "DebugMarkerSetObjectTagEXT")) return dev->layer_extensions.ext_debug_marker_enabled ? (void *)DebugMarkerSetObjectTagEXT : NULL; if (!strcmp(name, "DebugMarkerSetObjectNameEXT")) return dev->layer_extensions.ext_debug_marker_enabled ? (void *)DebugMarkerSetObjectNameEXT : NULL; @@ -5493,6 +5501,35 @@ VKAPI_ATTR void VKAPI_CALL CmdBindDescriptorBufferEmbeddedSamplers2EXT( } +// ---- VK_KHR_copy_memory_indirect extension trampoline/terminators + +VKAPI_ATTR void VKAPI_CALL CmdCopyMemoryIndirectKHR( + VkCommandBuffer commandBuffer, + const VkCopyMemoryIndirectInfoKHR* pCopyMemoryIndirectInfo) { + const VkLayerDispatchTable *disp = loader_get_dispatch(commandBuffer); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkCmdCopyMemoryIndirectKHR: Invalid commandBuffer " + "[VUID-vkCmdCopyMemoryIndirectKHR-commandBuffer-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + disp->CmdCopyMemoryIndirectKHR(commandBuffer, pCopyMemoryIndirectInfo); +} + +VKAPI_ATTR void VKAPI_CALL CmdCopyMemoryToImageIndirectKHR( + VkCommandBuffer commandBuffer, + const VkCopyMemoryToImageIndirectInfoKHR* pCopyMemoryToImageIndirectInfo) { + const VkLayerDispatchTable *disp = loader_get_dispatch(commandBuffer); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkCmdCopyMemoryToImageIndirectKHR: Invalid commandBuffer " + "[VUID-vkCmdCopyMemoryToImageIndirectKHR-commandBuffer-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + disp->CmdCopyMemoryToImageIndirectKHR(commandBuffer, pCopyMemoryToImageIndirectInfo); +} + + // ---- VK_EXT_debug_marker extension trampoline/terminators VKAPI_ATTR VkResult VKAPI_CALL DebugMarkerSetObjectTagEXT( @@ -11818,6 +11855,16 @@ bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *na return true; } + // ---- VK_KHR_copy_memory_indirect extension commands + if (!strcmp("vkCmdCopyMemoryIndirectKHR", name)) { + *addr = (void *)CmdCopyMemoryIndirectKHR; + return true; + } + if (!strcmp("vkCmdCopyMemoryToImageIndirectKHR", name)) { + *addr = (void *)CmdCopyMemoryToImageIndirectKHR; + return true; + } + // ---- VK_EXT_debug_marker extension commands if (!strcmp("vkDebugMarkerSetObjectTagEXT", name)) { *addr = (void *)DebugMarkerSetObjectTagEXT; diff --git a/loader/loader.rc b/loader/loader.rc index 47daef3eb..3fc9a45bc 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 327, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.327.Dev Build" +#define VER_FILE_VERSION 1, 4, 328, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.328.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index 38c086e97..36e806745 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.327" + "commit": "v1.4.328" }, { "name": "googletest", diff --git a/tests/framework/layer/generated/vk_dispatch_table_helper.h b/tests/framework/layer/generated/vk_dispatch_table_helper.h index 4b54ba2f7..a07d8ece7 100644 --- a/tests/framework/layer/generated/vk_dispatch_table_helper.h +++ b/tests/framework/layer/generated/vk_dispatch_table_helper.h @@ -370,6 +370,8 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->CmdPushDescriptorSetWithTemplate2KHR = (PFN_vkCmdPushDescriptorSetWithTemplate2KHR)gpa(device, "vkCmdPushDescriptorSetWithTemplate2KHR"); table->CmdSetDescriptorBufferOffsets2EXT = (PFN_vkCmdSetDescriptorBufferOffsets2EXT)gpa(device, "vkCmdSetDescriptorBufferOffsets2EXT"); table->CmdBindDescriptorBufferEmbeddedSamplers2EXT = (PFN_vkCmdBindDescriptorBufferEmbeddedSamplers2EXT)gpa(device, "vkCmdBindDescriptorBufferEmbeddedSamplers2EXT"); + table->CmdCopyMemoryIndirectKHR = (PFN_vkCmdCopyMemoryIndirectKHR)gpa(device, "vkCmdCopyMemoryIndirectKHR"); + table->CmdCopyMemoryToImageIndirectKHR = (PFN_vkCmdCopyMemoryToImageIndirectKHR)gpa(device, "vkCmdCopyMemoryToImageIndirectKHR"); table->DebugMarkerSetObjectTagEXT = (PFN_vkDebugMarkerSetObjectTagEXT)gpa(device, "vkDebugMarkerSetObjectTagEXT"); table->DebugMarkerSetObjectNameEXT = (PFN_vkDebugMarkerSetObjectNameEXT)gpa(device, "vkDebugMarkerSetObjectNameEXT"); table->CmdDebugMarkerBeginEXT = (PFN_vkCmdDebugMarkerBeginEXT)gpa(device, "vkCmdDebugMarkerBeginEXT"); From 0b058dd7537945bdf09d28fd9188ed7639b90f2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 01:39:53 +0000 Subject: [PATCH 55/65] build(deps): bump github/codeql-action from 3.30.3 to 3.30.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.3 to 3.30.5. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/192325c86100d080feab897ff886c34abd4c83a3...3599b3baa15b485a2e49ef411a7a4bb2452e7f93) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fc9501c1a..59e541ba8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/autobuild@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 - uses: actions/setup-python@v6 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 with: category: "/language:${{matrix.language}}" From b4e1325c3c90ed8f275aa53ed47cb2c1ed04822d Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 26 Sep 2025 13:35:08 -0500 Subject: [PATCH 56/65] Add utility for getting the string representation of a UUID Allows for the loader_log call to not have 16+ parameters. --- loader/log.c | 7 +++++++ loader/log.h | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/loader/log.c b/loader/log.c index 41b89ee65..78589a47c 100644 --- a/loader/log.c +++ b/loader/log.c @@ -274,3 +274,10 @@ void loader_log_asm_function_not_supported(const struct loader_instance *inst, V const char *func_name) { loader_log(inst, msg_type, msg_code, "Function %s not supported for this physical device", func_name); } + +void loader_log_generate_uuid_string(const uint8_t uuid[16], char output[UUID_STR_LEN]) { + assert(uuid && output); + snprintf(output, UUID_STR_LEN, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", uuid[0], uuid[1], + uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], + uuid[14], uuid[15]); +} diff --git a/loader/log.h b/loader/log.h index 1b594ab77..6075e1a0a 100644 --- a/loader/log.h +++ b/loader/log.h @@ -83,3 +83,7 @@ void loader_log_asm_function_not_supported(const struct loader_instance *inst, V const char *func_name) ASM_NAME("loader_log_asm_function_not_supported"); #undef ASM_NAME + +#define UUID_STR_LEN 37 // always 32 digits, 4 dashes, and 1 null terminator + +void loader_log_generate_uuid_string(const uint8_t uuid[16], char output[UUID_STR_LEN]); From 74047e496d6b2767fc1c84d0c07db71ba03b25ba Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 26 Sep 2025 14:23:23 -0500 Subject: [PATCH 57/65] Add driverUUID and driverVersion to settings file device configuration parameters deviceUUID is not sufficient to be able to distinguish VkPhysicalDevices as there can be multiple ICD's that report the same VkPhysicalDevice. This can easily occur when a development build of a driver is present alongside a stable build on a system. driverUUID then helps differentiate ICD's reporting the same VkPhysicalDevice, but isn't sufficient as some drivers use the same driverUUID across all builds (RADV from Mesa as example). Thus, driverVersion is used for that purpose. --- loader/loader.c | 161 +++++++++++++++++++-------- loader/settings.c | 84 ++++++++++---- loader/settings.h | 5 +- tests/framework/icd/test_icd.cpp | 6 + tests/framework/icd/test_icd.h | 1 + tests/framework/test_environment.cpp | 10 ++ tests/framework/test_environment.h | 3 + tests/loader_settings_tests.cpp | 51 +++++++++ 8 files changed, 248 insertions(+), 73 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 821872174..3e5c2d523 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -7142,6 +7142,46 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_EnumeratePhysicalDevices(VkInstance in return res; } +VkResult check_physical_device_extensions_for_driver_properties_extension(struct loader_physical_device_term *phys_dev_term, + bool *supports_driver_properties) { + *supports_driver_properties = false; + uint32_t extension_count = 0; + VkResult res = phys_dev_term->this_icd_term->dispatch.EnumerateDeviceExtensionProperties(phys_dev_term->phys_dev, NULL, + &extension_count, NULL); + if (res != VK_SUCCESS || extension_count == 0) { + return VK_SUCCESS; + } + VkExtensionProperties *extension_data = loader_stack_alloc(sizeof(VkExtensionProperties) * extension_count); + if (NULL == extension_data) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + res = phys_dev_term->this_icd_term->dispatch.EnumerateDeviceExtensionProperties(phys_dev_term->phys_dev, NULL, &extension_count, + extension_data); + if (res != VK_SUCCESS) { + return VK_SUCCESS; + } + for (uint32_t j = 0; j < extension_count; j++) { + if (!strcmp(extension_data[j].extensionName, VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) { + *supports_driver_properties = true; + return VK_SUCCESS; + } + } + return VK_SUCCESS; +} + +// Helper struct containing the relevant details of a VkPhysicalDevice necessary for applying the loader settings device +// configurations. +typedef struct physical_device_configuration_details { + bool pd_was_added; + bool pd_supports_vulkan_11; + bool pd_supports_driver_properties; + VkPhysicalDeviceProperties properties; + VkPhysicalDeviceIDProperties device_id_properties; + VkPhysicalDeviceDriverProperties driver_properties; + +} physical_device_configuration_details; + // Apply the device_configurations in the settings file to the output VkPhysicalDeviceList. // That means looking up each VkPhysicalDevice's deviceUUID, filtering using that, and putting them in the order of // device_configurations in the settings file. @@ -7150,45 +7190,46 @@ VkResult loader_apply_settings_device_configurations(struct loader_instance *ins loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, "Reordering the output of vkEnumeratePhysicalDevices to match the loader settings device configurations list"); - bool *pd_was_added = loader_stack_alloc(inst->phys_dev_count_term * sizeof(bool)); - if (NULL == pd_was_added) { + physical_device_configuration_details *pd_details = + loader_stack_alloc(inst->phys_dev_count_term * sizeof(physical_device_configuration_details)); + if (NULL == pd_details) { return VK_ERROR_OUT_OF_HOST_MEMORY; } - memset(pd_was_added, 0, inst->phys_dev_count_term * sizeof(bool)); + memset(pd_details, 0, inst->phys_dev_count_term * sizeof(physical_device_configuration_details)); - bool *pd_supports_11 = loader_stack_alloc(inst->phys_dev_count_term * sizeof(bool)); - if (NULL == pd_supports_11) { - return VK_ERROR_OUT_OF_HOST_MEMORY; - } - memset(pd_supports_11, 0, inst->phys_dev_count_term * sizeof(bool)); + for (uint32_t i = 0; i < inst->phys_dev_count_term; i++) { + struct loader_physical_device_term *phys_dev_term = inst->phys_devs_term[i]; - VkPhysicalDeviceProperties *pd_props = loader_stack_alloc(inst->phys_dev_count_term * sizeof(VkPhysicalDeviceProperties)); - if (NULL == pd_props) { - return VK_ERROR_OUT_OF_HOST_MEMORY; - } - memset(pd_props, 0, inst->phys_dev_count_term * sizeof(VkPhysicalDeviceProperties)); + phys_dev_term->this_icd_term->dispatch.GetPhysicalDeviceProperties(phys_dev_term->phys_dev, &pd_details[i].properties); + if (pd_details[i].properties.apiVersion < VK_API_VERSION_1_1) { + // Device isn't eligible for sorting + continue; + } + pd_details[i].pd_supports_vulkan_11 = true; + if (pd_details[i].properties.apiVersion >= VK_API_VERSION_1_2) { + pd_details[i].pd_supports_driver_properties = true; + } - VkPhysicalDeviceVulkan11Properties *pd_vulkan_11_props = - loader_stack_alloc(inst->phys_dev_count_term * sizeof(VkPhysicalDeviceVulkan11Properties)); - if (NULL == pd_vulkan_11_props) { - return VK_ERROR_OUT_OF_HOST_MEMORY; - } - memset(pd_vulkan_11_props, 0, inst->phys_dev_count_term * sizeof(VkPhysicalDeviceVulkan11Properties)); + // If this physical device isn't 1.2, then we need to check if it supports VK_KHR_driver_properties + if (!pd_details[i].pd_supports_driver_properties) { + VkResult res = check_physical_device_extensions_for_driver_properties_extension( + phys_dev_term, &pd_details[i].pd_supports_driver_properties); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + return res; + } + } - for (uint32_t i = 0; i < inst->phys_dev_count_term; i++) { - pd_vulkan_11_props[i].sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; + pd_details[i].device_id_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES; + pd_details[i].driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; + if (pd_details[i].pd_supports_driver_properties) { + pd_details[i].device_id_properties.pNext = (void *)&pd_details[i].driver_properties; + } - inst->phys_devs_term[i]->this_icd_term->dispatch.GetPhysicalDeviceProperties(inst->phys_devs_term[i]->phys_dev, - &pd_props[i]); - if (pd_props[i].apiVersion >= VK_API_VERSION_1_1) { - pd_supports_11[i] = true; - VkPhysicalDeviceProperties2 props2 = {0}; - props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; - props2.pNext = (void *)&pd_vulkan_11_props[i]; - if (inst->phys_devs_term[i]->this_icd_term->dispatch.GetPhysicalDeviceProperties2) { - inst->phys_devs_term[i]->this_icd_term->dispatch.GetPhysicalDeviceProperties2(inst->phys_devs_term[i]->phys_dev, - &props2); - } + VkPhysicalDeviceProperties2 props2 = {0}; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = (void *)&pd_details[i].device_id_properties; + if (phys_dev_term->this_icd_term->dispatch.GetPhysicalDeviceProperties2) { + phys_dev_term->this_icd_term->dispatch.GetPhysicalDeviceProperties2(phys_dev_term->phys_dev, &props2); } } @@ -7198,55 +7239,79 @@ VkResult loader_apply_settings_device_configurations(struct loader_instance *ins for (uint32_t i = 0; i < inst->settings.device_configuration_count; i++) { uint8_t *current_deviceUUID = inst->settings.device_configurations[i].deviceUUID; + uint8_t *current_driverUUID = inst->settings.device_configurations[i].driverUUID; bool configuration_found = false; for (uint32_t j = 0; j < inst->phys_dev_count_term; j++) { // Don't compare deviceUUID's if they have nothing, since we require deviceUUID's to effectively sort them. - if (!pd_supports_11[j]) { + if (!pd_details[j].pd_supports_vulkan_11) { continue; } - if (memcmp(current_deviceUUID, pd_vulkan_11_props[j].deviceUUID, sizeof(uint8_t) * VK_UUID_SIZE) == 0) { + if (memcmp(current_deviceUUID, pd_details[j].device_id_properties.deviceUUID, sizeof(uint8_t) * VK_UUID_SIZE) == 0 && + memcmp(current_driverUUID, pd_details[j].device_id_properties.driverUUID, sizeof(uint8_t) * VK_UUID_SIZE) == 0 && + inst->settings.device_configurations[i].driverVersion == pd_details[j].properties.driverVersion) { configuration_found = true; // Catch when there are more device_configurations than space available in the output if (written_output_index >= *pPhysicalDeviceCount) { *pPhysicalDeviceCount = written_output_index; // write out how many were written return VK_INCOMPLETE; } - loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "pPhysicalDevices array index %d is set to \"%s\" ", - written_output_index, pd_props[j].deviceName); + if (pd_details[j].pd_supports_driver_properties) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "pPhysicalDevices array index %d is set to \"%s\" (%s, version %d) ", written_output_index, + pd_details[j].properties.deviceName, pd_details[i].driver_properties.driverName, + pd_details[i].properties.driverVersion); + } else { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "pPhysicalDevices array index %d is set to \"%s\" (driver version %d) ", written_output_index, + pd_details[j].properties.deviceName, pd_details[i].properties.driverVersion); + } pPhysicalDevices[written_output_index++] = (VkPhysicalDevice)inst->phys_devs_term[j]; - pd_was_added[j] = true; + pd_details[j].pd_was_added = true; break; } } if (!configuration_found) { - uint8_t *id = current_deviceUUID; + char device_uuid_str[UUID_STR_LEN] = {0}; + loader_log_generate_uuid_string(current_deviceUUID, device_uuid_str); + char driver_uuid_str[UUID_STR_LEN] = {0}; + loader_log_generate_uuid_string(current_deviceUUID, driver_uuid_str); + // Log that this configuration was missing. - if (inst->settings.device_configurations[i].deviceName[0] != '\0') { + if (inst->settings.device_configurations[i].deviceName[0] != '\0' && + inst->settings.device_configurations[i].driverName[0] != '\0') { + loader_log( + inst, VULKAN_LOADER_WARN_BIT, 0, + "loader_apply_settings_device_configurations: settings file contained device_configuration which does not " + "appear in the enumerated VkPhysicalDevices. Missing VkPhysicalDevice with deviceName: \"%s\", " + "deviceUUID: %s, driverName: %s, driverUUID: %s, driverVersion: %d", + inst->settings.device_configurations[i].deviceName, device_uuid_str, + inst->settings.device_configurations[i].driverName, driver_uuid_str, + inst->settings.device_configurations[i].driverVersion); + } else if (inst->settings.device_configurations[i].deviceName[0] != '\0') { loader_log( inst, VULKAN_LOADER_WARN_BIT, 0, "loader_apply_settings_device_configurations: settings file contained device_configuration which does not " - "appear in the enumerated VkPhysicalDevices. Missing VkPhysicalDevice with deviceName: \"%s\" and deviceUUID: " - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - inst->settings.device_configurations[i].deviceName, id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], - id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); + "appear in the enumerated VkPhysicalDevices. Missing VkPhysicalDevice with deviceName: \"%s\", " + "deviceUUID: %s, driverUUID: %s, driverVersion: %d", + inst->settings.device_configurations[i].deviceName, device_uuid_str, driver_uuid_str, + inst->settings.device_configurations[i].driverVersion); } else { loader_log( inst, VULKAN_LOADER_WARN_BIT, 0, "loader_apply_settings_device_configurations: settings file contained device_configuration which does not " "appear in the enumerated VkPhysicalDevices. Missing VkPhysicalDevice with deviceUUID: " - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], - id[15]); + "%s, driverUUID: %s, driverVersion: %d", + device_uuid_str, driver_uuid_str, inst->settings.device_configurations[i].driverVersion); } } } for (uint32_t j = 0; j < inst->phys_dev_count_term; j++) { - if (!pd_was_added[j]) { + if (!pd_details[j].pd_was_added) { loader_log(inst, VULKAN_LOADER_INFO_BIT, 0, "VkPhysicalDevice \"%s\" did not appear in the settings file device configurations list, so was not added " "to the pPhysicalDevices array", - pd_props[j].deviceName); + pd_details[j].properties.deviceName); } } diff --git a/loader/settings.c b/loader/settings.c index 365cfb9d0..8ca81728b 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -297,39 +297,58 @@ VkResult parse_additional_drivers(const struct loader_instance* inst, cJSON* set return res; } -VkResult parse_device_configuration(const struct loader_instance* inst, cJSON* device_configuration_json, - loader_settings_device_configuration* device_configuration) { - (void)inst; - VkResult res = VK_SUCCESS; - cJSON* deviceUUID_array = loader_cJSON_GetObjectItem(device_configuration_json, "deviceUUID"); - if (NULL == deviceUUID_array) { - res = VK_ERROR_INITIALIZATION_FAILED; - goto out; +VkResult parse_uuid_array(cJSON* device_configuration_json, const char* uuid_name, uint8_t uuid[16]) { + cJSON* uuid_array = loader_cJSON_GetObjectItem(device_configuration_json, uuid_name); + if (NULL == uuid_array) { + return VK_ERROR_INITIALIZATION_FAILED; } - if (deviceUUID_array->type != cJSON_Array) { - res = VK_ERROR_INITIALIZATION_FAILED; - goto out; + if (uuid_array->type != cJSON_Array) { + return VK_ERROR_INITIALIZATION_FAILED; } - if (VK_UUID_SIZE != loader_cJSON_GetArraySize(deviceUUID_array)) { - res = VK_ERROR_INITIALIZATION_FAILED; - goto out; + if (VK_UUID_SIZE != loader_cJSON_GetArraySize(uuid_array)) { + return VK_ERROR_INITIALIZATION_FAILED; } cJSON* uuid_field = NULL; size_t i = 0; - cJSON_ArrayForEach(uuid_field, deviceUUID_array) { + cJSON_ArrayForEach(uuid_field, uuid_array) { + if (i >= VK_UUID_SIZE) { + break; + } if (uuid_field->type != cJSON_Number) { - res = VK_ERROR_INITIALIZATION_FAILED; - goto out; + return VK_ERROR_INITIALIZATION_FAILED; } if (uuid_field->valueint < 0 || uuid_field->valueint > 255) { - res = VK_ERROR_INITIALIZATION_FAILED; - goto out; + return VK_ERROR_INITIALIZATION_FAILED; } - device_configuration->deviceUUID[i] = (uint8_t)uuid_field->valueint; + + uuid[i] = (uint8_t)uuid_field->valueint; i++; } + return VK_SUCCESS; +} + +VkResult parse_device_configuration(const struct loader_instance* inst, cJSON* device_configuration_json, + loader_settings_device_configuration* device_configuration) { + (void)inst; + VkResult res = VK_SUCCESS; + + res = parse_uuid_array(device_configuration_json, "deviceUUID", device_configuration->deviceUUID); + if (VK_SUCCESS != res) { + goto out; + } + + res = parse_uuid_array(device_configuration_json, "driverUUID", device_configuration->driverUUID); + if (VK_SUCCESS != res) { + goto out; + } + + cJSON* driverVersion_json = loader_cJSON_GetObjectItem(device_configuration_json, "driverVersion"); + if (NULL == driverVersion_json || driverVersion_json->type != cJSON_Number) { + return VK_ERROR_INITIALIZATION_FAILED; + } + device_configuration->driverVersion = driverVersion_json->valueint; VkResult deviceNameRes = loader_parse_json_string_to_existing_str( device_configuration_json, "deviceName", VK_MAX_PHYSICAL_DEVICE_NAME_SIZE, device_configuration->deviceName); @@ -337,6 +356,13 @@ VkResult parse_device_configuration(const struct loader_instance* inst, cJSON* d res = deviceNameRes; goto out; } + + VkResult driverNameRes = loader_parse_json_string_to_existing_str( + device_configuration_json, "driverName", VK_MAX_PHYSICAL_DEVICE_NAME_SIZE, device_configuration->driverName); + if (VK_ERROR_OUT_OF_HOST_MEMORY == driverNameRes) { + res = driverNameRes; + goto out; + } out: if (res != VK_SUCCESS) { memset(device_configuration, 0, sizeof(loader_settings_device_configuration)); @@ -542,6 +568,10 @@ bool check_if_device_configurations_are_equal(loader_settings_device_configurati for (uint32_t i = 0; i < VK_UUID_SIZE; i++) { if (a->deviceUUID[i] != b->deviceUUID[i]) return false; } + for (uint32_t i = 0; i < VK_UUID_SIZE; i++) { + if (a->driverUUID[i] != b->driverUUID[i]) return false; + } + if (a->driverVersion != b->driverVersion) return false; return true; } @@ -613,13 +643,19 @@ void log_settings(const struct loader_instance* inst, loader_settings* settings) loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "Device Configurations count = %d", settings->device_configuration_count); for (uint32_t i = 0; i < settings->device_configuration_count; i++) { loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "---- Device Configuration [%d] ----", i); - uint8_t* id = settings->device_configurations[i].deviceUUID; - loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, - "deviceUUID: %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", id[0], id[1], id[2], - id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); + char device_uuid_str[UUID_STR_LEN] = {0}; + loader_log_generate_uuid_string(settings->device_configurations[i].deviceUUID, device_uuid_str); + char driver_uuid_str[UUID_STR_LEN] = {0}; + loader_log_generate_uuid_string(settings->device_configurations[i].driverUUID, driver_uuid_str); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "deviceUUID: %s", device_uuid_str); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "driverUUID: %s", driver_uuid_str); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "driverVersion: %d", settings->device_configurations[i].driverVersion); if ('\0' != settings->device_configurations[i].deviceName[0]) { loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "deviceName: %s", settings->device_configurations[i].deviceName); } + if ('\0' != settings->device_configurations[i].driverName[0]) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "driverName: %s", settings->device_configurations[i].driverName); + } } } loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "---------------------------------"); diff --git a/loader/settings.h b/loader/settings.h index 9ed5c80bf..df3448619 100644 --- a/loader/settings.h +++ b/loader/settings.h @@ -67,8 +67,11 @@ typedef struct loader_settings_driver_configuration { } loader_settings_driver_configuration; typedef struct loader_settings_device_configuration { - char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE]; uint8_t deviceUUID[VK_UUID_SIZE]; + uint8_t driverUUID[VK_UUID_SIZE]; + uint32_t driverVersion; + char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE]; + char driverName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE]; } loader_settings_device_configuration; typedef struct loader_settings { diff --git a/tests/framework/icd/test_icd.cpp b/tests/framework/icd/test_icd.cpp index 1cc964c35..56e8b0b2c 100644 --- a/tests/framework/icd/test_icd.cpp +++ b/tests/framework/icd/test_icd.cpp @@ -1231,6 +1231,12 @@ VKAPI_ATTR void VKAPI_CALL test_vkGetPhysicalDeviceProperties2(VkPhysicalDevice if (pNext->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES) { auto* vulkan_11_props = reinterpret_cast(pNext); memcpy(vulkan_11_props->deviceUUID, phys_dev.deviceUUID.data(), VK_UUID_SIZE); + memcpy(vulkan_11_props->driverUUID, phys_dev.driverUUID.data(), VK_UUID_SIZE); + } + if (pNext->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES) { + auto* device_id_props = reinterpret_cast(pNext); + memcpy(device_id_props->deviceUUID, phys_dev.deviceUUID.data(), VK_UUID_SIZE); + memcpy(device_id_props->driverUUID, phys_dev.driverUUID.data(), VK_UUID_SIZE); } if (pNext->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES) { auto* device_driver_props = reinterpret_cast(pNext); diff --git a/tests/framework/icd/test_icd.h b/tests/framework/icd/test_icd.h index 24309f410..4e4fcec0b 100644 --- a/tests/framework/icd/test_icd.h +++ b/tests/framework/icd/test_icd.h @@ -91,6 +91,7 @@ struct PhysicalDevice { DispatchableHandle vk_physical_device; BUILDER_VALUE(std::string, deviceName) BUILDER_VALUE(VulkanUUID, deviceUUID) + BUILDER_VALUE(VulkanUUID, driverUUID) BUILDER_VALUE(VkPhysicalDeviceProperties, properties) BUILDER_VALUE(VkPhysicalDeviceFeatures, features) BUILDER_VALUE(VkPhysicalDeviceMemoryProperties, memory_properties) diff --git a/tests/framework/test_environment.cpp b/tests/framework/test_environment.cpp index 261d21149..cf983d234 100644 --- a/tests/framework/test_environment.cpp +++ b/tests/framework/test_environment.cpp @@ -699,11 +699,21 @@ std::string get_loader_settings_file_contents(const LoaderSettings& loader_setti if (!device.deviceName.empty()) { writer.AddKeyedString("deviceName", device.deviceName); } + if (!device.driverName.empty()) { + writer.AddKeyedString("driverName", device.driverName); + } writer.StartKeyedArray("deviceUUID"); for (const auto& u : device.deviceUUID) { writer.AddInteger(u); } writer.EndArray(); + + writer.StartKeyedArray("driverUUID"); + for (const auto& u : device.driverUUID) { + writer.AddInteger(u); + } + writer.EndArray(); + writer.AddKeyedInteger("driverVersion", device.driverVersion); writer.EndObject(); } writer.EndArray(); diff --git a/tests/framework/test_environment.h b/tests/framework/test_environment.h index 678e88e99..59444b37e 100644 --- a/tests/framework/test_environment.h +++ b/tests/framework/test_environment.h @@ -365,7 +365,10 @@ using VulkanUUID = std::array; struct LoaderSettingsDeviceConfiguration { BUILDER_VALUE(VulkanUUID, deviceUUID) + BUILDER_VALUE(VulkanUUID, driverUUID) + BUILDER_VALUE(uint32_t, driverVersion) BUILDER_VALUE(std::string, deviceName) + BUILDER_VALUE(std::string, driverName) }; // Log files and their associated filter diff --git a/tests/loader_settings_tests.cpp b/tests/loader_settings_tests.cpp index a276bcce5..225af0e31 100644 --- a/tests/loader_settings_tests.cpp +++ b/tests/loader_settings_tests.cpp @@ -3354,6 +3354,57 @@ TEST(SettingsFile, MissingDriverConfiguration) { inst.GetPhysDev(VK_ERROR_INITIALIZATION_FAILED); } +TEST(SettingsFile, DeviceConfigurationWithSameDriver) { + FrameworkEnvironment env{}; + VulkanUUID device_uuid = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + VulkanUUID driver_uuid = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}; + + auto& icd0 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_icd_api_version(VK_API_VERSION_1_1); + auto& phys_dev_0 = icd0.add_and_get_physical_device(PhysicalDevice() + .set_api_version(VK_API_VERSION_1_2) + .set_deviceUUID(device_uuid) + .set_driverUUID(driver_uuid) + .set_deviceName("foobar") + .finish()); + phys_dev_0.properties.driverVersion = 1000; + std::string("Fake Driver XYZ").copy(phys_dev_0.driver_properties.driverName, VK_MAX_EXTENSION_NAME_SIZE); + + auto& icd1 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).set_icd_api_version(VK_API_VERSION_1_1); + auto& phys_dev_1 = icd1.add_and_get_physical_device(PhysicalDevice() + .set_api_version(VK_API_VERSION_1_2) + .set_deviceUUID(device_uuid) + .set_driverUUID(driver_uuid) + .set_deviceName("foobar") + .finish()); + phys_dev_1.properties.driverVersion = 30; + std::string("Fake Driver XYZ, but differently named").copy(phys_dev_1.driver_properties.driverName, VK_MAX_EXTENSION_NAME_SIZE); + + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting(AppSpecificSettings{}); + + env.loader_settings.app_specific_settings.at(0).device_configurations.clear(); + env.loader_settings.app_specific_settings.at(0).add_device_configuration( + LoaderSettingsDeviceConfiguration{} + .set_deviceUUID(device_uuid) + .set_driverUUID(driver_uuid) + .set_driverVersion(phys_dev_1.properties.driverVersion)); + env.loader_settings.app_specific_settings.at(0).add_device_configuration( + LoaderSettingsDeviceConfiguration{} + .set_deviceUUID(device_uuid) + .set_driverUUID(driver_uuid) + .set_driverVersion(phys_dev_0.properties.driverVersion)); + env.update_loader_settings(env.loader_settings); + + InstWrapper inst{env.vulkan_functions}; + inst.CheckCreate(); + auto phys_devs = inst.GetPhysDevs(2); + VkPhysicalDeviceProperties props1{}; + VkPhysicalDeviceProperties props2{}; + inst->vkGetPhysicalDeviceProperties(phys_devs.at(0), &props1); + inst->vkGetPhysicalDeviceProperties(phys_devs.at(1), &props2); + ASSERT_EQ(props1.driverVersion, phys_dev_1.properties.driverVersion); + ASSERT_EQ(props2.driverVersion, phys_dev_0.properties.driverVersion); +} + // Three drivers, second on has the matching UUID in the settings file. TEST(SettingsFile, DriverConfigurationIgnoresDriverEnvVars) { FrameworkEnvironment env{}; From 0a278cc725089cb67bf6027076e5d72f97c04d86 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 30 Sep 2025 17:04:23 -0500 Subject: [PATCH 58/65] Check return value of copy_surface_create_info Not propagating the error code meant that unknown structs in the pNext chains of surface create info structs caused crashes later that should have been prevented. This also allows Out Of Memory conditions to be propagated correctly --- loader/wsi.c | 91 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/loader/wsi.c b/loader/wsi.c index af821361b..5f6b1cdc2 100644 --- a/loader/wsi.c +++ b/loader/wsi.c @@ -841,8 +841,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateWin32SurfaceKHR(VkInstance insta const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, sizeof(VkWin32SurfaceCreateInfoKHR)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkWin32SurfaceCreateInfoKHR", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkWin32SurfaceCreateInfoKHR", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -940,8 +943,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateWaylandSurfaceKHR(VkInstance ins const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, sizeof(VkWaylandSurfaceCreateInfoKHR)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkWaylandSurfaceCreateInfoKHR", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkWaylandSurfaceCreateInfoKHR", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1043,8 +1049,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateXcbSurfaceKHR(VkInstance instanc const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, sizeof(VkXcbSurfaceCreateInfoKHR)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkXcbSurfaceCreateInfoKHR", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkXcbSurfaceCreateInfoKHR", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1149,8 +1158,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateXlibSurfaceKHR(VkInstance instan const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, sizeof(VkXlibSurfaceCreateInfoKHR)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkXlibSurfaceCreateInfoKHR", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkXlibSurfaceCreateInfoKHR", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1254,8 +1266,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateDirectFBSurfaceEXT(VkInstance in const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_DIRECTFB_SURFACE_CREATE_INFO_EXT, sizeof(VkDirectFBSurfaceCreateInfoEXT)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkDirectFBSurfaceCreateInfoEXT", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkDirectFBSurfaceCreateInfoEXT", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1402,8 +1417,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateHeadlessSurfaceEXT(VkInstance in const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT, sizeof(VkHeadlessSurfaceCreateInfoEXT)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkHeadlessSurfaceCreateInfoEXT", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkHeadlessSurfaceCreateInfoEXT", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1488,8 +1506,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateMacOSSurfaceMVK(VkInstance insta const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, sizeof(VkMacOSSurfaceCreateInfoMVK)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkMacOSSurfaceCreateInfoMVK", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkMacOSSurfaceCreateInfoMVK", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1599,8 +1620,11 @@ terminator_CreateStreamDescriptorSurfaceGGP(VkInstance instance, const VkStreamD const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_STREAM_DESCRIPTOR_SURFACE_CREATE_INFO_GGP, sizeof(VkStreamDescriptorSurfaceCreateInfoGGP)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkStreamDescriptorSurfaceCreateInfoGGP", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkStreamDescriptorSurfaceCreateInfoGGP", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1654,8 +1678,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateMetalSurfaceEXT(VkInstance insta const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, sizeof(VkMetalSurfaceCreateInfoEXT)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkMetalSurfaceCreateInfoEXT", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkMetalSurfaceCreateInfoEXT", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1715,8 +1742,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateScreenSurfaceQNX(VkInstance inst const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_SCREEN_SURFACE_CREATE_INFO_QNX, sizeof(VkScreenSurfaceCreateInfoQNX)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkScreenSurfaceCreateInfoQNX", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkScreenSurfaceCreateInfoQNX", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -1815,8 +1845,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateViSurfaceNN(VkInstance instance, const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_VI_SURFACE_CREATE_INFO_NN, sizeof(VkViSurfaceCreateInfoNN)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkViSurfaceCreateInfoNN", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkViSurfaceCreateInfoNN", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -2129,8 +2162,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateDisplayPlaneSurfaceKHR(VkInstanc {VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR, sizeof(VkDisplaySurfaceCreateInfoKHR)}, {VK_STRUCTURE_TYPE_DISPLAY_SURFACE_STEREO_CREATE_INFO_NV, sizeof(VkDisplaySurfaceStereoCreateInfoNV)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkDisplaySurfaceCreateInfoKHR", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkDisplaySurfaceCreateInfoKHR", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; @@ -2558,8 +2594,11 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateImagePipeSurfaceFUCHSIA(VkInstan const struct loader_struct_type_info ci_types[] = { {VK_STRUCTURE_TYPE_IMAGEPIPE_SURFACE_CREATE_INFO_FUCHSIA, sizeof(VkImagePipeSurfaceCreateInfoFUCHSIA)}, }; - copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, - "VkImagePipeSurfaceCreateInfoFUCHSIA", pAllocator); + result = copy_surface_create_info(loader_inst, icd_surface, pCreateInfo, sizeof(ci_types) / sizeof(ci_types[0]), ci_types, + "VkImagePipeSurfaceCreateInfoFUCHSIA", pAllocator); + if (VK_SUCCESS != result) { + goto out; + } *pSurface = (VkSurfaceKHR)(uintptr_t)icd_surface; From ae0461b671558197a9a50e5fcfcc3b2d3f406b42 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Thu, 2 Oct 2025 12:11:49 -0500 Subject: [PATCH 59/65] Update github actions CI to use macOS-14 macOS-13 is being removed from github actions. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b3aecd0d..e8fc9f75b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -274,7 +274,7 @@ jobs: mac: # Mac is 10x expensive to run on GitHub machines, so only run if we know something else passed as well needs: windows_clang - runs-on: macos-13 + runs-on: macos-14 timeout-minutes: 30 strategy: matrix: @@ -305,7 +305,7 @@ jobs: # Mac is 10x expensive to run on GitHub machines, so only run if we know something else passed as well needs: windows_clang name: ${{ matrix.CMAKE_SYSTEM_NAME }} - runs-on: macos-13 + runs-on: macos-14 timeout-minutes: 30 strategy: matrix: From 35e5469b619bfeeaf30d8c8e54ddde20e8a9e5f5 Mon Sep 17 00:00:00 2001 From: Charles Giessen <46324611+charles-lunarg@users.noreply.github.com> Date: Sun, 5 Oct 2025 22:10:38 -0500 Subject: [PATCH 60/65] Use PROJECT_VERSION instead of CMAKE_PROJECT_VERSION --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a42239cc7..ad4aa229f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ if(WIN32) set(BUILD_DLL_VERSIONINFO "" CACHE STRING "Set the version to be used in the loader.rc file. Default value is the currently generated header version") endif() -find_package(VulkanHeaders ${CMAKE_PROJECT_VERSION} CONFIG QUIET) +find_package(VulkanHeaders ${PROJECT_VERSION} CONFIG QUIET) include(GNUInstallDirs) From 385716f0a63fe2c26e54a5140c9877a80da66592 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:06:14 +0000 Subject: [PATCH 61/65] build(deps): bump github/codeql-action from 3.30.5 to 3.30.6 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.5 to 3.30.6. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/3599b3baa15b485a2e49ef411a7a4bb2452e7f93...64d10c13136e1c5bce3e5fbde8d4906eeaafc885) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 59e541ba8..f2897064a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 + uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 + uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 - uses: actions/setup-python@v6 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 + uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 with: category: "/language:${{matrix.language}}" From 40fdef426201fa25c5847e18d96d8f28f29a5195 Mon Sep 17 00:00:00 2001 From: Mike Schuchardt Date: Fri, 10 Oct 2025 09:20:07 -0700 Subject: [PATCH 62/65] build: Update to header 1.4.329 --- CMakeLists.txt | 2 +- loader/loader.rc | 4 ++-- scripts/known_good.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad4aa229f..0dc9a7487 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.328 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.329 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/loader.rc b/loader/loader.rc index 3fc9a45bc..af35e1337 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 328, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.328.Dev Build" +#define VER_FILE_VERSION 1, 4, 329, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.329.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index 36e806745..7052689f5 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.328" + "commit": "v1.4.329" }, { "name": "googletest", From 4a40e3cf16b35a5bc807e5f9b63913d6265bc08f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:05:03 +0000 Subject: [PATCH 63/65] build(deps): bump github/codeql-action from 3.30.6 to 4.30.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.6 to 4.30.8. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/64d10c13136e1c5bce3e5fbde8d4906eeaafc885...f443b600d91635bebf5b0d9ebc620189c0d6fba5) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.30.8 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f2897064a..0c480b28c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 + uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 + uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 - uses: actions/setup-python@v6 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 + uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 with: category: "/language:${{matrix.language}}" From 66f2ce1f4683231d13e7e0566a83fc65beddaf08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:18:20 +0000 Subject: [PATCH 64/65] build(deps): bump github/codeql-action from 4.30.8 to 4.30.9 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.30.8 to 4.30.9. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f443b600d91635bebf5b0d9ebc620189c0d6fba5...16140ae1a102900babc80a33c44059580f687047) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.30.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0c480b28c..769e7fba9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 + uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # If this step fails, then you should remove it and run the build manually - name: Autobuild if: matrix.language == 'python' - uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 + uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 - uses: actions/setup-python@v6 if: matrix.language == 'cpp' @@ -92,6 +92,6 @@ jobs: run: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 + uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 with: category: "/language:${{matrix.language}}" From 466498bc64eb77955c3b782f0127520548224de0 Mon Sep 17 00:00:00 2001 From: Mike Schuchardt Date: Fri, 24 Oct 2025 08:01:51 -0700 Subject: [PATCH 65/65] build: Update to header 1.4.330 --- CMakeLists.txt | 2 +- loader/generated/vk_layer_dispatch_table.h | 142 ++-- loader/generated/vk_loader_extensions.c | 769 +++++++++++------- loader/generated/vk_object_types.h | 80 +- loader/loader.rc | 4 +- scripts/known_good.json | 2 +- .../generated/vk_result_to_string_helper.h | 4 +- .../generated/vk_dispatch_table_helper.h | 136 ++-- 8 files changed, 673 insertions(+), 466 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0dc9a7487..afed8e2ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # ~~~ cmake_minimum_required(VERSION 3.22.1) -project(VULKAN_LOADER VERSION 1.4.329 LANGUAGES C) +project(VULKAN_LOADER VERSION 1.4.330 LANGUAGES C) option(CODE_COVERAGE "Enable Code Coverage" OFF) if (CODE_COVERAGE) diff --git a/loader/generated/vk_layer_dispatch_table.h b/loader/generated/vk_layer_dispatch_table.h index c03cd02d9..2b86d9880 100644 --- a/loader/generated/vk_layer_dispatch_table.h +++ b/loader/generated/vk_layer_dispatch_table.h @@ -343,30 +343,50 @@ typedef struct VkLayerDispatchTable_ { PFN_vkWaitForFences WaitForFences; PFN_vkCreateSemaphore CreateSemaphore; PFN_vkDestroySemaphore DestroySemaphore; - PFN_vkCreateEvent CreateEvent; - PFN_vkDestroyEvent DestroyEvent; - PFN_vkGetEventStatus GetEventStatus; - PFN_vkSetEvent SetEvent; - PFN_vkResetEvent ResetEvent; PFN_vkCreateQueryPool CreateQueryPool; PFN_vkDestroyQueryPool DestroyQueryPool; PFN_vkGetQueryPoolResults GetQueryPoolResults; PFN_vkCreateBuffer CreateBuffer; PFN_vkDestroyBuffer DestroyBuffer; - PFN_vkCreateBufferView CreateBufferView; - PFN_vkDestroyBufferView DestroyBufferView; PFN_vkCreateImage CreateImage; PFN_vkDestroyImage DestroyImage; PFN_vkGetImageSubresourceLayout GetImageSubresourceLayout; PFN_vkCreateImageView CreateImageView; PFN_vkDestroyImageView DestroyImageView; + PFN_vkCreateCommandPool CreateCommandPool; + PFN_vkDestroyCommandPool DestroyCommandPool; + PFN_vkResetCommandPool ResetCommandPool; + PFN_vkAllocateCommandBuffers AllocateCommandBuffers; + PFN_vkFreeCommandBuffers FreeCommandBuffers; + PFN_vkBeginCommandBuffer BeginCommandBuffer; + PFN_vkEndCommandBuffer EndCommandBuffer; + PFN_vkResetCommandBuffer ResetCommandBuffer; + PFN_vkCmdCopyBuffer CmdCopyBuffer; + PFN_vkCmdCopyImage CmdCopyImage; + PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage; + PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer; + PFN_vkCmdUpdateBuffer CmdUpdateBuffer; + PFN_vkCmdFillBuffer CmdFillBuffer; + PFN_vkCmdPipelineBarrier CmdPipelineBarrier; + PFN_vkCmdBeginQuery CmdBeginQuery; + PFN_vkCmdEndQuery CmdEndQuery; + PFN_vkCmdResetQueryPool CmdResetQueryPool; + PFN_vkCmdWriteTimestamp CmdWriteTimestamp; + PFN_vkCmdCopyQueryPoolResults CmdCopyQueryPoolResults; + PFN_vkCmdExecuteCommands CmdExecuteCommands; + PFN_vkCreateEvent CreateEvent; + PFN_vkDestroyEvent DestroyEvent; + PFN_vkGetEventStatus GetEventStatus; + PFN_vkSetEvent SetEvent; + PFN_vkResetEvent ResetEvent; + PFN_vkCreateBufferView CreateBufferView; + PFN_vkDestroyBufferView DestroyBufferView; PFN_vkCreateShaderModule CreateShaderModule; PFN_vkDestroyShaderModule DestroyShaderModule; PFN_vkCreatePipelineCache CreatePipelineCache; PFN_vkDestroyPipelineCache DestroyPipelineCache; PFN_vkGetPipelineCacheData GetPipelineCacheData; PFN_vkMergePipelineCaches MergePipelineCaches; - PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines; PFN_vkCreateComputePipelines CreateComputePipelines; PFN_vkDestroyPipeline DestroyPipeline; PFN_vkCreatePipelineLayout CreatePipelineLayout; @@ -381,20 +401,21 @@ typedef struct VkLayerDispatchTable_ { PFN_vkAllocateDescriptorSets AllocateDescriptorSets; PFN_vkFreeDescriptorSets FreeDescriptorSets; PFN_vkUpdateDescriptorSets UpdateDescriptorSets; + PFN_vkCmdBindPipeline CmdBindPipeline; + PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets; + PFN_vkCmdClearColorImage CmdClearColorImage; + PFN_vkCmdDispatch CmdDispatch; + PFN_vkCmdDispatchIndirect CmdDispatchIndirect; + PFN_vkCmdSetEvent CmdSetEvent; + PFN_vkCmdResetEvent CmdResetEvent; + PFN_vkCmdWaitEvents CmdWaitEvents; + PFN_vkCmdPushConstants CmdPushConstants; + PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines; PFN_vkCreateFramebuffer CreateFramebuffer; PFN_vkDestroyFramebuffer DestroyFramebuffer; PFN_vkCreateRenderPass CreateRenderPass; PFN_vkDestroyRenderPass DestroyRenderPass; PFN_vkGetRenderAreaGranularity GetRenderAreaGranularity; - PFN_vkCreateCommandPool CreateCommandPool; - PFN_vkDestroyCommandPool DestroyCommandPool; - PFN_vkResetCommandPool ResetCommandPool; - PFN_vkAllocateCommandBuffers AllocateCommandBuffers; - PFN_vkFreeCommandBuffers FreeCommandBuffers; - PFN_vkBeginCommandBuffer BeginCommandBuffer; - PFN_vkEndCommandBuffer EndCommandBuffer; - PFN_vkResetCommandBuffer ResetCommandBuffer; - PFN_vkCmdBindPipeline CmdBindPipeline; PFN_vkCmdSetViewport CmdSetViewport; PFN_vkCmdSetScissor CmdSetScissor; PFN_vkCmdSetLineWidth CmdSetLineWidth; @@ -404,66 +425,39 @@ typedef struct VkLayerDispatchTable_ { PFN_vkCmdSetStencilCompareMask CmdSetStencilCompareMask; PFN_vkCmdSetStencilWriteMask CmdSetStencilWriteMask; PFN_vkCmdSetStencilReference CmdSetStencilReference; - PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets; PFN_vkCmdBindIndexBuffer CmdBindIndexBuffer; PFN_vkCmdBindVertexBuffers CmdBindVertexBuffers; PFN_vkCmdDraw CmdDraw; PFN_vkCmdDrawIndexed CmdDrawIndexed; PFN_vkCmdDrawIndirect CmdDrawIndirect; PFN_vkCmdDrawIndexedIndirect CmdDrawIndexedIndirect; - PFN_vkCmdDispatch CmdDispatch; - PFN_vkCmdDispatchIndirect CmdDispatchIndirect; - PFN_vkCmdCopyBuffer CmdCopyBuffer; - PFN_vkCmdCopyImage CmdCopyImage; PFN_vkCmdBlitImage CmdBlitImage; - PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage; - PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer; - PFN_vkCmdUpdateBuffer CmdUpdateBuffer; - PFN_vkCmdFillBuffer CmdFillBuffer; - PFN_vkCmdClearColorImage CmdClearColorImage; PFN_vkCmdClearDepthStencilImage CmdClearDepthStencilImage; PFN_vkCmdClearAttachments CmdClearAttachments; PFN_vkCmdResolveImage CmdResolveImage; - PFN_vkCmdSetEvent CmdSetEvent; - PFN_vkCmdResetEvent CmdResetEvent; - PFN_vkCmdWaitEvents CmdWaitEvents; - PFN_vkCmdPipelineBarrier CmdPipelineBarrier; - PFN_vkCmdBeginQuery CmdBeginQuery; - PFN_vkCmdEndQuery CmdEndQuery; - PFN_vkCmdResetQueryPool CmdResetQueryPool; - PFN_vkCmdWriteTimestamp CmdWriteTimestamp; - PFN_vkCmdCopyQueryPoolResults CmdCopyQueryPoolResults; - PFN_vkCmdPushConstants CmdPushConstants; PFN_vkCmdBeginRenderPass CmdBeginRenderPass; PFN_vkCmdNextSubpass CmdNextSubpass; PFN_vkCmdEndRenderPass CmdEndRenderPass; - PFN_vkCmdExecuteCommands CmdExecuteCommands; // ---- Core Vulkan 1.1 commands PFN_vkBindBufferMemory2 BindBufferMemory2; PFN_vkBindImageMemory2 BindImageMemory2; PFN_vkGetDeviceGroupPeerMemoryFeatures GetDeviceGroupPeerMemoryFeatures; PFN_vkCmdSetDeviceMask CmdSetDeviceMask; - PFN_vkCmdDispatchBase CmdDispatchBase; PFN_vkGetImageMemoryRequirements2 GetImageMemoryRequirements2; PFN_vkGetBufferMemoryRequirements2 GetBufferMemoryRequirements2; PFN_vkGetImageSparseMemoryRequirements2 GetImageSparseMemoryRequirements2; PFN_vkTrimCommandPool TrimCommandPool; PFN_vkGetDeviceQueue2 GetDeviceQueue2; - PFN_vkCreateSamplerYcbcrConversion CreateSamplerYcbcrConversion; - PFN_vkDestroySamplerYcbcrConversion DestroySamplerYcbcrConversion; + PFN_vkCmdDispatchBase CmdDispatchBase; PFN_vkCreateDescriptorUpdateTemplate CreateDescriptorUpdateTemplate; PFN_vkDestroyDescriptorUpdateTemplate DestroyDescriptorUpdateTemplate; PFN_vkUpdateDescriptorSetWithTemplate UpdateDescriptorSetWithTemplate; PFN_vkGetDescriptorSetLayoutSupport GetDescriptorSetLayoutSupport; + PFN_vkCreateSamplerYcbcrConversion CreateSamplerYcbcrConversion; + PFN_vkDestroySamplerYcbcrConversion DestroySamplerYcbcrConversion; // ---- Core Vulkan 1.2 commands - PFN_vkCmdDrawIndirectCount CmdDrawIndirectCount; - PFN_vkCmdDrawIndexedIndirectCount CmdDrawIndexedIndirectCount; - PFN_vkCreateRenderPass2 CreateRenderPass2; - PFN_vkCmdBeginRenderPass2 CmdBeginRenderPass2; - PFN_vkCmdNextSubpass2 CmdNextSubpass2; - PFN_vkCmdEndRenderPass2 CmdEndRenderPass2; PFN_vkResetQueryPool ResetQueryPool; PFN_vkGetSemaphoreCounterValue GetSemaphoreCounterValue; PFN_vkWaitSemaphores WaitSemaphores; @@ -471,15 +465,18 @@ typedef struct VkLayerDispatchTable_ { PFN_vkGetBufferDeviceAddress GetBufferDeviceAddress; PFN_vkGetBufferOpaqueCaptureAddress GetBufferOpaqueCaptureAddress; PFN_vkGetDeviceMemoryOpaqueCaptureAddress GetDeviceMemoryOpaqueCaptureAddress; + PFN_vkCmdDrawIndirectCount CmdDrawIndirectCount; + PFN_vkCmdDrawIndexedIndirectCount CmdDrawIndexedIndirectCount; + PFN_vkCreateRenderPass2 CreateRenderPass2; + PFN_vkCmdBeginRenderPass2 CmdBeginRenderPass2; + PFN_vkCmdNextSubpass2 CmdNextSubpass2; + PFN_vkCmdEndRenderPass2 CmdEndRenderPass2; // ---- Core Vulkan 1.3 commands PFN_vkCreatePrivateDataSlot CreatePrivateDataSlot; PFN_vkDestroyPrivateDataSlot DestroyPrivateDataSlot; PFN_vkSetPrivateData SetPrivateData; PFN_vkGetPrivateData GetPrivateData; - PFN_vkCmdSetEvent2 CmdSetEvent2; - PFN_vkCmdResetEvent2 CmdResetEvent2; - PFN_vkCmdWaitEvents2 CmdWaitEvents2; PFN_vkCmdPipelineBarrier2 CmdPipelineBarrier2; PFN_vkCmdWriteTimestamp2 CmdWriteTimestamp2; PFN_vkQueueSubmit2 QueueSubmit2; @@ -487,6 +484,12 @@ typedef struct VkLayerDispatchTable_ { PFN_vkCmdCopyImage2 CmdCopyImage2; PFN_vkCmdCopyBufferToImage2 CmdCopyBufferToImage2; PFN_vkCmdCopyImageToBuffer2 CmdCopyImageToBuffer2; + PFN_vkGetDeviceBufferMemoryRequirements GetDeviceBufferMemoryRequirements; + PFN_vkGetDeviceImageMemoryRequirements GetDeviceImageMemoryRequirements; + PFN_vkGetDeviceImageSparseMemoryRequirements GetDeviceImageSparseMemoryRequirements; + PFN_vkCmdSetEvent2 CmdSetEvent2; + PFN_vkCmdResetEvent2 CmdResetEvent2; + PFN_vkCmdWaitEvents2 CmdWaitEvents2; PFN_vkCmdBlitImage2 CmdBlitImage2; PFN_vkCmdResolveImage2 CmdResolveImage2; PFN_vkCmdBeginRendering CmdBeginRendering; @@ -506,30 +509,27 @@ typedef struct VkLayerDispatchTable_ { PFN_vkCmdSetRasterizerDiscardEnable CmdSetRasterizerDiscardEnable; PFN_vkCmdSetDepthBiasEnable CmdSetDepthBiasEnable; PFN_vkCmdSetPrimitiveRestartEnable CmdSetPrimitiveRestartEnable; - PFN_vkGetDeviceBufferMemoryRequirements GetDeviceBufferMemoryRequirements; - PFN_vkGetDeviceImageMemoryRequirements GetDeviceImageMemoryRequirements; - PFN_vkGetDeviceImageSparseMemoryRequirements GetDeviceImageSparseMemoryRequirements; // ---- Core Vulkan 1.4 commands - PFN_vkCmdSetLineStipple CmdSetLineStipple; PFN_vkMapMemory2 MapMemory2; PFN_vkUnmapMemory2 UnmapMemory2; - PFN_vkCmdBindIndexBuffer2 CmdBindIndexBuffer2; - PFN_vkGetRenderingAreaGranularity GetRenderingAreaGranularity; PFN_vkGetDeviceImageSubresourceLayout GetDeviceImageSubresourceLayout; PFN_vkGetImageSubresourceLayout2 GetImageSubresourceLayout2; + PFN_vkCopyMemoryToImage CopyMemoryToImage; + PFN_vkCopyImageToMemory CopyImageToMemory; + PFN_vkCopyImageToImage CopyImageToImage; + PFN_vkTransitionImageLayout TransitionImageLayout; PFN_vkCmdPushDescriptorSet CmdPushDescriptorSet; PFN_vkCmdPushDescriptorSetWithTemplate CmdPushDescriptorSetWithTemplate; - PFN_vkCmdSetRenderingAttachmentLocations CmdSetRenderingAttachmentLocations; - PFN_vkCmdSetRenderingInputAttachmentIndices CmdSetRenderingInputAttachmentIndices; PFN_vkCmdBindDescriptorSets2 CmdBindDescriptorSets2; PFN_vkCmdPushConstants2 CmdPushConstants2; PFN_vkCmdPushDescriptorSet2 CmdPushDescriptorSet2; PFN_vkCmdPushDescriptorSetWithTemplate2 CmdPushDescriptorSetWithTemplate2; - PFN_vkCopyMemoryToImage CopyMemoryToImage; - PFN_vkCopyImageToMemory CopyImageToMemory; - PFN_vkCopyImageToImage CopyImageToImage; - PFN_vkTransitionImageLayout TransitionImageLayout; + PFN_vkCmdSetLineStipple CmdSetLineStipple; + PFN_vkCmdBindIndexBuffer2 CmdBindIndexBuffer2; + PFN_vkGetRenderingAreaGranularity GetRenderingAreaGranularity; + PFN_vkCmdSetRenderingAttachmentLocations CmdSetRenderingAttachmentLocations; + PFN_vkCmdSetRenderingInputAttachmentIndices CmdSetRenderingInputAttachmentIndices; // ---- VK_KHR_swapchain extension commands PFN_vkCreateSwapchainKHR CreateSwapchainKHR; @@ -750,6 +750,9 @@ typedef struct VkLayerDispatchTable_ { PFN_vkCmdCopyMemoryIndirectKHR CmdCopyMemoryIndirectKHR; PFN_vkCmdCopyMemoryToImageIndirectKHR CmdCopyMemoryToImageIndirectKHR; + // ---- VK_KHR_maintenance10 extension commands + PFN_vkCmdEndRendering2KHR CmdEndRendering2KHR; + // ---- VK_EXT_debug_marker extension commands PFN_vkDebugMarkerSetObjectTagEXT DebugMarkerSetObjectTagEXT; PFN_vkDebugMarkerSetObjectNameEXT DebugMarkerSetObjectNameEXT; @@ -1244,6 +1247,10 @@ typedef struct VkLayerDispatchTable_ { // ---- VK_QCOM_tile_memory_heap extension commands PFN_vkCmdBindTileMemoryQCOM CmdBindTileMemoryQCOM; + // ---- VK_EXT_memory_decompression extension commands + PFN_vkCmdDecompressMemoryEXT CmdDecompressMemoryEXT; + PFN_vkCmdDecompressMemoryIndirectCountEXT CmdDecompressMemoryIndirectCountEXT; + // ---- VK_NV_external_compute_queue extension commands PFN_vkCreateExternalComputeQueueNV CreateExternalComputeQueueNV; PFN_vkDestroyExternalComputeQueueNV DestroyExternalComputeQueueNV; @@ -1268,6 +1275,17 @@ typedef struct VkLayerDispatchTable_ { PFN_vkUpdateIndirectExecutionSetPipelineEXT UpdateIndirectExecutionSetPipelineEXT; PFN_vkUpdateIndirectExecutionSetShaderEXT UpdateIndirectExecutionSetShaderEXT; + // ---- VK_OHOS_native_buffer extension commands +#if defined(VK_USE_PLATFORM_OHOS) + PFN_vkGetSwapchainGrallocUsageOHOS GetSwapchainGrallocUsageOHOS; +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + PFN_vkAcquireImageOHOS AcquireImageOHOS; +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + PFN_vkQueueSignalReleaseImageOHOS QueueSignalReleaseImageOHOS; +#endif // VK_USE_PLATFORM_OHOS + // ---- VK_EXT_external_memory_metal extension commands #if defined(VK_USE_PLATFORM_METAL_EXT) PFN_vkGetMemoryMetalHandleEXT GetMemoryMetalHandleEXT; diff --git a/loader/generated/vk_loader_extensions.c b/loader/generated/vk_loader_extensions.c index 6610c5056..bd9c9bfbf 100644 --- a/loader/generated/vk_loader_extensions.c +++ b/loader/generated/vk_loader_extensions.c @@ -371,30 +371,50 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_dispatch_table(struct loader_dev_d table->WaitForFences = (PFN_vkWaitForFences)gpa(dev, "vkWaitForFences"); table->CreateSemaphore = (PFN_vkCreateSemaphore)gpa(dev, "vkCreateSemaphore"); table->DestroySemaphore = (PFN_vkDestroySemaphore)gpa(dev, "vkDestroySemaphore"); - table->CreateEvent = (PFN_vkCreateEvent)gpa(dev, "vkCreateEvent"); - table->DestroyEvent = (PFN_vkDestroyEvent)gpa(dev, "vkDestroyEvent"); - table->GetEventStatus = (PFN_vkGetEventStatus)gpa(dev, "vkGetEventStatus"); - table->SetEvent = (PFN_vkSetEvent)gpa(dev, "vkSetEvent"); - table->ResetEvent = (PFN_vkResetEvent)gpa(dev, "vkResetEvent"); table->CreateQueryPool = (PFN_vkCreateQueryPool)gpa(dev, "vkCreateQueryPool"); table->DestroyQueryPool = (PFN_vkDestroyQueryPool)gpa(dev, "vkDestroyQueryPool"); table->GetQueryPoolResults = (PFN_vkGetQueryPoolResults)gpa(dev, "vkGetQueryPoolResults"); table->CreateBuffer = (PFN_vkCreateBuffer)gpa(dev, "vkCreateBuffer"); table->DestroyBuffer = (PFN_vkDestroyBuffer)gpa(dev, "vkDestroyBuffer"); - table->CreateBufferView = (PFN_vkCreateBufferView)gpa(dev, "vkCreateBufferView"); - table->DestroyBufferView = (PFN_vkDestroyBufferView)gpa(dev, "vkDestroyBufferView"); table->CreateImage = (PFN_vkCreateImage)gpa(dev, "vkCreateImage"); table->DestroyImage = (PFN_vkDestroyImage)gpa(dev, "vkDestroyImage"); table->GetImageSubresourceLayout = (PFN_vkGetImageSubresourceLayout)gpa(dev, "vkGetImageSubresourceLayout"); table->CreateImageView = (PFN_vkCreateImageView)gpa(dev, "vkCreateImageView"); table->DestroyImageView = (PFN_vkDestroyImageView)gpa(dev, "vkDestroyImageView"); + table->CreateCommandPool = (PFN_vkCreateCommandPool)gpa(dev, "vkCreateCommandPool"); + table->DestroyCommandPool = (PFN_vkDestroyCommandPool)gpa(dev, "vkDestroyCommandPool"); + table->ResetCommandPool = (PFN_vkResetCommandPool)gpa(dev, "vkResetCommandPool"); + table->AllocateCommandBuffers = (PFN_vkAllocateCommandBuffers)gpa(dev, "vkAllocateCommandBuffers"); + table->FreeCommandBuffers = (PFN_vkFreeCommandBuffers)gpa(dev, "vkFreeCommandBuffers"); + table->BeginCommandBuffer = (PFN_vkBeginCommandBuffer)gpa(dev, "vkBeginCommandBuffer"); + table->EndCommandBuffer = (PFN_vkEndCommandBuffer)gpa(dev, "vkEndCommandBuffer"); + table->ResetCommandBuffer = (PFN_vkResetCommandBuffer)gpa(dev, "vkResetCommandBuffer"); + table->CmdCopyBuffer = (PFN_vkCmdCopyBuffer)gpa(dev, "vkCmdCopyBuffer"); + table->CmdCopyImage = (PFN_vkCmdCopyImage)gpa(dev, "vkCmdCopyImage"); + table->CmdCopyBufferToImage = (PFN_vkCmdCopyBufferToImage)gpa(dev, "vkCmdCopyBufferToImage"); + table->CmdCopyImageToBuffer = (PFN_vkCmdCopyImageToBuffer)gpa(dev, "vkCmdCopyImageToBuffer"); + table->CmdUpdateBuffer = (PFN_vkCmdUpdateBuffer)gpa(dev, "vkCmdUpdateBuffer"); + table->CmdFillBuffer = (PFN_vkCmdFillBuffer)gpa(dev, "vkCmdFillBuffer"); + table->CmdPipelineBarrier = (PFN_vkCmdPipelineBarrier)gpa(dev, "vkCmdPipelineBarrier"); + table->CmdBeginQuery = (PFN_vkCmdBeginQuery)gpa(dev, "vkCmdBeginQuery"); + table->CmdEndQuery = (PFN_vkCmdEndQuery)gpa(dev, "vkCmdEndQuery"); + table->CmdResetQueryPool = (PFN_vkCmdResetQueryPool)gpa(dev, "vkCmdResetQueryPool"); + table->CmdWriteTimestamp = (PFN_vkCmdWriteTimestamp)gpa(dev, "vkCmdWriteTimestamp"); + table->CmdCopyQueryPoolResults = (PFN_vkCmdCopyQueryPoolResults)gpa(dev, "vkCmdCopyQueryPoolResults"); + table->CmdExecuteCommands = (PFN_vkCmdExecuteCommands)gpa(dev, "vkCmdExecuteCommands"); + table->CreateEvent = (PFN_vkCreateEvent)gpa(dev, "vkCreateEvent"); + table->DestroyEvent = (PFN_vkDestroyEvent)gpa(dev, "vkDestroyEvent"); + table->GetEventStatus = (PFN_vkGetEventStatus)gpa(dev, "vkGetEventStatus"); + table->SetEvent = (PFN_vkSetEvent)gpa(dev, "vkSetEvent"); + table->ResetEvent = (PFN_vkResetEvent)gpa(dev, "vkResetEvent"); + table->CreateBufferView = (PFN_vkCreateBufferView)gpa(dev, "vkCreateBufferView"); + table->DestroyBufferView = (PFN_vkDestroyBufferView)gpa(dev, "vkDestroyBufferView"); table->CreateShaderModule = (PFN_vkCreateShaderModule)gpa(dev, "vkCreateShaderModule"); table->DestroyShaderModule = (PFN_vkDestroyShaderModule)gpa(dev, "vkDestroyShaderModule"); table->CreatePipelineCache = (PFN_vkCreatePipelineCache)gpa(dev, "vkCreatePipelineCache"); table->DestroyPipelineCache = (PFN_vkDestroyPipelineCache)gpa(dev, "vkDestroyPipelineCache"); table->GetPipelineCacheData = (PFN_vkGetPipelineCacheData)gpa(dev, "vkGetPipelineCacheData"); table->MergePipelineCaches = (PFN_vkMergePipelineCaches)gpa(dev, "vkMergePipelineCaches"); - table->CreateGraphicsPipelines = (PFN_vkCreateGraphicsPipelines)gpa(dev, "vkCreateGraphicsPipelines"); table->CreateComputePipelines = (PFN_vkCreateComputePipelines)gpa(dev, "vkCreateComputePipelines"); table->DestroyPipeline = (PFN_vkDestroyPipeline)gpa(dev, "vkDestroyPipeline"); table->CreatePipelineLayout = (PFN_vkCreatePipelineLayout)gpa(dev, "vkCreatePipelineLayout"); @@ -409,20 +429,21 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_dispatch_table(struct loader_dev_d table->AllocateDescriptorSets = (PFN_vkAllocateDescriptorSets)gpa(dev, "vkAllocateDescriptorSets"); table->FreeDescriptorSets = (PFN_vkFreeDescriptorSets)gpa(dev, "vkFreeDescriptorSets"); table->UpdateDescriptorSets = (PFN_vkUpdateDescriptorSets)gpa(dev, "vkUpdateDescriptorSets"); + table->CmdBindPipeline = (PFN_vkCmdBindPipeline)gpa(dev, "vkCmdBindPipeline"); + table->CmdBindDescriptorSets = (PFN_vkCmdBindDescriptorSets)gpa(dev, "vkCmdBindDescriptorSets"); + table->CmdClearColorImage = (PFN_vkCmdClearColorImage)gpa(dev, "vkCmdClearColorImage"); + table->CmdDispatch = (PFN_vkCmdDispatch)gpa(dev, "vkCmdDispatch"); + table->CmdDispatchIndirect = (PFN_vkCmdDispatchIndirect)gpa(dev, "vkCmdDispatchIndirect"); + table->CmdSetEvent = (PFN_vkCmdSetEvent)gpa(dev, "vkCmdSetEvent"); + table->CmdResetEvent = (PFN_vkCmdResetEvent)gpa(dev, "vkCmdResetEvent"); + table->CmdWaitEvents = (PFN_vkCmdWaitEvents)gpa(dev, "vkCmdWaitEvents"); + table->CmdPushConstants = (PFN_vkCmdPushConstants)gpa(dev, "vkCmdPushConstants"); + table->CreateGraphicsPipelines = (PFN_vkCreateGraphicsPipelines)gpa(dev, "vkCreateGraphicsPipelines"); table->CreateFramebuffer = (PFN_vkCreateFramebuffer)gpa(dev, "vkCreateFramebuffer"); table->DestroyFramebuffer = (PFN_vkDestroyFramebuffer)gpa(dev, "vkDestroyFramebuffer"); table->CreateRenderPass = (PFN_vkCreateRenderPass)gpa(dev, "vkCreateRenderPass"); table->DestroyRenderPass = (PFN_vkDestroyRenderPass)gpa(dev, "vkDestroyRenderPass"); table->GetRenderAreaGranularity = (PFN_vkGetRenderAreaGranularity)gpa(dev, "vkGetRenderAreaGranularity"); - table->CreateCommandPool = (PFN_vkCreateCommandPool)gpa(dev, "vkCreateCommandPool"); - table->DestroyCommandPool = (PFN_vkDestroyCommandPool)gpa(dev, "vkDestroyCommandPool"); - table->ResetCommandPool = (PFN_vkResetCommandPool)gpa(dev, "vkResetCommandPool"); - table->AllocateCommandBuffers = (PFN_vkAllocateCommandBuffers)gpa(dev, "vkAllocateCommandBuffers"); - table->FreeCommandBuffers = (PFN_vkFreeCommandBuffers)gpa(dev, "vkFreeCommandBuffers"); - table->BeginCommandBuffer = (PFN_vkBeginCommandBuffer)gpa(dev, "vkBeginCommandBuffer"); - table->EndCommandBuffer = (PFN_vkEndCommandBuffer)gpa(dev, "vkEndCommandBuffer"); - table->ResetCommandBuffer = (PFN_vkResetCommandBuffer)gpa(dev, "vkResetCommandBuffer"); - table->CmdBindPipeline = (PFN_vkCmdBindPipeline)gpa(dev, "vkCmdBindPipeline"); table->CmdSetViewport = (PFN_vkCmdSetViewport)gpa(dev, "vkCmdSetViewport"); table->CmdSetScissor = (PFN_vkCmdSetScissor)gpa(dev, "vkCmdSetScissor"); table->CmdSetLineWidth = (PFN_vkCmdSetLineWidth)gpa(dev, "vkCmdSetLineWidth"); @@ -432,66 +453,39 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_dispatch_table(struct loader_dev_d table->CmdSetStencilCompareMask = (PFN_vkCmdSetStencilCompareMask)gpa(dev, "vkCmdSetStencilCompareMask"); table->CmdSetStencilWriteMask = (PFN_vkCmdSetStencilWriteMask)gpa(dev, "vkCmdSetStencilWriteMask"); table->CmdSetStencilReference = (PFN_vkCmdSetStencilReference)gpa(dev, "vkCmdSetStencilReference"); - table->CmdBindDescriptorSets = (PFN_vkCmdBindDescriptorSets)gpa(dev, "vkCmdBindDescriptorSets"); table->CmdBindIndexBuffer = (PFN_vkCmdBindIndexBuffer)gpa(dev, "vkCmdBindIndexBuffer"); table->CmdBindVertexBuffers = (PFN_vkCmdBindVertexBuffers)gpa(dev, "vkCmdBindVertexBuffers"); table->CmdDraw = (PFN_vkCmdDraw)gpa(dev, "vkCmdDraw"); table->CmdDrawIndexed = (PFN_vkCmdDrawIndexed)gpa(dev, "vkCmdDrawIndexed"); table->CmdDrawIndirect = (PFN_vkCmdDrawIndirect)gpa(dev, "vkCmdDrawIndirect"); table->CmdDrawIndexedIndirect = (PFN_vkCmdDrawIndexedIndirect)gpa(dev, "vkCmdDrawIndexedIndirect"); - table->CmdDispatch = (PFN_vkCmdDispatch)gpa(dev, "vkCmdDispatch"); - table->CmdDispatchIndirect = (PFN_vkCmdDispatchIndirect)gpa(dev, "vkCmdDispatchIndirect"); - table->CmdCopyBuffer = (PFN_vkCmdCopyBuffer)gpa(dev, "vkCmdCopyBuffer"); - table->CmdCopyImage = (PFN_vkCmdCopyImage)gpa(dev, "vkCmdCopyImage"); table->CmdBlitImage = (PFN_vkCmdBlitImage)gpa(dev, "vkCmdBlitImage"); - table->CmdCopyBufferToImage = (PFN_vkCmdCopyBufferToImage)gpa(dev, "vkCmdCopyBufferToImage"); - table->CmdCopyImageToBuffer = (PFN_vkCmdCopyImageToBuffer)gpa(dev, "vkCmdCopyImageToBuffer"); - table->CmdUpdateBuffer = (PFN_vkCmdUpdateBuffer)gpa(dev, "vkCmdUpdateBuffer"); - table->CmdFillBuffer = (PFN_vkCmdFillBuffer)gpa(dev, "vkCmdFillBuffer"); - table->CmdClearColorImage = (PFN_vkCmdClearColorImage)gpa(dev, "vkCmdClearColorImage"); table->CmdClearDepthStencilImage = (PFN_vkCmdClearDepthStencilImage)gpa(dev, "vkCmdClearDepthStencilImage"); table->CmdClearAttachments = (PFN_vkCmdClearAttachments)gpa(dev, "vkCmdClearAttachments"); table->CmdResolveImage = (PFN_vkCmdResolveImage)gpa(dev, "vkCmdResolveImage"); - table->CmdSetEvent = (PFN_vkCmdSetEvent)gpa(dev, "vkCmdSetEvent"); - table->CmdResetEvent = (PFN_vkCmdResetEvent)gpa(dev, "vkCmdResetEvent"); - table->CmdWaitEvents = (PFN_vkCmdWaitEvents)gpa(dev, "vkCmdWaitEvents"); - table->CmdPipelineBarrier = (PFN_vkCmdPipelineBarrier)gpa(dev, "vkCmdPipelineBarrier"); - table->CmdBeginQuery = (PFN_vkCmdBeginQuery)gpa(dev, "vkCmdBeginQuery"); - table->CmdEndQuery = (PFN_vkCmdEndQuery)gpa(dev, "vkCmdEndQuery"); - table->CmdResetQueryPool = (PFN_vkCmdResetQueryPool)gpa(dev, "vkCmdResetQueryPool"); - table->CmdWriteTimestamp = (PFN_vkCmdWriteTimestamp)gpa(dev, "vkCmdWriteTimestamp"); - table->CmdCopyQueryPoolResults = (PFN_vkCmdCopyQueryPoolResults)gpa(dev, "vkCmdCopyQueryPoolResults"); - table->CmdPushConstants = (PFN_vkCmdPushConstants)gpa(dev, "vkCmdPushConstants"); table->CmdBeginRenderPass = (PFN_vkCmdBeginRenderPass)gpa(dev, "vkCmdBeginRenderPass"); table->CmdNextSubpass = (PFN_vkCmdNextSubpass)gpa(dev, "vkCmdNextSubpass"); table->CmdEndRenderPass = (PFN_vkCmdEndRenderPass)gpa(dev, "vkCmdEndRenderPass"); - table->CmdExecuteCommands = (PFN_vkCmdExecuteCommands)gpa(dev, "vkCmdExecuteCommands"); // ---- Core Vulkan 1.1 commands table->BindBufferMemory2 = (PFN_vkBindBufferMemory2)gpa(dev, "vkBindBufferMemory2"); table->BindImageMemory2 = (PFN_vkBindImageMemory2)gpa(dev, "vkBindImageMemory2"); table->GetDeviceGroupPeerMemoryFeatures = (PFN_vkGetDeviceGroupPeerMemoryFeatures)gpa(dev, "vkGetDeviceGroupPeerMemoryFeatures"); table->CmdSetDeviceMask = (PFN_vkCmdSetDeviceMask)gpa(dev, "vkCmdSetDeviceMask"); - table->CmdDispatchBase = (PFN_vkCmdDispatchBase)gpa(dev, "vkCmdDispatchBase"); table->GetImageMemoryRequirements2 = (PFN_vkGetImageMemoryRequirements2)gpa(dev, "vkGetImageMemoryRequirements2"); table->GetBufferMemoryRequirements2 = (PFN_vkGetBufferMemoryRequirements2)gpa(dev, "vkGetBufferMemoryRequirements2"); table->GetImageSparseMemoryRequirements2 = (PFN_vkGetImageSparseMemoryRequirements2)gpa(dev, "vkGetImageSparseMemoryRequirements2"); table->TrimCommandPool = (PFN_vkTrimCommandPool)gpa(dev, "vkTrimCommandPool"); table->GetDeviceQueue2 = (PFN_vkGetDeviceQueue2)gpa(dev, "vkGetDeviceQueue2"); - table->CreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)gpa(dev, "vkCreateSamplerYcbcrConversion"); - table->DestroySamplerYcbcrConversion = (PFN_vkDestroySamplerYcbcrConversion)gpa(dev, "vkDestroySamplerYcbcrConversion"); + table->CmdDispatchBase = (PFN_vkCmdDispatchBase)gpa(dev, "vkCmdDispatchBase"); table->CreateDescriptorUpdateTemplate = (PFN_vkCreateDescriptorUpdateTemplate)gpa(dev, "vkCreateDescriptorUpdateTemplate"); table->DestroyDescriptorUpdateTemplate = (PFN_vkDestroyDescriptorUpdateTemplate)gpa(dev, "vkDestroyDescriptorUpdateTemplate"); table->UpdateDescriptorSetWithTemplate = (PFN_vkUpdateDescriptorSetWithTemplate)gpa(dev, "vkUpdateDescriptorSetWithTemplate"); table->GetDescriptorSetLayoutSupport = (PFN_vkGetDescriptorSetLayoutSupport)gpa(dev, "vkGetDescriptorSetLayoutSupport"); + table->CreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)gpa(dev, "vkCreateSamplerYcbcrConversion"); + table->DestroySamplerYcbcrConversion = (PFN_vkDestroySamplerYcbcrConversion)gpa(dev, "vkDestroySamplerYcbcrConversion"); // ---- Core Vulkan 1.2 commands - table->CmdDrawIndirectCount = (PFN_vkCmdDrawIndirectCount)gpa(dev, "vkCmdDrawIndirectCount"); - table->CmdDrawIndexedIndirectCount = (PFN_vkCmdDrawIndexedIndirectCount)gpa(dev, "vkCmdDrawIndexedIndirectCount"); - table->CreateRenderPass2 = (PFN_vkCreateRenderPass2)gpa(dev, "vkCreateRenderPass2"); - table->CmdBeginRenderPass2 = (PFN_vkCmdBeginRenderPass2)gpa(dev, "vkCmdBeginRenderPass2"); - table->CmdNextSubpass2 = (PFN_vkCmdNextSubpass2)gpa(dev, "vkCmdNextSubpass2"); - table->CmdEndRenderPass2 = (PFN_vkCmdEndRenderPass2)gpa(dev, "vkCmdEndRenderPass2"); table->ResetQueryPool = (PFN_vkResetQueryPool)gpa(dev, "vkResetQueryPool"); table->GetSemaphoreCounterValue = (PFN_vkGetSemaphoreCounterValue)gpa(dev, "vkGetSemaphoreCounterValue"); table->WaitSemaphores = (PFN_vkWaitSemaphores)gpa(dev, "vkWaitSemaphores"); @@ -499,15 +493,18 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_dispatch_table(struct loader_dev_d table->GetBufferDeviceAddress = (PFN_vkGetBufferDeviceAddress)gpa(dev, "vkGetBufferDeviceAddress"); table->GetBufferOpaqueCaptureAddress = (PFN_vkGetBufferOpaqueCaptureAddress)gpa(dev, "vkGetBufferOpaqueCaptureAddress"); table->GetDeviceMemoryOpaqueCaptureAddress = (PFN_vkGetDeviceMemoryOpaqueCaptureAddress)gpa(dev, "vkGetDeviceMemoryOpaqueCaptureAddress"); + table->CmdDrawIndirectCount = (PFN_vkCmdDrawIndirectCount)gpa(dev, "vkCmdDrawIndirectCount"); + table->CmdDrawIndexedIndirectCount = (PFN_vkCmdDrawIndexedIndirectCount)gpa(dev, "vkCmdDrawIndexedIndirectCount"); + table->CreateRenderPass2 = (PFN_vkCreateRenderPass2)gpa(dev, "vkCreateRenderPass2"); + table->CmdBeginRenderPass2 = (PFN_vkCmdBeginRenderPass2)gpa(dev, "vkCmdBeginRenderPass2"); + table->CmdNextSubpass2 = (PFN_vkCmdNextSubpass2)gpa(dev, "vkCmdNextSubpass2"); + table->CmdEndRenderPass2 = (PFN_vkCmdEndRenderPass2)gpa(dev, "vkCmdEndRenderPass2"); // ---- Core Vulkan 1.3 commands table->CreatePrivateDataSlot = (PFN_vkCreatePrivateDataSlot)gpa(dev, "vkCreatePrivateDataSlot"); table->DestroyPrivateDataSlot = (PFN_vkDestroyPrivateDataSlot)gpa(dev, "vkDestroyPrivateDataSlot"); table->SetPrivateData = (PFN_vkSetPrivateData)gpa(dev, "vkSetPrivateData"); table->GetPrivateData = (PFN_vkGetPrivateData)gpa(dev, "vkGetPrivateData"); - table->CmdSetEvent2 = (PFN_vkCmdSetEvent2)gpa(dev, "vkCmdSetEvent2"); - table->CmdResetEvent2 = (PFN_vkCmdResetEvent2)gpa(dev, "vkCmdResetEvent2"); - table->CmdWaitEvents2 = (PFN_vkCmdWaitEvents2)gpa(dev, "vkCmdWaitEvents2"); table->CmdPipelineBarrier2 = (PFN_vkCmdPipelineBarrier2)gpa(dev, "vkCmdPipelineBarrier2"); table->CmdWriteTimestamp2 = (PFN_vkCmdWriteTimestamp2)gpa(dev, "vkCmdWriteTimestamp2"); table->QueueSubmit2 = (PFN_vkQueueSubmit2)gpa(dev, "vkQueueSubmit2"); @@ -515,6 +512,12 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_dispatch_table(struct loader_dev_d table->CmdCopyImage2 = (PFN_vkCmdCopyImage2)gpa(dev, "vkCmdCopyImage2"); table->CmdCopyBufferToImage2 = (PFN_vkCmdCopyBufferToImage2)gpa(dev, "vkCmdCopyBufferToImage2"); table->CmdCopyImageToBuffer2 = (PFN_vkCmdCopyImageToBuffer2)gpa(dev, "vkCmdCopyImageToBuffer2"); + table->GetDeviceBufferMemoryRequirements = (PFN_vkGetDeviceBufferMemoryRequirements)gpa(dev, "vkGetDeviceBufferMemoryRequirements"); + table->GetDeviceImageMemoryRequirements = (PFN_vkGetDeviceImageMemoryRequirements)gpa(dev, "vkGetDeviceImageMemoryRequirements"); + table->GetDeviceImageSparseMemoryRequirements = (PFN_vkGetDeviceImageSparseMemoryRequirements)gpa(dev, "vkGetDeviceImageSparseMemoryRequirements"); + table->CmdSetEvent2 = (PFN_vkCmdSetEvent2)gpa(dev, "vkCmdSetEvent2"); + table->CmdResetEvent2 = (PFN_vkCmdResetEvent2)gpa(dev, "vkCmdResetEvent2"); + table->CmdWaitEvents2 = (PFN_vkCmdWaitEvents2)gpa(dev, "vkCmdWaitEvents2"); table->CmdBlitImage2 = (PFN_vkCmdBlitImage2)gpa(dev, "vkCmdBlitImage2"); table->CmdResolveImage2 = (PFN_vkCmdResolveImage2)gpa(dev, "vkCmdResolveImage2"); table->CmdBeginRendering = (PFN_vkCmdBeginRendering)gpa(dev, "vkCmdBeginRendering"); @@ -534,30 +537,27 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_dispatch_table(struct loader_dev_d table->CmdSetRasterizerDiscardEnable = (PFN_vkCmdSetRasterizerDiscardEnable)gpa(dev, "vkCmdSetRasterizerDiscardEnable"); table->CmdSetDepthBiasEnable = (PFN_vkCmdSetDepthBiasEnable)gpa(dev, "vkCmdSetDepthBiasEnable"); table->CmdSetPrimitiveRestartEnable = (PFN_vkCmdSetPrimitiveRestartEnable)gpa(dev, "vkCmdSetPrimitiveRestartEnable"); - table->GetDeviceBufferMemoryRequirements = (PFN_vkGetDeviceBufferMemoryRequirements)gpa(dev, "vkGetDeviceBufferMemoryRequirements"); - table->GetDeviceImageMemoryRequirements = (PFN_vkGetDeviceImageMemoryRequirements)gpa(dev, "vkGetDeviceImageMemoryRequirements"); - table->GetDeviceImageSparseMemoryRequirements = (PFN_vkGetDeviceImageSparseMemoryRequirements)gpa(dev, "vkGetDeviceImageSparseMemoryRequirements"); // ---- Core Vulkan 1.4 commands - table->CmdSetLineStipple = (PFN_vkCmdSetLineStipple)gpa(dev, "vkCmdSetLineStipple"); table->MapMemory2 = (PFN_vkMapMemory2)gpa(dev, "vkMapMemory2"); table->UnmapMemory2 = (PFN_vkUnmapMemory2)gpa(dev, "vkUnmapMemory2"); - table->CmdBindIndexBuffer2 = (PFN_vkCmdBindIndexBuffer2)gpa(dev, "vkCmdBindIndexBuffer2"); - table->GetRenderingAreaGranularity = (PFN_vkGetRenderingAreaGranularity)gpa(dev, "vkGetRenderingAreaGranularity"); table->GetDeviceImageSubresourceLayout = (PFN_vkGetDeviceImageSubresourceLayout)gpa(dev, "vkGetDeviceImageSubresourceLayout"); table->GetImageSubresourceLayout2 = (PFN_vkGetImageSubresourceLayout2)gpa(dev, "vkGetImageSubresourceLayout2"); + table->CopyMemoryToImage = (PFN_vkCopyMemoryToImage)gpa(dev, "vkCopyMemoryToImage"); + table->CopyImageToMemory = (PFN_vkCopyImageToMemory)gpa(dev, "vkCopyImageToMemory"); + table->CopyImageToImage = (PFN_vkCopyImageToImage)gpa(dev, "vkCopyImageToImage"); + table->TransitionImageLayout = (PFN_vkTransitionImageLayout)gpa(dev, "vkTransitionImageLayout"); table->CmdPushDescriptorSet = (PFN_vkCmdPushDescriptorSet)gpa(dev, "vkCmdPushDescriptorSet"); table->CmdPushDescriptorSetWithTemplate = (PFN_vkCmdPushDescriptorSetWithTemplate)gpa(dev, "vkCmdPushDescriptorSetWithTemplate"); - table->CmdSetRenderingAttachmentLocations = (PFN_vkCmdSetRenderingAttachmentLocations)gpa(dev, "vkCmdSetRenderingAttachmentLocations"); - table->CmdSetRenderingInputAttachmentIndices = (PFN_vkCmdSetRenderingInputAttachmentIndices)gpa(dev, "vkCmdSetRenderingInputAttachmentIndices"); table->CmdBindDescriptorSets2 = (PFN_vkCmdBindDescriptorSets2)gpa(dev, "vkCmdBindDescriptorSets2"); table->CmdPushConstants2 = (PFN_vkCmdPushConstants2)gpa(dev, "vkCmdPushConstants2"); table->CmdPushDescriptorSet2 = (PFN_vkCmdPushDescriptorSet2)gpa(dev, "vkCmdPushDescriptorSet2"); table->CmdPushDescriptorSetWithTemplate2 = (PFN_vkCmdPushDescriptorSetWithTemplate2)gpa(dev, "vkCmdPushDescriptorSetWithTemplate2"); - table->CopyMemoryToImage = (PFN_vkCopyMemoryToImage)gpa(dev, "vkCopyMemoryToImage"); - table->CopyImageToMemory = (PFN_vkCopyImageToMemory)gpa(dev, "vkCopyImageToMemory"); - table->CopyImageToImage = (PFN_vkCopyImageToImage)gpa(dev, "vkCopyImageToImage"); - table->TransitionImageLayout = (PFN_vkTransitionImageLayout)gpa(dev, "vkTransitionImageLayout"); + table->CmdSetLineStipple = (PFN_vkCmdSetLineStipple)gpa(dev, "vkCmdSetLineStipple"); + table->CmdBindIndexBuffer2 = (PFN_vkCmdBindIndexBuffer2)gpa(dev, "vkCmdBindIndexBuffer2"); + table->GetRenderingAreaGranularity = (PFN_vkGetRenderingAreaGranularity)gpa(dev, "vkGetRenderingAreaGranularity"); + table->CmdSetRenderingAttachmentLocations = (PFN_vkCmdSetRenderingAttachmentLocations)gpa(dev, "vkCmdSetRenderingAttachmentLocations"); + table->CmdSetRenderingInputAttachmentIndices = (PFN_vkCmdSetRenderingInputAttachmentIndices)gpa(dev, "vkCmdSetRenderingInputAttachmentIndices"); } // Init Device function pointer dispatch table with extension commands @@ -788,6 +788,9 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_extension_dispatch_table(struct lo table->CmdCopyMemoryIndirectKHR = (PFN_vkCmdCopyMemoryIndirectKHR)gdpa(dev, "vkCmdCopyMemoryIndirectKHR"); table->CmdCopyMemoryToImageIndirectKHR = (PFN_vkCmdCopyMemoryToImageIndirectKHR)gdpa(dev, "vkCmdCopyMemoryToImageIndirectKHR"); + // ---- VK_KHR_maintenance10 extension commands + table->CmdEndRendering2KHR = (PFN_vkCmdEndRendering2KHR)gdpa(dev, "vkCmdEndRendering2KHR"); + // ---- VK_EXT_debug_marker extension commands table->DebugMarkerSetObjectTagEXT = (PFN_vkDebugMarkerSetObjectTagEXT)gdpa(dev, "vkDebugMarkerSetObjectTagEXT"); table->DebugMarkerSetObjectNameEXT = (PFN_vkDebugMarkerSetObjectNameEXT)gdpa(dev, "vkDebugMarkerSetObjectNameEXT"); @@ -1282,6 +1285,10 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_extension_dispatch_table(struct lo // ---- VK_QCOM_tile_memory_heap extension commands table->CmdBindTileMemoryQCOM = (PFN_vkCmdBindTileMemoryQCOM)gdpa(dev, "vkCmdBindTileMemoryQCOM"); + // ---- VK_EXT_memory_decompression extension commands + table->CmdDecompressMemoryEXT = (PFN_vkCmdDecompressMemoryEXT)gdpa(dev, "vkCmdDecompressMemoryEXT"); + table->CmdDecompressMemoryIndirectCountEXT = (PFN_vkCmdDecompressMemoryIndirectCountEXT)gdpa(dev, "vkCmdDecompressMemoryIndirectCountEXT"); + // ---- VK_NV_external_compute_queue extension commands table->CreateExternalComputeQueueNV = (PFN_vkCreateExternalComputeQueueNV)gdpa(dev, "vkCreateExternalComputeQueueNV"); table->DestroyExternalComputeQueueNV = (PFN_vkDestroyExternalComputeQueueNV)gdpa(dev, "vkDestroyExternalComputeQueueNV"); @@ -1306,6 +1313,17 @@ VKAPI_ATTR void VKAPI_CALL loader_init_device_extension_dispatch_table(struct lo table->UpdateIndirectExecutionSetPipelineEXT = (PFN_vkUpdateIndirectExecutionSetPipelineEXT)gdpa(dev, "vkUpdateIndirectExecutionSetPipelineEXT"); table->UpdateIndirectExecutionSetShaderEXT = (PFN_vkUpdateIndirectExecutionSetShaderEXT)gdpa(dev, "vkUpdateIndirectExecutionSetShaderEXT"); + // ---- VK_OHOS_native_buffer extension commands +#if defined(VK_USE_PLATFORM_OHOS) + table->GetSwapchainGrallocUsageOHOS = (PFN_vkGetSwapchainGrallocUsageOHOS)gdpa(dev, "vkGetSwapchainGrallocUsageOHOS"); +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + table->AcquireImageOHOS = (PFN_vkAcquireImageOHOS)gdpa(dev, "vkAcquireImageOHOS"); +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + table->QueueSignalReleaseImageOHOS = (PFN_vkQueueSignalReleaseImageOHOS)gdpa(dev, "vkQueueSignalReleaseImageOHOS"); +#endif // VK_USE_PLATFORM_OHOS + // ---- VK_EXT_external_memory_metal extension commands #if defined(VK_USE_PLATFORM_METAL_EXT) table->GetMemoryMetalHandleEXT = (PFN_vkGetMemoryMetalHandleEXT)gdpa(dev, "vkGetMemoryMetalHandleEXT"); @@ -1810,26 +1828,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->DestroySemaphore; } - if (!strcmp(name, "CreateEvent")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CreateEvent; - } - if (!strcmp(name, "DestroyEvent")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->DestroyEvent; - } - if (!strcmp(name, "GetEventStatus")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->GetEventStatus; - } - if (!strcmp(name, "SetEvent")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->SetEvent; - } - if (!strcmp(name, "ResetEvent")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->ResetEvent; - } if (!strcmp(name, "CreateQueryPool")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CreateQueryPool; @@ -1850,14 +1848,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->DestroyBuffer; } - if (!strcmp(name, "CreateBufferView")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CreateBufferView; - } - if (!strcmp(name, "DestroyBufferView")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->DestroyBufferView; - } if (!strcmp(name, "CreateImage")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CreateImage; @@ -1878,6 +1868,118 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->DestroyImageView; } + if (!strcmp(name, "CreateCommandPool")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CreateCommandPool; + } + if (!strcmp(name, "DestroyCommandPool")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->DestroyCommandPool; + } + if (!strcmp(name, "ResetCommandPool")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->ResetCommandPool; + } + if (!strcmp(name, "AllocateCommandBuffers")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->AllocateCommandBuffers; + } + if (!strcmp(name, "FreeCommandBuffers")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->FreeCommandBuffers; + } + if (!strcmp(name, "BeginCommandBuffer")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->BeginCommandBuffer; + } + if (!strcmp(name, "EndCommandBuffer")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->EndCommandBuffer; + } + if (!strcmp(name, "ResetCommandBuffer")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->ResetCommandBuffer; + } + if (!strcmp(name, "CmdCopyBuffer")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdCopyBuffer; + } + if (!strcmp(name, "CmdCopyImage")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdCopyImage; + } + if (!strcmp(name, "CmdCopyBufferToImage")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdCopyBufferToImage; + } + if (!strcmp(name, "CmdCopyImageToBuffer")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdCopyImageToBuffer; + } + if (!strcmp(name, "CmdUpdateBuffer")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdUpdateBuffer; + } + if (!strcmp(name, "CmdFillBuffer")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdFillBuffer; + } + if (!strcmp(name, "CmdPipelineBarrier")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdPipelineBarrier; + } + if (!strcmp(name, "CmdBeginQuery")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdBeginQuery; + } + if (!strcmp(name, "CmdEndQuery")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdEndQuery; + } + if (!strcmp(name, "CmdResetQueryPool")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdResetQueryPool; + } + if (!strcmp(name, "CmdWriteTimestamp")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdWriteTimestamp; + } + if (!strcmp(name, "CmdCopyQueryPoolResults")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdCopyQueryPoolResults; + } + if (!strcmp(name, "CmdExecuteCommands")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CmdExecuteCommands; + } + if (!strcmp(name, "CreateEvent")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CreateEvent; + } + if (!strcmp(name, "DestroyEvent")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->DestroyEvent; + } + if (!strcmp(name, "GetEventStatus")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->GetEventStatus; + } + if (!strcmp(name, "SetEvent")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->SetEvent; + } + if (!strcmp(name, "ResetEvent")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->ResetEvent; + } + if (!strcmp(name, "CreateBufferView")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->CreateBufferView; + } + if (!strcmp(name, "DestroyBufferView")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->DestroyBufferView; + } if (!strcmp(name, "CreateShaderModule")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CreateShaderModule; @@ -1902,10 +2004,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->MergePipelineCaches; } - if (!strcmp(name, "CreateGraphicsPipelines")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CreateGraphicsPipelines; - } if (!strcmp(name, "CreateComputePipelines")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CreateComputePipelines; @@ -1962,61 +2060,65 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->UpdateDescriptorSets; } - if (!strcmp(name, "CreateFramebuffer")) { + if (!strcmp(name, "CmdBindPipeline")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CreateFramebuffer; + return (void *)table->CmdBindPipeline; } - if (!strcmp(name, "DestroyFramebuffer")) { + if (!strcmp(name, "CmdBindDescriptorSets")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->DestroyFramebuffer; + return (void *)table->CmdBindDescriptorSets; } - if (!strcmp(name, "CreateRenderPass")) { + if (!strcmp(name, "CmdClearColorImage")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CreateRenderPass; + return (void *)table->CmdClearColorImage; } - if (!strcmp(name, "DestroyRenderPass")) { + if (!strcmp(name, "CmdDispatch")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->DestroyRenderPass; + return (void *)table->CmdDispatch; } - if (!strcmp(name, "GetRenderAreaGranularity")) { + if (!strcmp(name, "CmdDispatchIndirect")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->GetRenderAreaGranularity; + return (void *)table->CmdDispatchIndirect; } - if (!strcmp(name, "CreateCommandPool")) { + if (!strcmp(name, "CmdSetEvent")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CreateCommandPool; + return (void *)table->CmdSetEvent; } - if (!strcmp(name, "DestroyCommandPool")) { + if (!strcmp(name, "CmdResetEvent")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->DestroyCommandPool; + return (void *)table->CmdResetEvent; } - if (!strcmp(name, "ResetCommandPool")) { + if (!strcmp(name, "CmdWaitEvents")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->ResetCommandPool; + return (void *)table->CmdWaitEvents; } - if (!strcmp(name, "AllocateCommandBuffers")) { + if (!strcmp(name, "CmdPushConstants")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->AllocateCommandBuffers; + return (void *)table->CmdPushConstants; } - if (!strcmp(name, "FreeCommandBuffers")) { + if (!strcmp(name, "CreateGraphicsPipelines")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->FreeCommandBuffers; + return (void *)table->CreateGraphicsPipelines; } - if (!strcmp(name, "BeginCommandBuffer")) { + if (!strcmp(name, "CreateFramebuffer")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->BeginCommandBuffer; + return (void *)table->CreateFramebuffer; } - if (!strcmp(name, "EndCommandBuffer")) { + if (!strcmp(name, "DestroyFramebuffer")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->EndCommandBuffer; + return (void *)table->DestroyFramebuffer; } - if (!strcmp(name, "ResetCommandBuffer")) { + if (!strcmp(name, "CreateRenderPass")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->ResetCommandBuffer; + return (void *)table->CreateRenderPass; } - if (!strcmp(name, "CmdBindPipeline")) { + if (!strcmp(name, "DestroyRenderPass")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdBindPipeline; + return (void *)table->DestroyRenderPass; + } + if (!strcmp(name, "GetRenderAreaGranularity")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; + return (void *)table->GetRenderAreaGranularity; } if (!strcmp(name, "CmdSetViewport")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; @@ -2054,10 +2156,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CmdSetStencilReference; } - if (!strcmp(name, "CmdBindDescriptorSets")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdBindDescriptorSets; - } if (!strcmp(name, "CmdBindIndexBuffer")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CmdBindIndexBuffer; @@ -2082,46 +2180,10 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CmdDrawIndexedIndirect; } - if (!strcmp(name, "CmdDispatch")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdDispatch; - } - if (!strcmp(name, "CmdDispatchIndirect")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdDispatchIndirect; - } - if (!strcmp(name, "CmdCopyBuffer")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdCopyBuffer; - } - if (!strcmp(name, "CmdCopyImage")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdCopyImage; - } if (!strcmp(name, "CmdBlitImage")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CmdBlitImage; } - if (!strcmp(name, "CmdCopyBufferToImage")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdCopyBufferToImage; - } - if (!strcmp(name, "CmdCopyImageToBuffer")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdCopyImageToBuffer; - } - if (!strcmp(name, "CmdUpdateBuffer")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdUpdateBuffer; - } - if (!strcmp(name, "CmdFillBuffer")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdFillBuffer; - } - if (!strcmp(name, "CmdClearColorImage")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdClearColorImage; - } if (!strcmp(name, "CmdClearDepthStencilImage")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CmdClearDepthStencilImage; @@ -2134,46 +2196,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CmdResolveImage; } - if (!strcmp(name, "CmdSetEvent")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdSetEvent; - } - if (!strcmp(name, "CmdResetEvent")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdResetEvent; - } - if (!strcmp(name, "CmdWaitEvents")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdWaitEvents; - } - if (!strcmp(name, "CmdPipelineBarrier")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdPipelineBarrier; - } - if (!strcmp(name, "CmdBeginQuery")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdBeginQuery; - } - if (!strcmp(name, "CmdEndQuery")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdEndQuery; - } - if (!strcmp(name, "CmdResetQueryPool")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdResetQueryPool; - } - if (!strcmp(name, "CmdWriteTimestamp")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdWriteTimestamp; - } - if (!strcmp(name, "CmdCopyQueryPoolResults")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdCopyQueryPoolResults; - } - if (!strcmp(name, "CmdPushConstants")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdPushConstants; - } if (!strcmp(name, "CmdBeginRenderPass")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CmdBeginRenderPass; @@ -2186,10 +2208,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; return (void *)table->CmdEndRenderPass; } - if (!strcmp(name, "CmdExecuteCommands")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_0) return NULL; - return (void *)table->CmdExecuteCommands; - } // ---- Core Vulkan 1.1 commands if (!strcmp(name, "BindBufferMemory2")) { @@ -2208,10 +2226,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; return (void *)table->CmdSetDeviceMask; } - if (!strcmp(name, "CmdDispatchBase")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; - return (void *)table->CmdDispatchBase; - } if (!strcmp(name, "GetImageMemoryRequirements2")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; return (void *)table->GetImageMemoryRequirements2; @@ -2232,13 +2246,9 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; return (void *)table->GetDeviceQueue2; } - if (!strcmp(name, "CreateSamplerYcbcrConversion")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; - return (void *)table->CreateSamplerYcbcrConversion; - } - if (!strcmp(name, "DestroySamplerYcbcrConversion")) { + if (!strcmp(name, "CmdDispatchBase")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; - return (void *)table->DestroySamplerYcbcrConversion; + return (void *)table->CmdDispatchBase; } if (!strcmp(name, "CreateDescriptorUpdateTemplate")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; @@ -2250,38 +2260,22 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis } if (!strcmp(name, "UpdateDescriptorSetWithTemplate")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; - return (void *)table->UpdateDescriptorSetWithTemplate; + return (void *)table->UpdateDescriptorSetWithTemplate; + } + if (!strcmp(name, "GetDescriptorSetLayoutSupport")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; + return (void *)table->GetDescriptorSetLayoutSupport; + } + if (!strcmp(name, "CreateSamplerYcbcrConversion")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; + return (void *)table->CreateSamplerYcbcrConversion; } - if (!strcmp(name, "GetDescriptorSetLayoutSupport")) { + if (!strcmp(name, "DestroySamplerYcbcrConversion")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_1) return NULL; - return (void *)table->GetDescriptorSetLayoutSupport; + return (void *)table->DestroySamplerYcbcrConversion; } // ---- Core Vulkan 1.2 commands - if (!strcmp(name, "CmdDrawIndirectCount")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; - return (void *)table->CmdDrawIndirectCount; - } - if (!strcmp(name, "CmdDrawIndexedIndirectCount")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; - return (void *)table->CmdDrawIndexedIndirectCount; - } - if (!strcmp(name, "CreateRenderPass2")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; - return (void *)table->CreateRenderPass2; - } - if (!strcmp(name, "CmdBeginRenderPass2")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; - return (void *)table->CmdBeginRenderPass2; - } - if (!strcmp(name, "CmdNextSubpass2")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; - return (void *)table->CmdNextSubpass2; - } - if (!strcmp(name, "CmdEndRenderPass2")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; - return (void *)table->CmdEndRenderPass2; - } if (!strcmp(name, "ResetQueryPool")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; return (void *)table->ResetQueryPool; @@ -2310,6 +2304,30 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; return (void *)table->GetDeviceMemoryOpaqueCaptureAddress; } + if (!strcmp(name, "CmdDrawIndirectCount")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; + return (void *)table->CmdDrawIndirectCount; + } + if (!strcmp(name, "CmdDrawIndexedIndirectCount")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; + return (void *)table->CmdDrawIndexedIndirectCount; + } + if (!strcmp(name, "CreateRenderPass2")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; + return (void *)table->CreateRenderPass2; + } + if (!strcmp(name, "CmdBeginRenderPass2")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; + return (void *)table->CmdBeginRenderPass2; + } + if (!strcmp(name, "CmdNextSubpass2")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; + return (void *)table->CmdNextSubpass2; + } + if (!strcmp(name, "CmdEndRenderPass2")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_2) return NULL; + return (void *)table->CmdEndRenderPass2; + } // ---- Core Vulkan 1.3 commands if (!strcmp(name, "CreatePrivateDataSlot")) { @@ -2328,18 +2346,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; return (void *)table->GetPrivateData; } - if (!strcmp(name, "CmdSetEvent2")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; - return (void *)table->CmdSetEvent2; - } - if (!strcmp(name, "CmdResetEvent2")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; - return (void *)table->CmdResetEvent2; - } - if (!strcmp(name, "CmdWaitEvents2")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; - return (void *)table->CmdWaitEvents2; - } if (!strcmp(name, "CmdPipelineBarrier2")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; return (void *)table->CmdPipelineBarrier2; @@ -2368,6 +2374,30 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; return (void *)table->CmdCopyImageToBuffer2; } + if (!strcmp(name, "GetDeviceBufferMemoryRequirements")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; + return (void *)table->GetDeviceBufferMemoryRequirements; + } + if (!strcmp(name, "GetDeviceImageMemoryRequirements")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; + return (void *)table->GetDeviceImageMemoryRequirements; + } + if (!strcmp(name, "GetDeviceImageSparseMemoryRequirements")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; + return (void *)table->GetDeviceImageSparseMemoryRequirements; + } + if (!strcmp(name, "CmdSetEvent2")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; + return (void *)table->CmdSetEvent2; + } + if (!strcmp(name, "CmdResetEvent2")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; + return (void *)table->CmdResetEvent2; + } + if (!strcmp(name, "CmdWaitEvents2")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; + return (void *)table->CmdWaitEvents2; + } if (!strcmp(name, "CmdBlitImage2")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; return (void *)table->CmdBlitImage2; @@ -2444,24 +2474,8 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; return (void *)table->CmdSetPrimitiveRestartEnable; } - if (!strcmp(name, "GetDeviceBufferMemoryRequirements")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; - return (void *)table->GetDeviceBufferMemoryRequirements; - } - if (!strcmp(name, "GetDeviceImageMemoryRequirements")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; - return (void *)table->GetDeviceImageMemoryRequirements; - } - if (!strcmp(name, "GetDeviceImageSparseMemoryRequirements")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_3) return NULL; - return (void *)table->GetDeviceImageSparseMemoryRequirements; - } // ---- Core Vulkan 1.4 commands - if (!strcmp(name, "CmdSetLineStipple")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CmdSetLineStipple; - } if (!strcmp(name, "MapMemory2")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; return (void *)table->MapMemory2; @@ -2470,14 +2484,6 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; return (void *)table->UnmapMemory2; } - if (!strcmp(name, "CmdBindIndexBuffer2")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CmdBindIndexBuffer2; - } - if (!strcmp(name, "GetRenderingAreaGranularity")) { - if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->GetRenderingAreaGranularity; - } if (!strcmp(name, "GetDeviceImageSubresourceLayout")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; return (void *)table->GetDeviceImageSubresourceLayout; @@ -2486,21 +2492,29 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; return (void *)table->GetImageSubresourceLayout2; } - if (!strcmp(name, "CmdPushDescriptorSet")) { + if (!strcmp(name, "CopyMemoryToImage")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CmdPushDescriptorSet; + return (void *)table->CopyMemoryToImage; } - if (!strcmp(name, "CmdPushDescriptorSetWithTemplate")) { + if (!strcmp(name, "CopyImageToMemory")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CmdPushDescriptorSetWithTemplate; + return (void *)table->CopyImageToMemory; } - if (!strcmp(name, "CmdSetRenderingAttachmentLocations")) { + if (!strcmp(name, "CopyImageToImage")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CmdSetRenderingAttachmentLocations; + return (void *)table->CopyImageToImage; } - if (!strcmp(name, "CmdSetRenderingInputAttachmentIndices")) { + if (!strcmp(name, "TransitionImageLayout")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CmdSetRenderingInputAttachmentIndices; + return (void *)table->TransitionImageLayout; + } + if (!strcmp(name, "CmdPushDescriptorSet")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; + return (void *)table->CmdPushDescriptorSet; + } + if (!strcmp(name, "CmdPushDescriptorSetWithTemplate")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; + return (void *)table->CmdPushDescriptorSetWithTemplate; } if (!strcmp(name, "CmdBindDescriptorSets2")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; @@ -2518,21 +2532,25 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; return (void *)table->CmdPushDescriptorSetWithTemplate2; } - if (!strcmp(name, "CopyMemoryToImage")) { + if (!strcmp(name, "CmdSetLineStipple")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CopyMemoryToImage; + return (void *)table->CmdSetLineStipple; } - if (!strcmp(name, "CopyImageToMemory")) { + if (!strcmp(name, "CmdBindIndexBuffer2")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CopyImageToMemory; + return (void *)table->CmdBindIndexBuffer2; } - if (!strcmp(name, "CopyImageToImage")) { + if (!strcmp(name, "GetRenderingAreaGranularity")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->CopyImageToImage; + return (void *)table->GetRenderingAreaGranularity; } - if (!strcmp(name, "TransitionImageLayout")) { + if (!strcmp(name, "CmdSetRenderingAttachmentLocations")) { if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; - return (void *)table->TransitionImageLayout; + return (void *)table->CmdSetRenderingAttachmentLocations; + } + if (!strcmp(name, "CmdSetRenderingInputAttachmentIndices")) { + if (dev->should_ignore_device_commands_from_newer_version && api_version < VK_API_VERSION_1_4) return NULL; + return (void *)table->CmdSetRenderingInputAttachmentIndices; } // ---- VK_KHR_swapchain extension commands @@ -2754,6 +2772,9 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (!strcmp(name, "CmdCopyMemoryIndirectKHR")) return (void *)table->CmdCopyMemoryIndirectKHR; if (!strcmp(name, "CmdCopyMemoryToImageIndirectKHR")) return (void *)table->CmdCopyMemoryToImageIndirectKHR; + // ---- VK_KHR_maintenance10 extension commands + if (!strcmp(name, "CmdEndRendering2KHR")) return (void *)table->CmdEndRendering2KHR; + // ---- VK_EXT_debug_marker extension commands if (!strcmp(name, "DebugMarkerSetObjectTagEXT")) return dev->layer_extensions.ext_debug_marker_enabled ? (void *)DebugMarkerSetObjectTagEXT : NULL; if (!strcmp(name, "DebugMarkerSetObjectNameEXT")) return dev->layer_extensions.ext_debug_marker_enabled ? (void *)DebugMarkerSetObjectNameEXT : NULL; @@ -3248,6 +3269,10 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis // ---- VK_QCOM_tile_memory_heap extension commands if (!strcmp(name, "CmdBindTileMemoryQCOM")) return (void *)table->CmdBindTileMemoryQCOM; + // ---- VK_EXT_memory_decompression extension commands + if (!strcmp(name, "CmdDecompressMemoryEXT")) return (void *)table->CmdDecompressMemoryEXT; + if (!strcmp(name, "CmdDecompressMemoryIndirectCountEXT")) return (void *)table->CmdDecompressMemoryIndirectCountEXT; + // ---- VK_NV_external_compute_queue extension commands if (!strcmp(name, "CreateExternalComputeQueueNV")) return (void *)table->CreateExternalComputeQueueNV; if (!strcmp(name, "DestroyExternalComputeQueueNV")) return (void *)table->DestroyExternalComputeQueueNV; @@ -3272,6 +3297,17 @@ VKAPI_ATTR void* VKAPI_CALL loader_lookup_device_dispatch_table(const VkLayerDis if (!strcmp(name, "UpdateIndirectExecutionSetPipelineEXT")) return (void *)table->UpdateIndirectExecutionSetPipelineEXT; if (!strcmp(name, "UpdateIndirectExecutionSetShaderEXT")) return (void *)table->UpdateIndirectExecutionSetShaderEXT; + // ---- VK_OHOS_native_buffer extension commands +#if defined(VK_USE_PLATFORM_OHOS) + if (!strcmp(name, "GetSwapchainGrallocUsageOHOS")) return (void *)table->GetSwapchainGrallocUsageOHOS; +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + if (!strcmp(name, "AcquireImageOHOS")) return (void *)table->AcquireImageOHOS; +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + if (!strcmp(name, "QueueSignalReleaseImageOHOS")) return (void *)table->QueueSignalReleaseImageOHOS; +#endif // VK_USE_PLATFORM_OHOS + // ---- VK_EXT_external_memory_metal extension commands #if defined(VK_USE_PLATFORM_METAL_EXT) if (!strcmp(name, "GetMemoryMetalHandleEXT")) return (void *)table->GetMemoryMetalHandleEXT; @@ -5530,6 +5566,22 @@ VKAPI_ATTR void VKAPI_CALL CmdCopyMemoryToImageIndirectKHR( } +// ---- VK_KHR_maintenance10 extension trampoline/terminators + +VKAPI_ATTR void VKAPI_CALL CmdEndRendering2KHR( + VkCommandBuffer commandBuffer, + const VkRenderingEndInfoKHR* pRenderingEndInfo) { + const VkLayerDispatchTable *disp = loader_get_dispatch(commandBuffer); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkCmdEndRendering2KHR: Invalid commandBuffer " + "[VUID-vkCmdEndRendering2KHR-commandBuffer-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + disp->CmdEndRendering2KHR(commandBuffer, pRenderingEndInfo); +} + + // ---- VK_EXT_debug_marker extension trampoline/terminators VKAPI_ATTR VkResult VKAPI_CALL DebugMarkerSetObjectTagEXT( @@ -10462,6 +10514,39 @@ VKAPI_ATTR void VKAPI_CALL CmdBindTileMemoryQCOM( } +// ---- VK_EXT_memory_decompression extension trampoline/terminators + +VKAPI_ATTR void VKAPI_CALL CmdDecompressMemoryEXT( + VkCommandBuffer commandBuffer, + const VkDecompressMemoryInfoEXT* pDecompressMemoryInfoEXT) { + const VkLayerDispatchTable *disp = loader_get_dispatch(commandBuffer); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkCmdDecompressMemoryEXT: Invalid commandBuffer " + "[VUID-vkCmdDecompressMemoryEXT-commandBuffer-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + disp->CmdDecompressMemoryEXT(commandBuffer, pDecompressMemoryInfoEXT); +} + +VKAPI_ATTR void VKAPI_CALL CmdDecompressMemoryIndirectCountEXT( + VkCommandBuffer commandBuffer, + VkMemoryDecompressionMethodFlagsEXT decompressionMethod, + VkDeviceAddress indirectCommandsAddress, + VkDeviceAddress indirectCommandsCountAddress, + uint32_t maxDecompressionCount, + uint32_t stride) { + const VkLayerDispatchTable *disp = loader_get_dispatch(commandBuffer); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkCmdDecompressMemoryIndirectCountEXT: Invalid commandBuffer " + "[VUID-vkCmdDecompressMemoryIndirectCountEXT-commandBuffer-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + disp->CmdDecompressMemoryIndirectCountEXT(commandBuffer, decompressionMethod, indirectCommandsAddress, indirectCommandsCountAddress, maxDecompressionCount, stride); +} + + // ---- VK_NV_external_compute_queue extension trampoline/terminators VKAPI_ATTR VkResult VKAPI_CALL CreateExternalComputeQueueNV( @@ -10737,6 +10822,62 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateSurfaceOHOS( #endif // VK_USE_PLATFORM_OHOS +// ---- VK_OHOS_native_buffer extension trampoline/terminators + +#if defined(VK_USE_PLATFORM_OHOS) +VKAPI_ATTR VkResult VKAPI_CALL GetSwapchainGrallocUsageOHOS( + VkDevice device, + VkFormat format, + VkImageUsageFlags imageUsage, + uint64_t* grallocUsage) { + const VkLayerDispatchTable *disp = loader_get_dispatch(device); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkGetSwapchainGrallocUsageOHOS: Invalid device " + "[VUID-vkGetSwapchainGrallocUsageOHOS-device-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + return disp->GetSwapchainGrallocUsageOHOS(device, format, imageUsage, grallocUsage); +} + +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) +VKAPI_ATTR VkResult VKAPI_CALL AcquireImageOHOS( + VkDevice device, + VkImage image, + int32_t nativeFenceFd, + VkSemaphore semaphore, + VkFence fence) { + const VkLayerDispatchTable *disp = loader_get_dispatch(device); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkAcquireImageOHOS: Invalid device " + "[VUID-vkAcquireImageOHOS-device-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + return disp->AcquireImageOHOS(device, image, nativeFenceFd, semaphore, fence); +} + +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) +VKAPI_ATTR VkResult VKAPI_CALL QueueSignalReleaseImageOHOS( + VkQueue queue, + uint32_t waitSemaphoreCount, + const VkSemaphore* pWaitSemaphores, + VkImage image, + int32_t* pNativeFenceFd) { + const VkLayerDispatchTable *disp = loader_get_dispatch(queue); + if (NULL == disp) { + loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, + "vkQueueSignalReleaseImageOHOS: Invalid queue " + "[VUID-vkQueueSignalReleaseImageOHOS-queue-parameter]"); + abort(); /* Intentionally fail so user can correct issue. */ + } + return disp->QueueSignalReleaseImageOHOS(queue, waitSemaphoreCount, pWaitSemaphores, image, pNativeFenceFd); +} + +#endif // VK_USE_PLATFORM_OHOS + // ---- VK_NV_cooperative_matrix2 extension trampoline/terminators VKAPI_ATTR VkResult VKAPI_CALL GetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV( @@ -10810,7 +10951,7 @@ VKAPI_ATTR VkResult VKAPI_CALL GetMemoryMetalHandlePropertiesEXT( VKAPI_ATTR void VKAPI_CALL CmdEndRendering2EXT( VkCommandBuffer commandBuffer, - const VkRenderingEndInfoEXT* pRenderingEndInfo) { + const VkRenderingEndInfoKHR* pRenderingEndInfo) { const VkLayerDispatchTable *disp = loader_get_dispatch(commandBuffer); if (NULL == disp) { loader_log(NULL, VULKAN_LOADER_FATAL_ERROR_BIT | VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_VALIDATION_BIT, 0, @@ -11865,6 +12006,12 @@ bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *na return true; } + // ---- VK_KHR_maintenance10 extension commands + if (!strcmp("vkCmdEndRendering2KHR", name)) { + *addr = (void *)CmdEndRendering2KHR; + return true; + } + // ---- VK_EXT_debug_marker extension commands if (!strcmp("vkDebugMarkerSetObjectTagEXT", name)) { *addr = (void *)DebugMarkerSetObjectTagEXT; @@ -13331,6 +13478,16 @@ bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *na return true; } + // ---- VK_EXT_memory_decompression extension commands + if (!strcmp("vkCmdDecompressMemoryEXT", name)) { + *addr = (void *)CmdDecompressMemoryEXT; + return true; + } + if (!strcmp("vkCmdDecompressMemoryIndirectCountEXT", name)) { + *addr = (void *)CmdDecompressMemoryIndirectCountEXT; + return true; + } + // ---- VK_NV_external_compute_queue extension commands if (!strcmp("vkCreateExternalComputeQueueNV", name)) { *addr = (void *)CreateExternalComputeQueueNV; @@ -13413,6 +13570,26 @@ bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *na } #endif // VK_USE_PLATFORM_OHOS + // ---- VK_OHOS_native_buffer extension commands +#if defined(VK_USE_PLATFORM_OHOS) + if (!strcmp("vkGetSwapchainGrallocUsageOHOS", name)) { + *addr = (void *)GetSwapchainGrallocUsageOHOS; + return true; + } +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + if (!strcmp("vkAcquireImageOHOS", name)) { + *addr = (void *)AcquireImageOHOS; + return true; + } +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + if (!strcmp("vkQueueSignalReleaseImageOHOS", name)) { + *addr = (void *)QueueSignalReleaseImageOHOS; + return true; + } +#endif // VK_USE_PLATFORM_OHOS + // ---- VK_NV_cooperative_matrix2 extension commands if (!strcmp("vkGetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV", name)) { *addr = (void *)GetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV; diff --git a/loader/generated/vk_object_types.h b/loader/generated/vk_object_types.h index 30a5bb10c..4d5c2e4eb 100644 --- a/loader/generated/vk_object_types.h +++ b/loader/generated/vk_object_types.h @@ -49,23 +49,23 @@ typedef enum VulkanObjectType { kVulkanObjectTypeCommandBuffer = 8, kVulkanObjectTypeFence = 9, kVulkanObjectTypeDeviceMemory = 10, - kVulkanObjectTypeEvent = 11, - kVulkanObjectTypeQueryPool = 12, - kVulkanObjectTypeBufferView = 13, - kVulkanObjectTypeImageView = 14, - kVulkanObjectTypeShaderModule = 15, - kVulkanObjectTypePipelineCache = 16, - kVulkanObjectTypePipelineLayout = 17, - kVulkanObjectTypePipeline = 18, - kVulkanObjectTypeRenderPass = 19, - kVulkanObjectTypeDescriptorSetLayout = 20, - kVulkanObjectTypeSampler = 21, - kVulkanObjectTypeDescriptorSet = 22, - kVulkanObjectTypeDescriptorPool = 23, - kVulkanObjectTypeFramebuffer = 24, - kVulkanObjectTypeCommandPool = 25, - kVulkanObjectTypeSamplerYcbcrConversion = 26, - kVulkanObjectTypeDescriptorUpdateTemplate = 27, + kVulkanObjectTypeQueryPool = 11, + kVulkanObjectTypeImageView = 12, + kVulkanObjectTypeCommandPool = 13, + kVulkanObjectTypeRenderPass = 14, + kVulkanObjectTypeFramebuffer = 15, + kVulkanObjectTypeEvent = 16, + kVulkanObjectTypeBufferView = 17, + kVulkanObjectTypeShaderModule = 18, + kVulkanObjectTypePipelineCache = 19, + kVulkanObjectTypePipelineLayout = 20, + kVulkanObjectTypePipeline = 21, + kVulkanObjectTypeDescriptorSetLayout = 22, + kVulkanObjectTypeSampler = 23, + kVulkanObjectTypeDescriptorSet = 24, + kVulkanObjectTypeDescriptorPool = 25, + kVulkanObjectTypeDescriptorUpdateTemplate = 26, + kVulkanObjectTypeSamplerYcbcrConversion = 27, kVulkanObjectTypePrivateDataSlot = 28, kVulkanObjectTypeSurfaceKHR = 29, kVulkanObjectTypeSwapchainKHR = 30, @@ -98,8 +98,8 @@ typedef enum VulkanObjectType { kVulkanObjectTypeIndirectCommandsLayoutEXT = 57, kVulkanObjectTypeMax = 58, // Aliases for backwards compatibility of "promoted" types - kVulkanObjectTypeSamplerYcbcrConversionKHR = kVulkanObjectTypeSamplerYcbcrConversion, kVulkanObjectTypeDescriptorUpdateTemplateKHR = kVulkanObjectTypeDescriptorUpdateTemplate, + kVulkanObjectTypeSamplerYcbcrConversionKHR = kVulkanObjectTypeSamplerYcbcrConversion, kVulkanObjectTypePrivateDataSlotEXT = kVulkanObjectTypePrivateDataSlot, } VulkanObjectType; @@ -116,23 +116,23 @@ static const char * const object_string[kVulkanObjectTypeMax] = { "CommandBuffer", "Fence", "DeviceMemory", - "Event", "QueryPool", - "BufferView", "ImageView", + "CommandPool", + "RenderPass", + "Framebuffer", + "Event", + "BufferView", "ShaderModule", "PipelineCache", "PipelineLayout", "Pipeline", - "RenderPass", "DescriptorSetLayout", "Sampler", "DescriptorSet", "DescriptorPool", - "Framebuffer", - "CommandPool", - "SamplerYcbcrConversion", "DescriptorUpdateTemplate", + "SamplerYcbcrConversion", "PrivateDataSlot", "SurfaceKHR", "SwapchainKHR", @@ -178,23 +178,23 @@ const VkDebugReportObjectTypeEXT get_debug_report_enum[] = { VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, // kVulkanObjectTypeCommandBuffer VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT, // kVulkanObjectTypeFence VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, // kVulkanObjectTypeDeviceMemory - VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT, // kVulkanObjectTypeEvent VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT, // kVulkanObjectTypeQueryPool - VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT, // kVulkanObjectTypeBufferView VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT, // kVulkanObjectTypeImageView + VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT, // kVulkanObjectTypeCommandPool + VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT, // kVulkanObjectTypeRenderPass + VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT, // kVulkanObjectTypeFramebuffer + VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT, // kVulkanObjectTypeEvent + VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT, // kVulkanObjectTypeBufferView VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, // kVulkanObjectTypeShaderModule VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT, // kVulkanObjectTypePipelineCache VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT, // kVulkanObjectTypePipelineLayout VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, // kVulkanObjectTypePipeline - VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT, // kVulkanObjectTypeRenderPass VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT, // kVulkanObjectTypeDescriptorSetLayout VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT, // kVulkanObjectTypeSampler VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT, // kVulkanObjectTypeDescriptorSet VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT, // kVulkanObjectTypeDescriptorPool - VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT, // kVulkanObjectTypeFramebuffer - VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT, // kVulkanObjectTypeCommandPool - VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT, // kVulkanObjectTypeSamplerYcbcrConversion VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT, // kVulkanObjectTypeDescriptorUpdateTemplate + VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT, // kVulkanObjectTypeSamplerYcbcrConversion VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, // kVulkanObjectTypePrivateDataSlot VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT, // kVulkanObjectTypeSurfaceKHR VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT, // kVulkanObjectTypeSwapchainKHR @@ -240,23 +240,23 @@ const VkObjectType get_object_type_enum[] = { VK_OBJECT_TYPE_COMMAND_BUFFER, // kVulkanObjectTypeCommandBuffer VK_OBJECT_TYPE_FENCE, // kVulkanObjectTypeFence VK_OBJECT_TYPE_DEVICE_MEMORY, // kVulkanObjectTypeDeviceMemory - VK_OBJECT_TYPE_EVENT, // kVulkanObjectTypeEvent VK_OBJECT_TYPE_QUERY_POOL, // kVulkanObjectTypeQueryPool - VK_OBJECT_TYPE_BUFFER_VIEW, // kVulkanObjectTypeBufferView VK_OBJECT_TYPE_IMAGE_VIEW, // kVulkanObjectTypeImageView + VK_OBJECT_TYPE_COMMAND_POOL, // kVulkanObjectTypeCommandPool + VK_OBJECT_TYPE_RENDER_PASS, // kVulkanObjectTypeRenderPass + VK_OBJECT_TYPE_FRAMEBUFFER, // kVulkanObjectTypeFramebuffer + VK_OBJECT_TYPE_EVENT, // kVulkanObjectTypeEvent + VK_OBJECT_TYPE_BUFFER_VIEW, // kVulkanObjectTypeBufferView VK_OBJECT_TYPE_SHADER_MODULE, // kVulkanObjectTypeShaderModule VK_OBJECT_TYPE_PIPELINE_CACHE, // kVulkanObjectTypePipelineCache VK_OBJECT_TYPE_PIPELINE_LAYOUT, // kVulkanObjectTypePipelineLayout VK_OBJECT_TYPE_PIPELINE, // kVulkanObjectTypePipeline - VK_OBJECT_TYPE_RENDER_PASS, // kVulkanObjectTypeRenderPass VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, // kVulkanObjectTypeDescriptorSetLayout VK_OBJECT_TYPE_SAMPLER, // kVulkanObjectTypeSampler VK_OBJECT_TYPE_DESCRIPTOR_SET, // kVulkanObjectTypeDescriptorSet VK_OBJECT_TYPE_DESCRIPTOR_POOL, // kVulkanObjectTypeDescriptorPool - VK_OBJECT_TYPE_FRAMEBUFFER, // kVulkanObjectTypeFramebuffer - VK_OBJECT_TYPE_COMMAND_POOL, // kVulkanObjectTypeCommandPool - VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION, // kVulkanObjectTypeSamplerYcbcrConversion VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE, // kVulkanObjectTypeDescriptorUpdateTemplate + VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION, // kVulkanObjectTypeSamplerYcbcrConversion VK_OBJECT_TYPE_PRIVATE_DATA_SLOT, // kVulkanObjectTypePrivateDataSlot VK_OBJECT_TYPE_SURFACE_KHR, // kVulkanObjectTypeSurfaceKHR VK_OBJECT_TYPE_SWAPCHAIN_KHR, // kVulkanObjectTypeSwapchainKHR @@ -345,10 +345,10 @@ static inline VkObjectType convertDebugReportObjectToCoreObject(VkDebugReportObj return VK_OBJECT_TYPE_FRAMEBUFFER; } else if (debug_report_obj == VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT) { return VK_OBJECT_TYPE_COMMAND_POOL; - } else if (debug_report_obj == VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT) { - return VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION; } else if (debug_report_obj == VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT) { return VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE; + } else if (debug_report_obj == VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT) { + return VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION; } else if (debug_report_obj == VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT) { return VK_OBJECT_TYPE_SURFACE_KHR; } else if (debug_report_obj == VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT) { @@ -435,10 +435,10 @@ static inline VkDebugReportObjectTypeEXT convertCoreObjectToDebugReportObject(Vk return VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT; } else if (core_report_obj == VK_OBJECT_TYPE_COMMAND_POOL) { return VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT; - } else if (core_report_obj == VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION) { - return VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT; } else if (core_report_obj == VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE) { return VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT; + } else if (core_report_obj == VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION) { + return VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT; } else if (core_report_obj == VK_OBJECT_TYPE_SURFACE_KHR) { return VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT; } else if (core_report_obj == VK_OBJECT_TYPE_SWAPCHAIN_KHR) { diff --git a/loader/loader.rc b/loader/loader.rc index af35e1337..2e5b60202 100644 --- a/loader/loader.rc +++ b/loader/loader.rc @@ -22,8 +22,8 @@ #include "winres.h" // All set through CMake -#define VER_FILE_VERSION 1, 4, 329, 0 -#define VER_FILE_DESCRIPTION_STR "1.4.329.Dev Build" +#define VER_FILE_VERSION 1, 4, 330, 0 +#define VER_FILE_DESCRIPTION_STR "1.4.330.Dev Build" #define VER_FILE_VERSION_STR "Vulkan Loader - Dev Build" #define VER_COPYRIGHT_STR "Copyright (C) 2015-2025" diff --git a/scripts/known_good.json b/scripts/known_good.json index 7052689f5..5d06e151e 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -7,7 +7,7 @@ "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build", "install_dir": "Vulkan-Headers/build/install", - "commit": "v1.4.329" + "commit": "v1.4.330" }, { "name": "googletest", diff --git a/tests/framework/generated/vk_result_to_string_helper.h b/tests/framework/generated/vk_result_to_string_helper.h index 88dfa6a46..de942aaa3 100644 --- a/tests/framework/generated/vk_result_to_string_helper.h +++ b/tests/framework/generated/vk_result_to_string_helper.h @@ -73,10 +73,10 @@ inline std::ostream& operator<<(std::ostream& os, const VkResult& result) { return os << "VK_ERROR_OUT_OF_POOL_MEMORY"; case (VK_ERROR_INVALID_EXTERNAL_HANDLE): return os << "VK_ERROR_INVALID_EXTERNAL_HANDLE"; - case (VK_ERROR_FRAGMENTATION): - return os << "VK_ERROR_FRAGMENTATION"; case (VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS): return os << "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; + case (VK_ERROR_FRAGMENTATION): + return os << "VK_ERROR_FRAGMENTATION"; case (VK_PIPELINE_COMPILE_REQUIRED): return os << "VK_PIPELINE_COMPILE_REQUIRED"; case (VK_ERROR_NOT_PERMITTED): diff --git a/tests/framework/layer/generated/vk_dispatch_table_helper.h b/tests/framework/layer/generated/vk_dispatch_table_helper.h index a07d8ece7..41535dac5 100644 --- a/tests/framework/layer/generated/vk_dispatch_table_helper.h +++ b/tests/framework/layer/generated/vk_dispatch_table_helper.h @@ -62,30 +62,50 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->WaitForFences = (PFN_vkWaitForFences)gpa(device, "vkWaitForFences"); table->CreateSemaphore = (PFN_vkCreateSemaphore)gpa(device, "vkCreateSemaphore"); table->DestroySemaphore = (PFN_vkDestroySemaphore)gpa(device, "vkDestroySemaphore"); - table->CreateEvent = (PFN_vkCreateEvent)gpa(device, "vkCreateEvent"); - table->DestroyEvent = (PFN_vkDestroyEvent)gpa(device, "vkDestroyEvent"); - table->GetEventStatus = (PFN_vkGetEventStatus)gpa(device, "vkGetEventStatus"); - table->SetEvent = (PFN_vkSetEvent)gpa(device, "vkSetEvent"); - table->ResetEvent = (PFN_vkResetEvent)gpa(device, "vkResetEvent"); table->CreateQueryPool = (PFN_vkCreateQueryPool)gpa(device, "vkCreateQueryPool"); table->DestroyQueryPool = (PFN_vkDestroyQueryPool)gpa(device, "vkDestroyQueryPool"); table->GetQueryPoolResults = (PFN_vkGetQueryPoolResults)gpa(device, "vkGetQueryPoolResults"); table->CreateBuffer = (PFN_vkCreateBuffer)gpa(device, "vkCreateBuffer"); table->DestroyBuffer = (PFN_vkDestroyBuffer)gpa(device, "vkDestroyBuffer"); - table->CreateBufferView = (PFN_vkCreateBufferView)gpa(device, "vkCreateBufferView"); - table->DestroyBufferView = (PFN_vkDestroyBufferView)gpa(device, "vkDestroyBufferView"); table->CreateImage = (PFN_vkCreateImage)gpa(device, "vkCreateImage"); table->DestroyImage = (PFN_vkDestroyImage)gpa(device, "vkDestroyImage"); table->GetImageSubresourceLayout = (PFN_vkGetImageSubresourceLayout)gpa(device, "vkGetImageSubresourceLayout"); table->CreateImageView = (PFN_vkCreateImageView)gpa(device, "vkCreateImageView"); table->DestroyImageView = (PFN_vkDestroyImageView)gpa(device, "vkDestroyImageView"); + table->CreateCommandPool = (PFN_vkCreateCommandPool)gpa(device, "vkCreateCommandPool"); + table->DestroyCommandPool = (PFN_vkDestroyCommandPool)gpa(device, "vkDestroyCommandPool"); + table->ResetCommandPool = (PFN_vkResetCommandPool)gpa(device, "vkResetCommandPool"); + table->AllocateCommandBuffers = (PFN_vkAllocateCommandBuffers)gpa(device, "vkAllocateCommandBuffers"); + table->FreeCommandBuffers = (PFN_vkFreeCommandBuffers)gpa(device, "vkFreeCommandBuffers"); + table->BeginCommandBuffer = (PFN_vkBeginCommandBuffer)gpa(device, "vkBeginCommandBuffer"); + table->EndCommandBuffer = (PFN_vkEndCommandBuffer)gpa(device, "vkEndCommandBuffer"); + table->ResetCommandBuffer = (PFN_vkResetCommandBuffer)gpa(device, "vkResetCommandBuffer"); + table->CmdCopyBuffer = (PFN_vkCmdCopyBuffer)gpa(device, "vkCmdCopyBuffer"); + table->CmdCopyImage = (PFN_vkCmdCopyImage)gpa(device, "vkCmdCopyImage"); + table->CmdCopyBufferToImage = (PFN_vkCmdCopyBufferToImage)gpa(device, "vkCmdCopyBufferToImage"); + table->CmdCopyImageToBuffer = (PFN_vkCmdCopyImageToBuffer)gpa(device, "vkCmdCopyImageToBuffer"); + table->CmdUpdateBuffer = (PFN_vkCmdUpdateBuffer)gpa(device, "vkCmdUpdateBuffer"); + table->CmdFillBuffer = (PFN_vkCmdFillBuffer)gpa(device, "vkCmdFillBuffer"); + table->CmdPipelineBarrier = (PFN_vkCmdPipelineBarrier)gpa(device, "vkCmdPipelineBarrier"); + table->CmdBeginQuery = (PFN_vkCmdBeginQuery)gpa(device, "vkCmdBeginQuery"); + table->CmdEndQuery = (PFN_vkCmdEndQuery)gpa(device, "vkCmdEndQuery"); + table->CmdResetQueryPool = (PFN_vkCmdResetQueryPool)gpa(device, "vkCmdResetQueryPool"); + table->CmdWriteTimestamp = (PFN_vkCmdWriteTimestamp)gpa(device, "vkCmdWriteTimestamp"); + table->CmdCopyQueryPoolResults = (PFN_vkCmdCopyQueryPoolResults)gpa(device, "vkCmdCopyQueryPoolResults"); + table->CmdExecuteCommands = (PFN_vkCmdExecuteCommands)gpa(device, "vkCmdExecuteCommands"); + table->CreateEvent = (PFN_vkCreateEvent)gpa(device, "vkCreateEvent"); + table->DestroyEvent = (PFN_vkDestroyEvent)gpa(device, "vkDestroyEvent"); + table->GetEventStatus = (PFN_vkGetEventStatus)gpa(device, "vkGetEventStatus"); + table->SetEvent = (PFN_vkSetEvent)gpa(device, "vkSetEvent"); + table->ResetEvent = (PFN_vkResetEvent)gpa(device, "vkResetEvent"); + table->CreateBufferView = (PFN_vkCreateBufferView)gpa(device, "vkCreateBufferView"); + table->DestroyBufferView = (PFN_vkDestroyBufferView)gpa(device, "vkDestroyBufferView"); table->CreateShaderModule = (PFN_vkCreateShaderModule)gpa(device, "vkCreateShaderModule"); table->DestroyShaderModule = (PFN_vkDestroyShaderModule)gpa(device, "vkDestroyShaderModule"); table->CreatePipelineCache = (PFN_vkCreatePipelineCache)gpa(device, "vkCreatePipelineCache"); table->DestroyPipelineCache = (PFN_vkDestroyPipelineCache)gpa(device, "vkDestroyPipelineCache"); table->GetPipelineCacheData = (PFN_vkGetPipelineCacheData)gpa(device, "vkGetPipelineCacheData"); table->MergePipelineCaches = (PFN_vkMergePipelineCaches)gpa(device, "vkMergePipelineCaches"); - table->CreateGraphicsPipelines = (PFN_vkCreateGraphicsPipelines)gpa(device, "vkCreateGraphicsPipelines"); table->CreateComputePipelines = (PFN_vkCreateComputePipelines)gpa(device, "vkCreateComputePipelines"); table->DestroyPipeline = (PFN_vkDestroyPipeline)gpa(device, "vkDestroyPipeline"); table->CreatePipelineLayout = (PFN_vkCreatePipelineLayout)gpa(device, "vkCreatePipelineLayout"); @@ -100,20 +120,21 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->AllocateDescriptorSets = (PFN_vkAllocateDescriptorSets)gpa(device, "vkAllocateDescriptorSets"); table->FreeDescriptorSets = (PFN_vkFreeDescriptorSets)gpa(device, "vkFreeDescriptorSets"); table->UpdateDescriptorSets = (PFN_vkUpdateDescriptorSets)gpa(device, "vkUpdateDescriptorSets"); + table->CmdBindPipeline = (PFN_vkCmdBindPipeline)gpa(device, "vkCmdBindPipeline"); + table->CmdBindDescriptorSets = (PFN_vkCmdBindDescriptorSets)gpa(device, "vkCmdBindDescriptorSets"); + table->CmdClearColorImage = (PFN_vkCmdClearColorImage)gpa(device, "vkCmdClearColorImage"); + table->CmdDispatch = (PFN_vkCmdDispatch)gpa(device, "vkCmdDispatch"); + table->CmdDispatchIndirect = (PFN_vkCmdDispatchIndirect)gpa(device, "vkCmdDispatchIndirect"); + table->CmdSetEvent = (PFN_vkCmdSetEvent)gpa(device, "vkCmdSetEvent"); + table->CmdResetEvent = (PFN_vkCmdResetEvent)gpa(device, "vkCmdResetEvent"); + table->CmdWaitEvents = (PFN_vkCmdWaitEvents)gpa(device, "vkCmdWaitEvents"); + table->CmdPushConstants = (PFN_vkCmdPushConstants)gpa(device, "vkCmdPushConstants"); + table->CreateGraphicsPipelines = (PFN_vkCreateGraphicsPipelines)gpa(device, "vkCreateGraphicsPipelines"); table->CreateFramebuffer = (PFN_vkCreateFramebuffer)gpa(device, "vkCreateFramebuffer"); table->DestroyFramebuffer = (PFN_vkDestroyFramebuffer)gpa(device, "vkDestroyFramebuffer"); table->CreateRenderPass = (PFN_vkCreateRenderPass)gpa(device, "vkCreateRenderPass"); table->DestroyRenderPass = (PFN_vkDestroyRenderPass)gpa(device, "vkDestroyRenderPass"); table->GetRenderAreaGranularity = (PFN_vkGetRenderAreaGranularity)gpa(device, "vkGetRenderAreaGranularity"); - table->CreateCommandPool = (PFN_vkCreateCommandPool)gpa(device, "vkCreateCommandPool"); - table->DestroyCommandPool = (PFN_vkDestroyCommandPool)gpa(device, "vkDestroyCommandPool"); - table->ResetCommandPool = (PFN_vkResetCommandPool)gpa(device, "vkResetCommandPool"); - table->AllocateCommandBuffers = (PFN_vkAllocateCommandBuffers)gpa(device, "vkAllocateCommandBuffers"); - table->FreeCommandBuffers = (PFN_vkFreeCommandBuffers)gpa(device, "vkFreeCommandBuffers"); - table->BeginCommandBuffer = (PFN_vkBeginCommandBuffer)gpa(device, "vkBeginCommandBuffer"); - table->EndCommandBuffer = (PFN_vkEndCommandBuffer)gpa(device, "vkEndCommandBuffer"); - table->ResetCommandBuffer = (PFN_vkResetCommandBuffer)gpa(device, "vkResetCommandBuffer"); - table->CmdBindPipeline = (PFN_vkCmdBindPipeline)gpa(device, "vkCmdBindPipeline"); table->CmdSetViewport = (PFN_vkCmdSetViewport)gpa(device, "vkCmdSetViewport"); table->CmdSetScissor = (PFN_vkCmdSetScissor)gpa(device, "vkCmdSetScissor"); table->CmdSetLineWidth = (PFN_vkCmdSetLineWidth)gpa(device, "vkCmdSetLineWidth"); @@ -123,62 +144,35 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->CmdSetStencilCompareMask = (PFN_vkCmdSetStencilCompareMask)gpa(device, "vkCmdSetStencilCompareMask"); table->CmdSetStencilWriteMask = (PFN_vkCmdSetStencilWriteMask)gpa(device, "vkCmdSetStencilWriteMask"); table->CmdSetStencilReference = (PFN_vkCmdSetStencilReference)gpa(device, "vkCmdSetStencilReference"); - table->CmdBindDescriptorSets = (PFN_vkCmdBindDescriptorSets)gpa(device, "vkCmdBindDescriptorSets"); table->CmdBindIndexBuffer = (PFN_vkCmdBindIndexBuffer)gpa(device, "vkCmdBindIndexBuffer"); table->CmdBindVertexBuffers = (PFN_vkCmdBindVertexBuffers)gpa(device, "vkCmdBindVertexBuffers"); table->CmdDraw = (PFN_vkCmdDraw)gpa(device, "vkCmdDraw"); table->CmdDrawIndexed = (PFN_vkCmdDrawIndexed)gpa(device, "vkCmdDrawIndexed"); table->CmdDrawIndirect = (PFN_vkCmdDrawIndirect)gpa(device, "vkCmdDrawIndirect"); table->CmdDrawIndexedIndirect = (PFN_vkCmdDrawIndexedIndirect)gpa(device, "vkCmdDrawIndexedIndirect"); - table->CmdDispatch = (PFN_vkCmdDispatch)gpa(device, "vkCmdDispatch"); - table->CmdDispatchIndirect = (PFN_vkCmdDispatchIndirect)gpa(device, "vkCmdDispatchIndirect"); - table->CmdCopyBuffer = (PFN_vkCmdCopyBuffer)gpa(device, "vkCmdCopyBuffer"); - table->CmdCopyImage = (PFN_vkCmdCopyImage)gpa(device, "vkCmdCopyImage"); table->CmdBlitImage = (PFN_vkCmdBlitImage)gpa(device, "vkCmdBlitImage"); - table->CmdCopyBufferToImage = (PFN_vkCmdCopyBufferToImage)gpa(device, "vkCmdCopyBufferToImage"); - table->CmdCopyImageToBuffer = (PFN_vkCmdCopyImageToBuffer)gpa(device, "vkCmdCopyImageToBuffer"); - table->CmdUpdateBuffer = (PFN_vkCmdUpdateBuffer)gpa(device, "vkCmdUpdateBuffer"); - table->CmdFillBuffer = (PFN_vkCmdFillBuffer)gpa(device, "vkCmdFillBuffer"); - table->CmdClearColorImage = (PFN_vkCmdClearColorImage)gpa(device, "vkCmdClearColorImage"); table->CmdClearDepthStencilImage = (PFN_vkCmdClearDepthStencilImage)gpa(device, "vkCmdClearDepthStencilImage"); table->CmdClearAttachments = (PFN_vkCmdClearAttachments)gpa(device, "vkCmdClearAttachments"); table->CmdResolveImage = (PFN_vkCmdResolveImage)gpa(device, "vkCmdResolveImage"); - table->CmdSetEvent = (PFN_vkCmdSetEvent)gpa(device, "vkCmdSetEvent"); - table->CmdResetEvent = (PFN_vkCmdResetEvent)gpa(device, "vkCmdResetEvent"); - table->CmdWaitEvents = (PFN_vkCmdWaitEvents)gpa(device, "vkCmdWaitEvents"); - table->CmdPipelineBarrier = (PFN_vkCmdPipelineBarrier)gpa(device, "vkCmdPipelineBarrier"); - table->CmdBeginQuery = (PFN_vkCmdBeginQuery)gpa(device, "vkCmdBeginQuery"); - table->CmdEndQuery = (PFN_vkCmdEndQuery)gpa(device, "vkCmdEndQuery"); - table->CmdResetQueryPool = (PFN_vkCmdResetQueryPool)gpa(device, "vkCmdResetQueryPool"); - table->CmdWriteTimestamp = (PFN_vkCmdWriteTimestamp)gpa(device, "vkCmdWriteTimestamp"); - table->CmdCopyQueryPoolResults = (PFN_vkCmdCopyQueryPoolResults)gpa(device, "vkCmdCopyQueryPoolResults"); - table->CmdPushConstants = (PFN_vkCmdPushConstants)gpa(device, "vkCmdPushConstants"); table->CmdBeginRenderPass = (PFN_vkCmdBeginRenderPass)gpa(device, "vkCmdBeginRenderPass"); table->CmdNextSubpass = (PFN_vkCmdNextSubpass)gpa(device, "vkCmdNextSubpass"); table->CmdEndRenderPass = (PFN_vkCmdEndRenderPass)gpa(device, "vkCmdEndRenderPass"); - table->CmdExecuteCommands = (PFN_vkCmdExecuteCommands)gpa(device, "vkCmdExecuteCommands"); table->BindBufferMemory2 = (PFN_vkBindBufferMemory2)gpa(device, "vkBindBufferMemory2"); table->BindImageMemory2 = (PFN_vkBindImageMemory2)gpa(device, "vkBindImageMemory2"); table->GetDeviceGroupPeerMemoryFeatures = (PFN_vkGetDeviceGroupPeerMemoryFeatures)gpa(device, "vkGetDeviceGroupPeerMemoryFeatures"); table->CmdSetDeviceMask = (PFN_vkCmdSetDeviceMask)gpa(device, "vkCmdSetDeviceMask"); - table->CmdDispatchBase = (PFN_vkCmdDispatchBase)gpa(device, "vkCmdDispatchBase"); table->GetImageMemoryRequirements2 = (PFN_vkGetImageMemoryRequirements2)gpa(device, "vkGetImageMemoryRequirements2"); table->GetBufferMemoryRequirements2 = (PFN_vkGetBufferMemoryRequirements2)gpa(device, "vkGetBufferMemoryRequirements2"); table->GetImageSparseMemoryRequirements2 = (PFN_vkGetImageSparseMemoryRequirements2)gpa(device, "vkGetImageSparseMemoryRequirements2"); table->TrimCommandPool = (PFN_vkTrimCommandPool)gpa(device, "vkTrimCommandPool"); table->GetDeviceQueue2 = (PFN_vkGetDeviceQueue2)gpa(device, "vkGetDeviceQueue2"); - table->CreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)gpa(device, "vkCreateSamplerYcbcrConversion"); - table->DestroySamplerYcbcrConversion = (PFN_vkDestroySamplerYcbcrConversion)gpa(device, "vkDestroySamplerYcbcrConversion"); + table->CmdDispatchBase = (PFN_vkCmdDispatchBase)gpa(device, "vkCmdDispatchBase"); table->CreateDescriptorUpdateTemplate = (PFN_vkCreateDescriptorUpdateTemplate)gpa(device, "vkCreateDescriptorUpdateTemplate"); table->DestroyDescriptorUpdateTemplate = (PFN_vkDestroyDescriptorUpdateTemplate)gpa(device, "vkDestroyDescriptorUpdateTemplate"); table->UpdateDescriptorSetWithTemplate = (PFN_vkUpdateDescriptorSetWithTemplate)gpa(device, "vkUpdateDescriptorSetWithTemplate"); table->GetDescriptorSetLayoutSupport = (PFN_vkGetDescriptorSetLayoutSupport)gpa(device, "vkGetDescriptorSetLayoutSupport"); - table->CmdDrawIndirectCount = (PFN_vkCmdDrawIndirectCount)gpa(device, "vkCmdDrawIndirectCount"); - table->CmdDrawIndexedIndirectCount = (PFN_vkCmdDrawIndexedIndirectCount)gpa(device, "vkCmdDrawIndexedIndirectCount"); - table->CreateRenderPass2 = (PFN_vkCreateRenderPass2)gpa(device, "vkCreateRenderPass2"); - table->CmdBeginRenderPass2 = (PFN_vkCmdBeginRenderPass2)gpa(device, "vkCmdBeginRenderPass2"); - table->CmdNextSubpass2 = (PFN_vkCmdNextSubpass2)gpa(device, "vkCmdNextSubpass2"); - table->CmdEndRenderPass2 = (PFN_vkCmdEndRenderPass2)gpa(device, "vkCmdEndRenderPass2"); + table->CreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)gpa(device, "vkCreateSamplerYcbcrConversion"); + table->DestroySamplerYcbcrConversion = (PFN_vkDestroySamplerYcbcrConversion)gpa(device, "vkDestroySamplerYcbcrConversion"); table->ResetQueryPool = (PFN_vkResetQueryPool)gpa(device, "vkResetQueryPool"); table->GetSemaphoreCounterValue = (PFN_vkGetSemaphoreCounterValue)gpa(device, "vkGetSemaphoreCounterValue"); table->WaitSemaphores = (PFN_vkWaitSemaphores)gpa(device, "vkWaitSemaphores"); @@ -186,13 +180,16 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->GetBufferDeviceAddress = (PFN_vkGetBufferDeviceAddress)gpa(device, "vkGetBufferDeviceAddress"); table->GetBufferOpaqueCaptureAddress = (PFN_vkGetBufferOpaqueCaptureAddress)gpa(device, "vkGetBufferOpaqueCaptureAddress"); table->GetDeviceMemoryOpaqueCaptureAddress = (PFN_vkGetDeviceMemoryOpaqueCaptureAddress)gpa(device, "vkGetDeviceMemoryOpaqueCaptureAddress"); + table->CmdDrawIndirectCount = (PFN_vkCmdDrawIndirectCount)gpa(device, "vkCmdDrawIndirectCount"); + table->CmdDrawIndexedIndirectCount = (PFN_vkCmdDrawIndexedIndirectCount)gpa(device, "vkCmdDrawIndexedIndirectCount"); + table->CreateRenderPass2 = (PFN_vkCreateRenderPass2)gpa(device, "vkCreateRenderPass2"); + table->CmdBeginRenderPass2 = (PFN_vkCmdBeginRenderPass2)gpa(device, "vkCmdBeginRenderPass2"); + table->CmdNextSubpass2 = (PFN_vkCmdNextSubpass2)gpa(device, "vkCmdNextSubpass2"); + table->CmdEndRenderPass2 = (PFN_vkCmdEndRenderPass2)gpa(device, "vkCmdEndRenderPass2"); table->CreatePrivateDataSlot = (PFN_vkCreatePrivateDataSlot)gpa(device, "vkCreatePrivateDataSlot"); table->DestroyPrivateDataSlot = (PFN_vkDestroyPrivateDataSlot)gpa(device, "vkDestroyPrivateDataSlot"); table->SetPrivateData = (PFN_vkSetPrivateData)gpa(device, "vkSetPrivateData"); table->GetPrivateData = (PFN_vkGetPrivateData)gpa(device, "vkGetPrivateData"); - table->CmdSetEvent2 = (PFN_vkCmdSetEvent2)gpa(device, "vkCmdSetEvent2"); - table->CmdResetEvent2 = (PFN_vkCmdResetEvent2)gpa(device, "vkCmdResetEvent2"); - table->CmdWaitEvents2 = (PFN_vkCmdWaitEvents2)gpa(device, "vkCmdWaitEvents2"); table->CmdPipelineBarrier2 = (PFN_vkCmdPipelineBarrier2)gpa(device, "vkCmdPipelineBarrier2"); table->CmdWriteTimestamp2 = (PFN_vkCmdWriteTimestamp2)gpa(device, "vkCmdWriteTimestamp2"); table->QueueSubmit2 = (PFN_vkQueueSubmit2)gpa(device, "vkQueueSubmit2"); @@ -200,6 +197,12 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->CmdCopyImage2 = (PFN_vkCmdCopyImage2)gpa(device, "vkCmdCopyImage2"); table->CmdCopyBufferToImage2 = (PFN_vkCmdCopyBufferToImage2)gpa(device, "vkCmdCopyBufferToImage2"); table->CmdCopyImageToBuffer2 = (PFN_vkCmdCopyImageToBuffer2)gpa(device, "vkCmdCopyImageToBuffer2"); + table->GetDeviceBufferMemoryRequirements = (PFN_vkGetDeviceBufferMemoryRequirements)gpa(device, "vkGetDeviceBufferMemoryRequirements"); + table->GetDeviceImageMemoryRequirements = (PFN_vkGetDeviceImageMemoryRequirements)gpa(device, "vkGetDeviceImageMemoryRequirements"); + table->GetDeviceImageSparseMemoryRequirements = (PFN_vkGetDeviceImageSparseMemoryRequirements)gpa(device, "vkGetDeviceImageSparseMemoryRequirements"); + table->CmdSetEvent2 = (PFN_vkCmdSetEvent2)gpa(device, "vkCmdSetEvent2"); + table->CmdResetEvent2 = (PFN_vkCmdResetEvent2)gpa(device, "vkCmdResetEvent2"); + table->CmdWaitEvents2 = (PFN_vkCmdWaitEvents2)gpa(device, "vkCmdWaitEvents2"); table->CmdBlitImage2 = (PFN_vkCmdBlitImage2)gpa(device, "vkCmdBlitImage2"); table->CmdResolveImage2 = (PFN_vkCmdResolveImage2)gpa(device, "vkCmdResolveImage2"); table->CmdBeginRendering = (PFN_vkCmdBeginRendering)gpa(device, "vkCmdBeginRendering"); @@ -219,28 +222,25 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->CmdSetRasterizerDiscardEnable = (PFN_vkCmdSetRasterizerDiscardEnable)gpa(device, "vkCmdSetRasterizerDiscardEnable"); table->CmdSetDepthBiasEnable = (PFN_vkCmdSetDepthBiasEnable)gpa(device, "vkCmdSetDepthBiasEnable"); table->CmdSetPrimitiveRestartEnable = (PFN_vkCmdSetPrimitiveRestartEnable)gpa(device, "vkCmdSetPrimitiveRestartEnable"); - table->GetDeviceBufferMemoryRequirements = (PFN_vkGetDeviceBufferMemoryRequirements)gpa(device, "vkGetDeviceBufferMemoryRequirements"); - table->GetDeviceImageMemoryRequirements = (PFN_vkGetDeviceImageMemoryRequirements)gpa(device, "vkGetDeviceImageMemoryRequirements"); - table->GetDeviceImageSparseMemoryRequirements = (PFN_vkGetDeviceImageSparseMemoryRequirements)gpa(device, "vkGetDeviceImageSparseMemoryRequirements"); - table->CmdSetLineStipple = (PFN_vkCmdSetLineStipple)gpa(device, "vkCmdSetLineStipple"); table->MapMemory2 = (PFN_vkMapMemory2)gpa(device, "vkMapMemory2"); table->UnmapMemory2 = (PFN_vkUnmapMemory2)gpa(device, "vkUnmapMemory2"); - table->CmdBindIndexBuffer2 = (PFN_vkCmdBindIndexBuffer2)gpa(device, "vkCmdBindIndexBuffer2"); - table->GetRenderingAreaGranularity = (PFN_vkGetRenderingAreaGranularity)gpa(device, "vkGetRenderingAreaGranularity"); table->GetDeviceImageSubresourceLayout = (PFN_vkGetDeviceImageSubresourceLayout)gpa(device, "vkGetDeviceImageSubresourceLayout"); table->GetImageSubresourceLayout2 = (PFN_vkGetImageSubresourceLayout2)gpa(device, "vkGetImageSubresourceLayout2"); + table->CopyMemoryToImage = (PFN_vkCopyMemoryToImage)gpa(device, "vkCopyMemoryToImage"); + table->CopyImageToMemory = (PFN_vkCopyImageToMemory)gpa(device, "vkCopyImageToMemory"); + table->CopyImageToImage = (PFN_vkCopyImageToImage)gpa(device, "vkCopyImageToImage"); + table->TransitionImageLayout = (PFN_vkTransitionImageLayout)gpa(device, "vkTransitionImageLayout"); table->CmdPushDescriptorSet = (PFN_vkCmdPushDescriptorSet)gpa(device, "vkCmdPushDescriptorSet"); table->CmdPushDescriptorSetWithTemplate = (PFN_vkCmdPushDescriptorSetWithTemplate)gpa(device, "vkCmdPushDescriptorSetWithTemplate"); - table->CmdSetRenderingAttachmentLocations = (PFN_vkCmdSetRenderingAttachmentLocations)gpa(device, "vkCmdSetRenderingAttachmentLocations"); - table->CmdSetRenderingInputAttachmentIndices = (PFN_vkCmdSetRenderingInputAttachmentIndices)gpa(device, "vkCmdSetRenderingInputAttachmentIndices"); table->CmdBindDescriptorSets2 = (PFN_vkCmdBindDescriptorSets2)gpa(device, "vkCmdBindDescriptorSets2"); table->CmdPushConstants2 = (PFN_vkCmdPushConstants2)gpa(device, "vkCmdPushConstants2"); table->CmdPushDescriptorSet2 = (PFN_vkCmdPushDescriptorSet2)gpa(device, "vkCmdPushDescriptorSet2"); table->CmdPushDescriptorSetWithTemplate2 = (PFN_vkCmdPushDescriptorSetWithTemplate2)gpa(device, "vkCmdPushDescriptorSetWithTemplate2"); - table->CopyMemoryToImage = (PFN_vkCopyMemoryToImage)gpa(device, "vkCopyMemoryToImage"); - table->CopyImageToMemory = (PFN_vkCopyImageToMemory)gpa(device, "vkCopyImageToMemory"); - table->CopyImageToImage = (PFN_vkCopyImageToImage)gpa(device, "vkCopyImageToImage"); - table->TransitionImageLayout = (PFN_vkTransitionImageLayout)gpa(device, "vkTransitionImageLayout"); + table->CmdSetLineStipple = (PFN_vkCmdSetLineStipple)gpa(device, "vkCmdSetLineStipple"); + table->CmdBindIndexBuffer2 = (PFN_vkCmdBindIndexBuffer2)gpa(device, "vkCmdBindIndexBuffer2"); + table->GetRenderingAreaGranularity = (PFN_vkGetRenderingAreaGranularity)gpa(device, "vkGetRenderingAreaGranularity"); + table->CmdSetRenderingAttachmentLocations = (PFN_vkCmdSetRenderingAttachmentLocations)gpa(device, "vkCmdSetRenderingAttachmentLocations"); + table->CmdSetRenderingInputAttachmentIndices = (PFN_vkCmdSetRenderingInputAttachmentIndices)gpa(device, "vkCmdSetRenderingInputAttachmentIndices"); table->CreateSwapchainKHR = (PFN_vkCreateSwapchainKHR)gpa(device, "vkCreateSwapchainKHR"); table->DestroySwapchainKHR = (PFN_vkDestroySwapchainKHR)gpa(device, "vkDestroySwapchainKHR"); table->GetSwapchainImagesKHR = (PFN_vkGetSwapchainImagesKHR)gpa(device, "vkGetSwapchainImagesKHR"); @@ -372,6 +372,7 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->CmdBindDescriptorBufferEmbeddedSamplers2EXT = (PFN_vkCmdBindDescriptorBufferEmbeddedSamplers2EXT)gpa(device, "vkCmdBindDescriptorBufferEmbeddedSamplers2EXT"); table->CmdCopyMemoryIndirectKHR = (PFN_vkCmdCopyMemoryIndirectKHR)gpa(device, "vkCmdCopyMemoryIndirectKHR"); table->CmdCopyMemoryToImageIndirectKHR = (PFN_vkCmdCopyMemoryToImageIndirectKHR)gpa(device, "vkCmdCopyMemoryToImageIndirectKHR"); + table->CmdEndRendering2KHR = (PFN_vkCmdEndRendering2KHR)gpa(device, "vkCmdEndRendering2KHR"); table->DebugMarkerSetObjectTagEXT = (PFN_vkDebugMarkerSetObjectTagEXT)gpa(device, "vkDebugMarkerSetObjectTagEXT"); table->DebugMarkerSetObjectNameEXT = (PFN_vkDebugMarkerSetObjectNameEXT)gpa(device, "vkDebugMarkerSetObjectNameEXT"); table->CmdDebugMarkerBeginEXT = (PFN_vkCmdDebugMarkerBeginEXT)gpa(device, "vkCmdDebugMarkerBeginEXT"); @@ -710,6 +711,8 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->GetScreenBufferPropertiesQNX = (PFN_vkGetScreenBufferPropertiesQNX)gpa(device, "vkGetScreenBufferPropertiesQNX"); #endif // VK_USE_PLATFORM_SCREEN_QNX table->CmdBindTileMemoryQCOM = (PFN_vkCmdBindTileMemoryQCOM)gpa(device, "vkCmdBindTileMemoryQCOM"); + table->CmdDecompressMemoryEXT = (PFN_vkCmdDecompressMemoryEXT)gpa(device, "vkCmdDecompressMemoryEXT"); + table->CmdDecompressMemoryIndirectCountEXT = (PFN_vkCmdDecompressMemoryIndirectCountEXT)gpa(device, "vkCmdDecompressMemoryIndirectCountEXT"); table->CreateExternalComputeQueueNV = (PFN_vkCreateExternalComputeQueueNV)gpa(device, "vkCreateExternalComputeQueueNV"); table->DestroyExternalComputeQueueNV = (PFN_vkDestroyExternalComputeQueueNV)gpa(device, "vkDestroyExternalComputeQueueNV"); table->GetExternalComputeQueueDataNV = (PFN_vkGetExternalComputeQueueDataNV)gpa(device, "vkGetExternalComputeQueueDataNV"); @@ -726,6 +729,15 @@ static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDisp table->DestroyIndirectExecutionSetEXT = (PFN_vkDestroyIndirectExecutionSetEXT)gpa(device, "vkDestroyIndirectExecutionSetEXT"); table->UpdateIndirectExecutionSetPipelineEXT = (PFN_vkUpdateIndirectExecutionSetPipelineEXT)gpa(device, "vkUpdateIndirectExecutionSetPipelineEXT"); table->UpdateIndirectExecutionSetShaderEXT = (PFN_vkUpdateIndirectExecutionSetShaderEXT)gpa(device, "vkUpdateIndirectExecutionSetShaderEXT"); +#if defined(VK_USE_PLATFORM_OHOS) + table->GetSwapchainGrallocUsageOHOS = (PFN_vkGetSwapchainGrallocUsageOHOS)gpa(device, "vkGetSwapchainGrallocUsageOHOS"); +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + table->AcquireImageOHOS = (PFN_vkAcquireImageOHOS)gpa(device, "vkAcquireImageOHOS"); +#endif // VK_USE_PLATFORM_OHOS +#if defined(VK_USE_PLATFORM_OHOS) + table->QueueSignalReleaseImageOHOS = (PFN_vkQueueSignalReleaseImageOHOS)gpa(device, "vkQueueSignalReleaseImageOHOS"); +#endif // VK_USE_PLATFORM_OHOS #if defined(VK_USE_PLATFORM_METAL_EXT) table->GetMemoryMetalHandleEXT = (PFN_vkGetMemoryMetalHandleEXT)gpa(device, "vkGetMemoryMetalHandleEXT"); #endif // VK_USE_PLATFORM_METAL_EXT