From 96ada348e7f8750c6ae6a524f8633f47e37e65a1 Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 14 Jul 2026 18:51:50 +0200 Subject: [PATCH 01/17] profiles: Fix dependencies and alone schema generation only --- .gitignore | 6 +-- CMakeLists.txt | 73 +++++++++++++++++++++++--------- library/test/CMakeLists.txt | 20 ++++----- profiles/CMakeLists.txt | 71 ++++++++++++++++++------------- profiles/test/CMakeLists.txt | 6 +-- scripts/gen_profiles_solution.py | 2 +- scripts/tests/CMakeLists.txt | 10 ++--- 7 files changed, 114 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index 07b94a40..dfcc2d15 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,6 @@ scripts/source/vulkan_object.py scripts/source/base_generator.py scripts/source/vk.xml scripts/profiles.spec -profiles.spec -scripts/profiles -scripts/profiles.exe +vkprofiles.spec +scripts/vkprofiles +scripts/vkprofiles.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a3d9684..4bfa2aac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,54 @@ endif() set(PROFILES_SCHEMA_FILENAME "profiles-0.8-latest.json") # Generate profiles executable -set(OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts") +function(add_pyinstaller_target TARGET_NAME) + # Define the arguments the function accepts + set(options) + set(oneValueArgs SCRIPT OUTPUT_DIR FOLDER OUTPUT_NAME) + set(multiValueArgs DEPENDS) + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARG_SCRIPT) + message(FATAL_ERROR "add_pyinstaller_target: SCRIPT argument is required for target ${TARGET_NAME}") + endif() + + if(NOT ARG_OUTPUT_DIR) + set(ARG_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + endif() + + if(NOT ARG_OUTPUT_NAME) + set(ARG_OUTPUT_NAME "${TARGET_NAME}") + endif() + + # Handle OS-specific executable extension + if(WIN32) + set(EXE_NAME "${ARG_OUTPUT_NAME}.exe") + else() + set(EXE_NAME "${ARG_OUTPUT_NAME}") + endif() + set(FINAL_EXECUTABLE "${ARG_OUTPUT_DIR}/${EXE_NAME}") + + # Generate the executable via PyInstaller + add_custom_command( + OUTPUT "${FINAL_EXECUTABLE}" + COMMAND ${Python3_EXECUTABLE} -m PyInstaller --onefile --name "${ARG_OUTPUT_NAME}" --distpath "${ARG_OUTPUT_DIR}" "${ARG_SCRIPT}" + DEPENDS ${ARG_DEPENDS} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Packaging ${TARGET_NAME} into a standalone executable..." + VERBATIM + ) + + # Create the custom target to hook into the build system + add_custom_target( + ${TARGET_NAME} + DEPENDS "${FINAL_EXECUTABLE}" + ) + + # Set IDE folder property if provided + if(ARG_FOLDER) + set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "${ARG_FOLDER}") + endif() +endfunction() set(PYTHON_DEPENDENCIES "${PROFILES_SCRIPT}" @@ -83,27 +130,13 @@ set(PYTHON_DEPENDENCIES "${CMAKE_CURRENT_SOURCE_DIR}/scripts/source/vulkan_object_version.py" ) -if(WIN32) - set(EXE_NAME "profiles.exe") -else() - set(EXE_NAME "profiles") -endif() -set(FINAL_EXECUTABLE "${OUTPUT_DIR}/${EXE_NAME}") - -add_custom_command( - OUTPUT "${FINAL_EXECUTABLE}" - COMMAND ${Python3_EXECUTABLE} -m PyInstaller --onefile --distpath "${OUTPUT_DIR}" "${PROFILES_SCRIPT}" +add_pyinstaller_target(VpProfilesProcessor + OUTPUT_NAME "vkprofiles" + SCRIPT "${PROFILES_SCRIPT}" + OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts" DEPENDS ${PYTHON_DEPENDENCIES} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Packaging profiles.py into a standalone executable..." - VERBATIM -) - -add_custom_target( - profiles - DEPENDS "${FINAL_EXECUTABLE}" + FOLDER "Profiles generator" ) -set_target_properties(profiles PROPERTIES FOLDER "Profiles generator") # The profiles directory regenerates the Profiles source and headers. add_subdirectory(profiles) diff --git a/library/test/CMakeLists.txt b/library/test/CMakeLists.txt index fa8e455c..c8b2fb28 100644 --- a/library/test/CMakeLists.txt +++ b/library/test/CMakeLists.txt @@ -36,10 +36,8 @@ add_custom_target(VpLibrary_test_generated_library SOURCES ${SOLUTION_SCRIPT} ${CMAKE_CURRENT_LIST_DIR}/profiles DEPENDS ${SOLUTION_SCRIPT} ${CMAKE_CURRENT_LIST_DIR}/profiles ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml) - set_target_properties(VpLibrary_test_generated_library PROPERTIES FOLDER "Profiles API library") - -add_dependencies(VpLibrary_test_generated_library VpGenerated) +add_dependencies(VpLibrary_test_generated_library VpGenerate-Libraries) # The release source is in ../source/vulkan_profiles.cpp, but the debug source (with # verbose messages during profile validation) is in ../source/debug/vulkan_profiles.cpp. @@ -57,7 +55,7 @@ function(add_unit_test NAME) target_compile_definitions(${TEST_NAME_HO} PUBLIC "VK_ENABLE_BETA_EXTENSIONS=1") target_include_directories(${TEST_NAME_HO} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") target_link_libraries(${TEST_NAME_HO} PRIVATE ${test_libraries}) - add_dependencies(${TEST_NAME_HO} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME_HO} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME_HO} COMMAND ${TEST_NAME_HO} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME_HO} PROPERTIES FOLDER "Profiles API library") @@ -69,7 +67,7 @@ function(add_unit_test NAME) target_compile_definitions(${TEST_NAME_WS} PUBLIC "VK_ENABLE_BETA_EXTENSIONS=1") target_include_directories(${TEST_NAME_WS} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") target_link_libraries(${TEST_NAME_WS} PRIVATE ${test_libraries}) - add_dependencies(${TEST_NAME_WS} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME_WS} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME_WS} COMMAND ${TEST_NAME_WS} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME_WS} PROPERTIES FOLDER "Profiles API library") endfunction(add_unit_test) @@ -87,7 +85,7 @@ function(add_unit_test_object NAME) target_compile_definitions(${TEST_NAME_HO} PUBLIC "VP_USE_OBJECT=1") target_include_directories(${TEST_NAME_HO} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") target_link_libraries(${TEST_NAME_HO} PRIVATE ${test_libraries}) - add_dependencies(${TEST_NAME_HO} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME_HO} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME_HO} COMMAND ${TEST_NAME_HO} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME_HO} PROPERTIES FOLDER "Profiles API library") @@ -100,7 +98,7 @@ function(add_unit_test_object NAME) target_compile_definitions(${TEST_NAME_WS} PUBLIC "VP_USE_OBJECT=1") target_include_directories(${TEST_NAME_WS} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") target_link_libraries(${TEST_NAME_WS} PRIVATE ${test_libraries}) - add_dependencies(${TEST_NAME_WS} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME_WS} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME_WS} COMMAND ${TEST_NAME_WS} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME_WS} PROPERTIES FOLDER "Profiles API library") endfunction(add_unit_test_object) @@ -174,7 +172,7 @@ function (add_unit_test_simple_android_apk NAME) create_android_package(${TEST_NAME_HO}) target_include_directories(${TEST_NAME_HO} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") - add_dependencies(${TEST_NAME_HO} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME_HO} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME_HO} COMMAND ${TEST_NAME_HO} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME_HO} PROPERTIES FOLDER "Profiles API library") endfunction(add_unit_test_simple_android_apk) @@ -193,7 +191,7 @@ function(add_unit_test_simple NAME) endif() target_include_directories(${TEST_NAME_HO} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") target_link_libraries(${TEST_NAME_HO} PRIVATE ${test_libraries}) - add_dependencies(${TEST_NAME_HO} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME_HO} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME_HO} COMMAND ${TEST_NAME_HO} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME_HO} PROPERTIES FOLDER "Profiles API library") endfunction(add_unit_test_simple) @@ -211,7 +209,7 @@ function(add_unit_test_with_debug_messages_variant NAME) endif() target_include_directories(${TEST_NAME} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") target_link_libraries(${TEST_NAME} PRIVATE ${test_libraries}) - add_dependencies(${TEST_NAME} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME} PROPERTIES FOLDER "Profiles API library") @@ -222,7 +220,7 @@ function(add_unit_test_with_debug_messages_variant NAME) target_compile_definitions(${TEST_NAME_DEBUG_MESSAGES} PUBLIC "WITH_DEBUG_MESSAGES=1") target_include_directories(${TEST_NAME_DEBUG_MESSAGES} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") target_link_libraries(${TEST_NAME_DEBUG_MESSAGES} PRIVATE ${test_libraries}) - add_dependencies(${TEST_NAME_DEBUG_MESSAGES} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME_DEBUG_MESSAGES} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME_DEBUG_MESSAGES} COMMAND ${TEST_NAME_DEBUG_MESSAGES} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME_DEBUG_MESSAGES} PROPERTIES FOLDER "Profiles API library") endfunction(add_unit_test_with_debug_messages_variant) diff --git a/profiles/CMakeLists.txt b/profiles/CMakeLists.txt index 292e86f2..7e471347 100644 --- a/profiles/CMakeLists.txt +++ b/profiles/CMakeLists.txt @@ -49,6 +49,20 @@ set(PROFILES_FILES_FOR_ANDROID_DOC "VP_ANDROID_15_requirements.json,VP_ANDROID_16_requirements.json,VP_ANDROID_17_requirements.json,VP_ANDROID_vulkan_profile_2021.json,VP_ANDROID_vulkan_profile_2022.json,VP_ANDROID_vulkan_profile_2025.json" ) +# Generate profiles schema +add_custom_target(VpGenerate-ProfilesSchema + COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/schema + COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + --api ${API_TYPE} + --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml + --output-schema ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} + --validate + VERBATIM + SOURCES ${SOLUTION_SCRIPT} + DEPENDS ${SOLUTION_SCRIPT}) +set_target_properties(VpGenerate-ProfilesSchema PROPERTIES FOLDER "Profiles schema") +add_dependencies(VpGenerate-ProfilesSchema VpProfilesProcessor) + set(PROFILE_DESKTOP_MAX_2024_LABEL "LunarG Vulkan Desktop Max 2024 profile") set(PROFILE_DESKTOP_MAX_2024_DESC "A profile generated by the intersection of a collection of GPUInfo.org device reports to support the latest AMD, Intel and NVIDIA GPUs and drivers.") set(PROFILE_DESKTOP_MAX_2024_API_VERSION "1.3.244") @@ -57,7 +71,8 @@ set(PROFILE_DESKTOP_MAX_2026_LABEL "LunarG Vulkan Desktop Max 2026 profile") set(PROFILE_DESKTOP_MAX_2026_DESC "A profile generated by the intersection of a collection of GPUInfo.org device reports to support the latest AMD, Intel and NVIDIA GPUs and drivers.") set(PROFILE_DESKTOP_MAX_2026_API_VERSION "1.4.353") -add_custom_target(VpCreateDesktopMax ALL +# Generate profiles files +add_custom_target(VpGenerate-ProfilesDesktopMax ALL COMMAND Python3::Interpreter ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_max_2024 @@ -81,33 +96,33 @@ add_custom_target(VpCreateDesktopMax ALL VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) +set_target_properties(VpGenerate-ProfilesDesktopMax PROPERTIES FOLDER "Profiles generator") +add_dependencies(VpGenerate-ProfilesDesktopMax VpGenerate-ProfilesSchema) -set_target_properties(VpCreateDesktopMax PROPERTIES FOLDER "Profiles generator") - -add_custom_target(VpCreateDesktopBaseline ALL +add_custom_target(VpGenerate-ProfilesDesktopBaseline ALL COMMAND Python3::Interpreter ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --config ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline_config.json --output-path ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline.json --strip-duplicate-structs + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline.json ${CMAKE_CURRENT_LIST_DIR} VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) +set_target_properties(VpGenerate-ProfilesDesktopBaseline PROPERTIES FOLDER "Profiles generator") +add_dependencies(VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesSchema) -set_target_properties(VpCreateDesktopBaseline PROPERTIES FOLDER "Profiles generator") - -add_custom_target(VpGenerated - COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/schema +add_custom_target(VpGenerate-Libraries COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/library/include/vulkan COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/library/include/vulkan/debug COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/library/source COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/library/source/debug COMMAND ${CMAKE_COMMAND} -E copy # Copy the Roadmap profiles file from Vulkan-Header repository - # ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/profiles/VP_KHR_roadmap.json - ${CMAKE_CURRENT_LIST_DIR}/Khronos/VP_KHR_roadmap.json - ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_minimum_requirements.json - ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline.json ${CMAKE_CURRENT_LIST_DIR} + ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/profiles/VP_KHR_roadmap.json + # ${CMAKE_CURRENT_LIST_DIR}/Khronos/VP_KHR_roadmap.json + ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_minimum_requirements.json ${CMAKE_CURRENT_LIST_DIR} COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml @@ -137,7 +152,7 @@ add_custom_target(VpGenerated --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/Android - --input-filenames VP_ANDROID_vulkan_profile_2021.json + --input-filenames ${PROFILES_FILES_FOR_ANDROID_DOC} --output-library-inc ${PROJECT_SOURCE_DIR}/library/test --output-library-src ${PROJECT_SOURCE_DIR}/library/test --output-library-filename "generated_vulkan_profiles_android" @@ -160,6 +175,13 @@ add_custom_target(VpGenerated --output-library-src ${PROJECT_SOURCE_DIR}/library/test --output-library-filename "generated_vulkan_profiles_debug" --config debug + VERBATIM + SOURCES ${SOLUTION_SCRIPT} ${PROFILES_FILES} + DEPENDS ${SOLUTION_SCRIPT} ${PROFILES_FILES}) +set_target_properties(VpGenerate-Libraries PROPERTIES FOLDER "Profiles generator") +add_dependencies(VpGenerate-Libraries VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesDesktopMax) + +add_custom_target(VpGenerate-Markdown COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml @@ -181,10 +203,8 @@ add_custom_target(VpGenerated VERBATIM SOURCES ${SOLUTION_SCRIPT} ${PROFILES_FILES} DEPENDS ${SOLUTION_SCRIPT} ${PROFILES_FILES}) - -set_target_properties(VpGenerated PROPERTIES FOLDER "Profiles generator") - -add_dependencies(VpGenerated VpCreateDesktopBaseline VpCreateDesktopMax) +set_target_properties(VpGenerate-Markdown PROPERTIES FOLDER "Profiles generator") +add_dependencies(VpGenerate-Markdown VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesDesktopMax) add_custom_target(VpTestIntersect ALL COMMAND Python3::Interpreter ${MERGE_SCRIPT} @@ -196,10 +216,8 @@ add_custom_target(VpTestIntersect ALL VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) - set_target_properties(VpTestIntersect PROPERTIES FOLDER "Profiles generator/Tests") - -add_dependencies(VpTestIntersect VpGenerated) +add_dependencies(VpTestIntersect VpProfilesProcessor) add_custom_target(VpTestUnion ALL COMMAND Python3::Interpreter ${MERGE_SCRIPT} @@ -211,10 +229,8 @@ add_custom_target(VpTestUnion ALL VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) - set_target_properties(VpTestUnion PROPERTIES FOLDER "Profiles generator/Tests") - -add_dependencies(VpTestUnion VpGenerated) +add_dependencies(VpTestUnion VpProfilesProcessor) add_custom_target(VpTestGeneratedName ALL COMMAND Python3::Interpreter ${MERGE_SCRIPT} @@ -224,10 +240,8 @@ add_custom_target(VpTestGeneratedName ALL VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) - set_target_properties(VpTestGeneratedName PROPERTIES FOLDER "Profiles generator/Tests") - -add_dependencies(VpTestGeneratedName VpGenerated) +add_dependencies(VpTestGeneratedName VpProfilesProcessor) add_custom_target(VpTestHostImageCopy ALL COMMAND Python3::Interpreter ${MERGE_SCRIPT} @@ -237,10 +251,8 @@ add_custom_target(VpTestHostImageCopy ALL VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) - set_target_properties(VpTestHostImageCopy PROPERTIES FOLDER "Profiles generator/Tests") - -add_dependencies(VpTestHostImageCopy VpGenerated) +add_dependencies(VpTestHostImageCopy VpProfilesProcessor) set(json_install_dir "${CMAKE_INSTALL_DATADIR}/vulkan/config/VK_LAYER_KHRONOS_profiles") if (WIN32) @@ -256,6 +268,7 @@ install( install( FILES + ${MERGE_SCRIPT} ${MERGE_SCRIPT} ${SOLUTION_SCRIPT} ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} diff --git a/profiles/test/CMakeLists.txt b/profiles/test/CMakeLists.txt index c74e9619..4860639e 100644 --- a/profiles/test/CMakeLists.txt +++ b/profiles/test/CMakeLists.txt @@ -34,12 +34,8 @@ target_compile_definitions(${TEST_NAME} PRIVATE PROFILES_SCHEMA_FILENAME="${CMAKE_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME}" JSON_TEST_FILES_PATH="${CMAKE_SOURCE_DIR}/profiles/test/data/" PROFILE_FILES_PATH="${CMAKE_SOURCE_DIR}/profiles/") -add_dependencies(${TEST_NAME} VpGenerated) - target_link_libraries(${TEST_NAME} PRIVATE GTest::gtest GTest::gtest_main jsoncpp_static valijson) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME} --gtest_catch_exceptions=0) - set_target_properties(${TEST_NAME} PROPERTIES FOLDER "Profiles schema") - -add_dependencies(VpProfile_test_schema_validation VpGenerated) +add_dependencies(${TEST_NAME} VpGenerate-ProfilesDesktopMax VpGenerate-ProfilesDesktopBaseline) diff --git a/scripts/gen_profiles_solution.py b/scripts/gen_profiles_solution.py index b72504e6..fe5178e4 100644 --- a/scripts/gen_profiles_solution.py +++ b/scripts/gen_profiles_solution.py @@ -7843,7 +7843,7 @@ def gen_videoFormats(self, videoProfileName, videoCodec): parser.add_argument('--registry', '-r', action='store', required=True, help='Use specified registry file instead of vk.xml (video.xml must be present in the same directory for video support).') - parser.add_argument('--input', '-i', action='store', required=True, + parser.add_argument('--input', '-i', action='store', help='Path to directory with profiles.') parser.add_argument('--input-filenames', action='store', help='The optional filenames of the profiles files in the directory. If this parameter is not set, all profiles files are loaded.') diff --git a/scripts/tests/CMakeLists.txt b/scripts/tests/CMakeLists.txt index ec75dddf..daff041c 100644 --- a/scripts/tests/CMakeLists.txt +++ b/scripts/tests/CMakeLists.txt @@ -33,9 +33,9 @@ function(add_vulkan_python_test TARGET_NAME SCRIPT_FILENAME) endfunction() if(NOT APPLE) # macOS doesn't handle dynamically loading Python jsonschema' - add_vulkan_python_test(VpTestVulkanProfilesUtil test_util.py) - #add_vulkan_python_test(VpTestVulkanObjectEmpty test_init.py) requires Header 357 - #add_vulkan_python_test(VpTestVulkanObjectUtils test_vulkan_object_utils.py) requires Header 357 - add_vulkan_python_test(VpTestVulkanObjectDepends test_vulkan_object_expression_parsing.py) - add_vulkan_python_test(VpTestVulkanProfilesJson test_vulkan_profiles.py) + add_vulkan_python_test(VpProfilesProcessor_TestVulkanProfilesUtil test_util.py) + #add_vulkan_python_test(VpProfilesProcessor_TestVulkanObjectEmpty test_init.py) requires Header 357 + #add_vulkan_python_test(VpProfilesProcessor_TestVulkanObjectUtils test_vulkan_object_utils.py) requires Header 357 + add_vulkan_python_test(VpProfilesProcessor_TestVulkanObjectDepends test_vulkan_object_expression_parsing.py) + add_vulkan_python_test(VpProfilesProcessor_TestVulkanProfilesJson test_vulkan_profiles.py) endif() From 3ae090b0cb4c906e3776df0f5479a0663a73e005 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 15 Jul 2026 15:24:38 +0200 Subject: [PATCH 02/17] script: Exact VulkanRegistry to be replaced by VulkanObject --- scripts/gen_profiles_solution.py | 2115 +---------------------------- scripts/source/generate_schema.py | 613 +++++++++ scripts/source/log.py | 35 + scripts/source/vulkan_registry.py | 1521 +++++++++++++++++++++ 4 files changed, 2173 insertions(+), 2111 deletions(-) create mode 100644 scripts/source/generate_schema.py create mode 100644 scripts/source/log.py create mode 100644 scripts/source/vulkan_registry.py diff --git a/scripts/gen_profiles_solution.py b/scripts/gen_profiles_solution.py index fe5178e4..d225054f 100644 --- a/scripts/gen_profiles_solution.py +++ b/scripts/gen_profiles_solution.py @@ -15,60 +15,23 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Authors: +# Authors: # - Daniel Rakos # - Christophe Riccio import os import re -import copy import itertools import functools import argparse from typing import OrderedDict import xml.etree.ElementTree as etree import json -from collections import deque from string import Template -def apiNameMatch(str, supported): - """Return whether a required api name matches a pattern specified for an - XML 'api' attribute or 'supported' attribute. - - str - API name such as 'vulkan' or 'openxr'. May be None, in which - case it never matches (this should not happen). - - supported - comma-separated list of XML API names. May be None, in - which case str always matches (this is the usual case).""" - - if str is not None: - return supported is None or str in supported.split(',') - - # Fallthrough case - either str is None or the test failed - return False - -def stripNonmatchingAPIs(tree, apiName, actuallyDelete = True): - """Remove tree Elements with 'api' attributes matching apiName. - tree - Element at the root of the hierarchy to strip. Only its - children can actually be removed, not the tree itself. - apiName - string which much match a command-separated component of - the 'api' attribute. - actuallyDelete - only delete matching elements if True.""" - - stack = deque() - stack.append(tree) - - while len(stack) > 0: - parent = stack.pop() - - for child in parent.findall('*'): - api = child.get('api') - - if apiNameMatch(apiName, api): - # Add child to the queue - stack.append(child) - elif not apiNameMatch(apiName, api): - # Child does not match requested api. Remove it. - if actuallyDelete: - parent.remove(child) +from source.generate_schema import VulkanProfilesSchemaGenerator +from source.vulkan_registry import VulkanRegistry, VulkanVersionNumber +from source.log import Log COPYRIGHT_HEADER = ''' /* @@ -3108,40 +3071,6 @@ def stripNonmatchingAPIs(tree, apiName, actuallyDelete = True): } ''' - -# Evaluates that a condition is satisfied per the specified list of values -# e.g.: -# condition = '(A+B),C' -# evaluates to True for values = [ 'A', 'B' ] -# evaluates to True for values = [ 'C' ] -# evaluates to False for values = [ 'A' ] -# evaluates to False for values = [ 'B' ] -def evalConditionFromList(condition, values): - evalstr = "" - value = "" - - def genExpressionFromValue(value): - return value if value == "" else "('{0}' in values)".format(value) - - for char in condition: - if char in ['(', ')', '+', ',']: - evalstr += genExpressionFromValue(value) - value = "" - if char == '+': - # '+' means AND - evalstr += ' and ' - elif char == ',': - # ',' means OR - evalstr += ' or ' - else: - evalstr += char - else: - value += char - evalstr += genExpressionFromValue(value) - - return eval(evalstr) - - # Generates a C/C++ condition verifying flags # e.g.: # condition = '(A+B),C' @@ -3172,1451 +3101,6 @@ def genExpressionFromValue(variable, value): return c_cond - -class Log(): - def f(msg): - print('FATAL: ' + msg) - raise Exception(msg) - - def e(msg): - print('ERROR: ' + msg) - - def w(msg): - print('WARNING: ' + msg) - - def i(msg): - print(msg) - - -class VulkanPlatform(): - def __init__(self, data): - self.name = data.get('name') - self.protect = data.get('protect') - - -class VulkanStructMember(): - def __init__(self, name, type, limittype, isArray = False): - self.name = name - self.type = type - self.limittype = limittype - self.isArray = isArray - self.arraySizeMember = None - self.nullTerminated = False - self.arraySize = None - self.arraySizeCap = None - - def isDynamicallySizedArrayWithCap(self): - return self.isArray and self.arraySizeCap is not None - -class VulkanStruct(): - def __init__(self, name): - self.name = name - self.sType = None - self.extends = [] - self.members = OrderedDict() - self.aliases = [ name ] - self.isAlias = False - self.definedByVersion = None - self.definedByExtensions = [] - self.isBeta = None - - -class VulkanEnum(): - def __init__(self, name): - self.name = name - self.aliases = [ name ] - self.isAlias = False - self.values = [] - self.aliasValues = dict() - - -class VulkanBitmask(): - def __init__(self, name): - self.name = name - self.aliases = [ name ] - self.isAlias = False - self.bitsType = None - - -class VulkanFeature(): - def __init__(self, name): - self.name = name - self.structs = set() - - -class VulkanLimit(): - def __init__(self, name): - self.name = name - self.structs = set() - - -class VulkanVideoRequiredCapabilities(): - def __init__(self, struct, member, value): - self.struct = struct - self.member = member - self.value = value - - -class VulkanVideoFormat(): - def __init__(self, name, usage): - self.name = name - self.usage = usage - self.properties = OrderedDict() - self.requiredCaps = list() - super().__init__() - - def matchesImageUsageFlags(self, flags): - # Check if the specified list of VkImageUsageFlags matches the usage criteria - # for this video format category - return evalConditionFromList(self.usage, flags) - - def hasRequiredCapabilities(self, videoCapabilities, registry): - hasAllRequiredCaps = True - for requiredCap in self.requiredCaps: - capabilitiesData = None - if requiredCap.struct in videoCapabilities: - capabilitiesData = videoCapabilities[requiredCap.struct] - else: - # Check also for possible aliases - for alias in registry.structs[requiredCap.struct].aliases: - if alias in videoCapabilities: - capabilitiesData = videoCapabilities[alias] - - if capabilitiesData is not None: - if requiredCap.member in capabilitiesData: - value = capabilitiesData[requiredCap.member] - if isinstance(value, list): - hasAllRequiredCaps = evalConditionFromList(requiredCap.value, value) - else: - hasAllRequiredCaps = (requiredCap.value == value) - else: - # Required capability structure member is missing - hasAllRequiredCaps = False - else: - # Entire required capability structure is missing - hasAllRequiredCaps = False - return hasAllRequiredCaps - - -class VulkanVideoProfileStructMember(): - def __init__(self, name): - self.name = name - self.values = OrderedDict() - - -class VulkanVideoProfileStruct(): - def __init__(self, struct): - self.struct = struct - self.members = OrderedDict() - - -class VulkanVideoCodec(): - def __init__(self, name, extend = None, value = None): - self.name = name - self.value = value - self.profileStructs = OrderedDict() - self.capabilities = OrderedDict() - self.formats = OrderedDict() - if extend is not None: - self.profileStructs = copy.deepcopy(extend.profileStructs) - self.capabilities = copy.deepcopy(extend.capabilities) - self.formats = copy.deepcopy(extend.formats) - - def isSpecific(self): - return self.value is not None - - def getVideoFormatCategoriesForFormat(self, videoFormat, videoCapabilities, registry): - result = list() - - baseProps = registry.getBaseVideoFormatPropertiesFromVideoFormat(videoFormat) - - # Find the the video format categories the video format belongs to - if 'imageUsageFlags' in baseProps: - foundVideoFormatCategory = False - hadMatchWithMissingPrerequisities = None - for videoFormatCategory in self.formats.values(): - # Check if the video format matches the image usage requirements of the video format category - if not videoFormatCategory.matchesImageUsageFlags(baseProps['imageUsageFlags']): - continue - # Make sure that the video profile has the required capabilites for this video format category - if not videoFormatCategory.hasRequiredCapabilities(videoCapabilities, registry): - hadMatchWithMissingPrerequisities = videoFormatCategory - continue - # This video format does indeed fall into this video format category - foundVideoFormatCategory = True - result.append(videoFormatCategory) - if not foundVideoFormatCategory: - if hadMatchWithMissingPrerequisities is not None: - Log.e("Video format from category {0} with missing prerequisites:\n{1}".format(hadMatchWithMissingPrerequisities.name, json.dumps(videoFormat, indent=4))) - else: - Log.e("Unrecognized video format category for imageUsageFlags in video format:\n{0}".format(json.dumps(videoFormat, indent=4))) - else: - Log.f("Missing imageUsageFlags from video format:\n{0}".format(json.dumps(videoFormat, indent=4))) - - return result - - -class VulkanVersionNumber(): - def __init__(self, versionStr, targetApi = None, versionName = None): - match = re.search(r"^([1-9][0-9]*)\.([0-9]+)$", versionStr) - if match != None: - # Only major and minor version specified - self.major = int(match.group(1)) - self.minor = int(match.group(2)) - self.patch = None - else: - # Otherwise expect major, minor, and patch version - match = re.search(r"^([1-9][0-9]*)\.([0-9]+)\.([0-9]+)$", versionStr) - if match != None: - self.major = int(match.group(1)) - self.minor = int(match.group(2)) - self.patch = int(match.group(3)) - else: - Log.f("Invalid API version string: '{0}'".format(versionStr)) - - # Construct version number pre-processor definition's name - if targetApi == 'vulkan': - self.versionName = 'VK_VERSION_{0}_{1}'.format(self.major, self.minor) - self.versionMacro = 'VK_API_VERSION_{0}_{1}'.format(self.major, self.minor) - self.versionStructSuffic = '{0}{1}'.format(self.major, self.minor) - - elif targetApi is not None: - Log.f("Unknown target API '{0}'".format(targetApi)) - - def get_api_version_string(self): - return 'VK_API_VERSION_' + str(self.major) + '_' + str(self.minor) - - def __eq__(self, other): - if isinstance(other, VulkanVersionNumber): - # Only consider major and minor version in comparison - return self.major == other.major and self.minor == other.minor - else: - return False - - def __gt__(self, other): - # Only consider major and minor version in comparison - return self.major > other.major or (self.major == other.major and self.minor > other.minor) - - def __lt__(self, other): - # Only consider major and minor version in comparison - return self.major < other.major or (self.major == other.major and self.minor < other.minor) - - def __ne__(self, other): - return not self.__eq__(other) - - def __ge__(self, other): - return self.__eq__(other) or self.__gt__(other) - - def __le__(self, other): - return self.__eq__(other) or self.__lt__(other) - - def __str__(self): - if self.patch != None: - return '{0}.{1}.{2}'.format(self.major, self.minor, self.patch) - else: - return '{0}.{1}'.format(self.major, self.minor) - - -class VulkanDefinitions(): - def __init__(self): - self.enums = set() - self.types = set() - - def add(self, elements): - for element in elements: - for enum in element.findall("./enum"): - self.enums.add(enum.get('name')) - for type in element.findall("./type"): - self.types.add(type.get('name')) - - def addDependencies(self, xml, targetApi): - # Add types that are required by required types as dependency - for type in xml.findall("./types/type[@requires]"): - apiList = type.get('api') - - # Skip dependency if it does not apply to the target API - if apiList is not None and not targetApi in apiList.split(','): - continue - - name = type.find('./name') - if name is not None and name.text in self.types: - self.types.add(type.get('requires')) - - # Add types that contain the definition of required alias types as dependency - for type in xml.findall("./types/type[@alias]"): - - # Skip dependency if it does not apply to the target API - if apiList is not None and not targetApi in apiList.split(','): - continue - - name = type.get('name') - if name in self.types: - self.types.add(type.get('alias')) - - -class VulkanDefinitionScope(): - def parseAliases(self, xml): - self.sTypeAliases = dict() - for sTypeAlias in xml.findall("./require/enum[@alias]"): - if re.search(r'^VK_STRUCTURE_TYPE_.*', sTypeAlias.get('name')): - self.sTypeAliases[sTypeAlias.get('alias')] = sTypeAlias.get('name') - - -class VulkanVersion(VulkanDefinitionScope): - def __init__(self, xml, targetApi): - self.name = xml.get('name') - self.number = VulkanVersionNumber(xml.get('number'), targetApi, self.name) - self.extensions = [] - self.features = dict() - self.limits = dict() - self.parseAliases(xml) - - -class VulkanExtension(VulkanDefinitionScope): - def __init__(self, xml, upperCaseName): - self.name = xml.get('name') - self.upperCaseName = upperCaseName - self.type = xml.get('type') - self.features = dict() - self.limits = dict() - self.platform = xml.get('platform') - self.provisional = xml.get('provisional') - self.promotedTo = xml.get('promotedto').split(',') if xml.get('promotedto') is not None else [] - self.obsoletedBy = xml.get('obsoletedby') - self.deprecatedBy = xml.get('deprecatedby') - self.spec_version = 1 - for e in xml.findall("./require/enum"): - if (e.get('name').endswith("SPEC_VERSION")): - self.spec_version = e.get('value') - break - self.parseAliases(xml) - - -# Dynamic arrays are ill-formed, but some of them still have a maximum size that can be used -struct_with_valid_dynamic_array = ["VkQueueFamilyGlobalPriorityProperties"] -# These dynamic arrays have a known maximum possible size -struct_with_dynamic_array_size_cap = ["VkPhysicalDeviceHostImageCopyProperties", "VkPhysicalDeviceHostImageCopyPropertiesEXT", "VkPhysicalDeviceVulkan14Properties"] - -class VulkanRegistry(): - def __init__(self, registryFile, api = 'vulkan'): - Log.i("Loading registry file: '{0}'".format(registryFile)) - xml = etree.parse(registryFile) - stripNonmatchingAPIs(xml.getroot(), api, actuallyDelete = True) - - videoRegistryFile = registryFile.replace('vk.xml', 'video.xml') - if os.path.isfile(videoRegistryFile): - Log.i("Loading video registry file: '{0}'".format(videoRegistryFile)) - videoxml = etree.parse(videoRegistryFile) - else: - Log.w("Video registry file '{0}' does not exist, building without video support".format(videoRegistryFile)) - videoxml = None - - self.api = api - self.require = VulkanDefinitions() - self.remove = VulkanDefinitions() - - self.parsePlatformInfo(xml) - self.parseVersionInfo(xml) - self.parseExtensionInfo(xml) - - self.require.addDependencies(xml, self.api) - - self.parseStructInfo(xml) - self.parsePrerequisites(xml) - self.parseEnums(xml) - self.parseFormats(xml) - self.parseBitmasks(xml) - self.parseConstants(xml) - self.parseAliases(xml) - self.parseExternalTypes(xml) - self.parseFeatures(xml) - self.parseLimits(xml) - self.parseHeaderVersion(xml) - self.parseVideoCodecs(xml, videoxml) - self.applyWorkarounds() - - def findAllFeatures(self, xml, xpath = None): - results = [] - for feature in xml.findall("./feature"): - apiList = feature.get('api') - if self.api in apiList.split(','): - if xpath is None: - results.append(feature) - else: - results.extend(feature.findall(xpath)) - return results - - def findAllExtensions(self, xml, xpath = None): - results = [] - for extension in xml.findall("./extensions/extension"): - apiList = extension.get('supported') - if self.api in apiList.split(','): - if xpath is None: - results.append(extension) - else: - results.extend(extension.findall(xpath)) - return results - - def parseRequireRemove(self, xml): - self.require.add(xml.findall("./require")) - self.remove.add(xml.findall("./remove")) - - def parsePlatformInfo(self, xml): - self.platforms = dict() - for plat in xml.findall("./platforms/platform"): - self.platforms[plat.get('name')] = VulkanPlatform(plat) - - def parseVersionInfo(self, xml): - self.versions = dict() - for feature in self.findAllFeatures(xml): - if re.search(r"^[1-9][0-9]*\.[0-9]+$", feature.get('number')): - self.versions[feature.get('name')] = VulkanVersion(feature, self.api) - self.parseRequireRemove(feature) - else: - Log.f("Unsupported feature with number '{0}'".format(feature.get('number'))) - - def parseExtensionInfo(self, xml): - self.extensions = dict() - for ext in self.findAllExtensions(xml): - name = ext.get('name') - - # Find name enum (due to inconsistencies in lower case and upper case names this is non-trivial) - foundNameEnum = False - matches = ext.findall("./require/enum[@value='\"" + name + "\"']") - for match in matches: - if match.get('name').endswith("_EXTENSION_NAME"): - # Add extension definition - self.extensions[name] = VulkanExtension(ext, match.get('name')[:-len("_EXTENSION_NAME")]) - foundNameEnum = True - break - if not foundNameEnum: - Log.f("Cannot find name enum for extension '{0}'".format(name)) - - self.parseRequireRemove(ext) - - def parseStructInfo(self, xml): - self.structs = dict() - for struct in xml.findall("./types/type[@category='struct']"): - name = struct.get('name') - - # Don't process structure if it is not required or if it is removed - if name not in self.require.types or name in self.remove.types: - continue - - # Define base struct information - structDef = VulkanStruct(name) - - # Find out whether it's an extension structure - extends = struct.get('structextends') - if extends != None: - structDef.extends = extends.split(',') - - # Find sType value - sType = struct.find("./member[name='sType']") - if sType != None: - structDef.sType = sType.get('values') - - # Parse struct members - for member in struct.findall('./member'): - name = member.find('./name').text - tail = member.find('./name').tail - type = member.find('./type').text - - # Only add real members (skip sType and pNext) - if name != 'sType' and name != 'pNext': - # Define base member information - structDef.members[name] = VulkanStructMember( - name, - type, - member.get('limittype') - ) - - # Detect if it's an array - if tail != None and tail[0] == '[': - structDef.members[name].isArray = True - match1D = re.search(r"^\[([0-9]+)\]$", tail) - match2D = re.search(r"^\[([0-9]+)\]\[([0-9]+)\]$", tail) - enum = member.find('./enum') - if match1D != None: - # [] case - structDef.members[name].arraySize = int(match1D.group(1)) - elif match2D != None: - # [][] case - structDef.members[name].arraySize = [ int(match2D.group(1)), int(match2D.group(2)) ] - elif tail == '[' and enum != None and enum.tail == ']': - # [] case - structDef.members[name].arraySize = enum.text - elif structDef.name == 'VkPhysicalDeviceDataGraphOperationSupportARM': - # Handle xml bug - structDef.members['name'].arraySize = 'VK_MAX_PHYSICAL_DEVICE_DATA_GRAPH_OPERATION_SET_NAME_SIZE_ARM' - else: - Log.f("Unsupported array format for struct member '{0}::{1}'".format(structDef.name, name)) - - # If it has a "len" attribute then it's also an array, just a dynamically sized one - if member.get('len') != None: - lenMeta = member.get('len').split(',') - for len in lenMeta: - if len == 'null-terminated': - # Values are null-terminated - structDef.members[name].nullTerminated = True - else: - # This is a pointer to an array with a corresponding count member - structDef.members[name].isArray = True - structDef.members[name].arraySizeMember = len - - # Some arrays have a natural maximum size even if they are dynamic. For example, a list - # of VkImageLayouts, because that enum itself is limited. - if structDef.members[name].type == 'VkImageLayout': - structDef.members[name].arraySizeCap = 64 - - # If any of the members is a dynamic array then we should remove the corresponding count member - for member in list(structDef.members.values()): - if member.isArray and member.arraySizeMember != None and struct.get('name') not in struct_with_valid_dynamic_array and struct.get('name') not in struct_with_dynamic_array_size_cap: - structDef.members.pop(member.arraySizeMember, None) - - # Store struct definition - self.structs[struct.get('name')] = structDef - - def parsePrerequisites(self, xml): - # Check features (i.e. API versions) - for feature in self.findAllFeatures(xml): - for requireType in feature.findall('./require/type'): - # Add feature as the source of the definition of a struct - if requireType.get('name') in self.structs: - self.structs[requireType.get('name')].definedByVersion = VulkanVersionNumber(feature.get('number'), self.api, feature.get('name')) - - # Check extensions - for extension in self.findAllExtensions(xml): - for requireType in extension.findall('./require/type'): - # Add extension as the source of the definition of a struct - if requireType.get('name') in self.structs: - self.structs[requireType.get('name')].definedByExtensions.append(extension.get('name')) - - def parseEnums(self, xml): - self.enums = dict() - # Find enum definitions - for enum in xml.findall("./types/type[@category='enum']"): - name = enum.get('name') - - # Don't process enum type if it is not required or if it is removed - if name not in self.require.types or name in self.remove.types: - continue - - # Create enum type - enumDef = VulkanEnum(name) - - # First collect base values - values = xml.find("./enums[@name='" + enumDef.name + "']") - if values is not None: - for value in values.findall("./enum"): - if value.get('alias') is None: - enumDef.values.append(value.get('name')) - - # Then find extension values - for value in self.findAllFeatures(xml, "./require/enum[@extends='" + enumDef.name + "']"): - if value.get('alias') is None: - enumDef.values.append(value.get('name')) - for value in self.findAllExtensions(xml, "./require/enum[@extends='" + enumDef.name + "']"): - if value.get('alias') is None: - enumDef.values.append(value.get('name')) - - # Remove any values that are marked as removed - removedValues = [] - for name in enumDef.values: - if name in self.remove.enums: - removedValues.append(name) - for name in removedValues: - enumDef.values.remove(name) - - # Finally store it in the registry - self.enums[enumDef.name] = enumDef - - def parseFormats(self, xml): - self.formatCompression = dict() - for enum in xml.findall("./formats/format"): - if enum.get('compressed'): - self.formatCompression[enum.get('name')] = enum.get('compressed') - - self.aliasFormats = list() - for format in self.findAllExtensions(xml, "./require/enum[@extends='VkFormat'][@alias]"): - self.aliasFormats.append(format.attrib["name"]) - - self.betaFormatFeatures = list() - for format_feature in self.findAllExtensions(xml, "./require/enum[@protect='VK_ENABLE_BETA_EXTENSIONS']"): - self.betaFormatFeatures.append(format_feature.attrib["name"]) - - def parseBitmasks(self, xml): - self.bitmasks = dict() - # Find bitmask definitions - for bitmask in xml.findall("./types/type[@category='bitmask']"): - # Only consider non-alias bitmasks - name = bitmask.find("./name") - if bitmask.get('alias') is None and name != None: - # Don't process bitmask type if it is not required or if it is removed - if name.text not in self.require.types or name.text in self.remove.types: - continue - - bitmaskDef = VulkanBitmask(name.text) - - # Get the name of the corresponding FlagBits type - bitsName = bitmask.get('bitvalues') - if bitsName is None: - # Currently some definitions use "requires", not "bitvalues" - bitsName = bitmask.get('requires') - - if bitsName != None: - if bitsName in self.enums: - bitmaskDef.bitsType = self.enums[bitsName] - else: - Log.f("Could not find bits enum '{0}' for bitmask '{1}'".format(bitsName, bitmaskDef.name)) - else: - # This bitmask doesn't have any bits defined - pass - - # Finally store it in the registry - self.bitmasks[bitmaskDef.name] = bitmaskDef - - def parseConstants(self, xml): - self.constants = dict() - # Find constant definitions - constants = xml.find("./enums[@name='API Constants']").findall("./enum[@value]") - if constants != None: - for constant in constants: - self.constants[constant.get('name')] = constant.get('value') - else: - Log.f("Failed to find API constants in the registry") - - def parseAliases(self, xml): - # Find any struct aliases - for struct in xml.findall("./types/type[@category='struct']"): - name = struct.get('name') - - # Don't process structure if it is not required or if it is removed - if name not in self.require.types or name in self.remove.types: - continue - - alias = struct.get('alias') - if alias != None: - # Don't process alias if it is not required or if it is removed - if alias not in self.require.types or alias in self.remove.types: - continue - - if alias in self.structs: - baseStructDef = self.structs[alias] - aliasStructDef = self.structs[name] - - # Set as alias - aliasStructDef.isAlias = True - - # Fill missing struct information for the alias - aliasStructDef.extends = baseStructDef.extends - aliasStructDef.members = baseStructDef.members - aliasStructDef.aliases = baseStructDef.aliases - aliasStructDef.aliases.append(name) - - # Use alias structure dependencies as the structure dependencies if the latter has none - # This is needed to handle the case when the structure is not part of the target API - # but is a dependency of the alias - if baseStructDef.definedByVersion is None and len(baseStructDef.definedByExtensions) == 0: - baseStructDef.definedByVersion = aliasStructDef.definedByVersion - baseStructDef.definedByExtensions = aliasStructDef.definedByExtensions - - if baseStructDef.sType != None: - sTypeAlias = None - - # First try to find sType alias in core versions - if aliasStructDef.definedByVersion != None: - for versionName in self.versions: - version = self.versions[versionName] - if version.number <= aliasStructDef.definedByVersion: - sTypeAlias = version.sTypeAliases.get(baseStructDef.sType) - if sTypeAlias != None: - break - - # Otherwise need to find sType alias in extension - if sTypeAlias == None: - for extName in aliasStructDef.definedByExtensions: - sTypeAlias = self.extensions[extName].sTypeAliases.get(baseStructDef.sType) - if sTypeAlias != None: - break - - #Workaround due to a vk.xml issue that was resolved with 1.1.119 - if alias == 'VkPhysicalDeviceVariablePointersFeatures': - sTypeAlias = 'VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES' - - if sTypeAlias != None: - aliasStructDef.sType = sTypeAlias - - # Find any enum aliases - for enum in xml.findall("./types/type[@category='enum']"): - name = enum.get('name') - - # Don't process enum type if it is not required or if it is removed - if name not in self.require.types or name in self.remove.types: - continue - - alias = enum.get('alias') - if alias != None: - # Don't process alias if it is not required or if it is removed - if alias not in self.require.types or alias in self.remove.types: - continue - - if alias in self.enums: - baseEnumDef = self.enums[alias] - aliasEnumDef = self.enums[name] - - # Set as alias - aliasEnumDef.isAlias = True - - # Merge aliases - aliasEnumDef.aliases = baseEnumDef.aliases - aliasEnumDef.aliases.append(name) - - # Merge values respecting original order - for value in aliasEnumDef.values: - if not value in baseEnumDef.values: - baseEnumDef.values.append(value) - aliasEnumDef.values = baseEnumDef.values - else: - Log.f("Failed to find alias '{0}' of enum '{1}'".format(alias, enum.get('name'))) - - # Find any enum value aliases - for enum in xml.findall("./enums"): - if enum.get('name') in self.enums.keys(): - enumDef = self.enums[enum.get('name')] - for aliasValue in enum.findall("./enum[@alias]"): - name = aliasValue.get('name') - alias = aliasValue.get('alias') - enumDef.values.append(name) - enumDef.aliasValues[name] = alias - for aliasValue in self.findAllExtensions(xml, "./require/enum[@alias]"): - if aliasValue.get('extends'): - enumDef = self.enums[aliasValue.get('extends')] - name = aliasValue.get('name') - alias = aliasValue.get('alias') - enumDef.values.append(name) - enumDef.aliasValues[name] = alias - - # Find any bitmask (flags) aliases - for bitmask in xml.findall("./types/type[@category='bitmask']"): - name = bitmask.get('name') - - # Don't process bitmask if it is not required or if it is removed - if name not in self.require.types or name in self.remove.types: - continue - - alias = bitmask.get('alias') - if alias != None: - # Don't process alias if it is not required or if it is removed - if alias not in self.require.types or alias in self.remove.types: - continue - - if alias in self.bitmasks: - # Duplicate bitmask definition - baseBitmaskDef = self.bitmasks[alias] - aliasBitmaskDef = VulkanBitmask(name) - aliasBitmaskDef.bitsType = baseBitmaskDef.bitsType - - # Set as alias - aliasBitmaskDef.isAlias = True - - # Merge aliases - aliasBitmaskDef.aliases = baseBitmaskDef.aliases - aliasBitmaskDef.aliases.append(name) - else: - Log.f("Failed to find alias '{0}' of bitmask '{1}'".format(alias, bitmask.get('name'))) - - # Find any constant aliases - for constant in xml.find("./enums[@name='API Constants']").findall("./enum[@alias]"): - self.constants[constant.get('name')] = self.constants[constant.get('alias')] - - def parseExternalTypes(self, xml): - self.includes = set() - self.externalTypes = set() - - # Find all include definitions - for include in xml.findall("./types/type[@category='include']"): - self.includes.add(include.get('name')) - - # Find all types depending on the includes - for type in xml.findall("./types/type[@requires]"): - if type.get('requires') in self.includes: - self.externalTypes.add(type.get('name')) - - def parseFeatures(self, xml): - # First, parse features specific to Vulkan versions - for version in self.versions.values(): - if version.number.major == 1 and version.number.minor == 0: - # For version 1.0 use VkPhysicalDeviceFeatures - structDef = self.structs['VkPhysicalDeviceFeatures'] - for memberDef in structDef.members.values(): - version.features[memberDef.name] = VulkanFeature(memberDef.name) - version.features[memberDef.name].structs.add('VkPhysicalDeviceFeatures') - else: - # For all other versions use the feature structures required by it - featureStructNames = [] - xmlVersion = xml.find("./feature[@name='" + version.name + "']") - for type in xmlVersion.findall("./require/type"): - name = type.get('name') - if name in self.structs and 'VkPhysicalDeviceFeatures2' in self.structs[name].extends: - featureStructNames.append(name) - # VkPhysicalDeviceVulkan11Features is defined in Vulkan 1.2, but it actually - # contains Vulkan 1.1 features, so treat it as such - if version.number.major == 1 and version.number.minor == 1: - featureStructNames.append('VkPhysicalDeviceVulkan11Features') - elif version.number.major == 1 and version.number.minor == 2: - if 'VkPhysicalDeviceVulkan11Features' in featureStructNames: - featureStructNames.remove('VkPhysicalDeviceVulkan11Features') - # For each feature collect all feature structures containing them, and their aliases - for featureStructName in featureStructNames: - if (featureStructName in self.structs): - structDef = self.structs[featureStructName] - for memberName in structDef.members.keys(): - if not memberName in version.features: - version.features[memberName] = VulkanFeature(memberName) - version.features[memberName].structs.update(structDef.aliases) - - # Then parse features specific to extensions - for extension in self.extensions.values(): - featureStructNames = [] - xmlExtension = xml.find("./extensions/extension[@name='" + extension.name + "']") - for type in xmlExtension.findall("./require/type"): - name = type.get('name') - if name in self.structs and 'VkPhysicalDeviceFeatures2' in self.structs[name].extends: - featureStructNames.append(name) - # For each feature collect all feature structures containing them, and their aliases - for featureStructName in featureStructNames: - structDef = self.structs[featureStructName] - for memberName in structDef.members.keys(): - extension.features[memberName] = VulkanFeature(memberName) - extension.features[memberName].structs.update(structDef.aliases) - # For each feature we also have to check whether it's part of core so that - # any not strictly alias struct (i.e. the VkPhysicalDeviceVulkanXXFeatures) - # get included as well - for version in self.versions.values(): - if memberName in version.features and version.features[memberName].structs >= extension.features[memberName].structs: - extension.features[memberName].structs = version.features[memberName].structs - - def parseLimits(self, xml): - # First, parse properties/limits specific to Vulkan versions - for version in self.versions.values(): - if version.number.major == 1 and version.number.minor == 0: - # The properties extension structures are a misnomer, as they contain limits, - # however, the naming will stay with us, so in order to avoid nested - # "properties" (limits), we simply use VkPhysicalDeviceLimits directly here - # for version 1.0 limits, plus, not having a better place to put them, we - # also include VkPhysicalDeviceSparseProperties here (even though they are - # more like features) - limitStructNames = [ 'VkPhysicalDeviceLimits', 'VkPhysicalDeviceSparseProperties' ] - else: - # For all other versions use the property structures required by it - limitStructNames = [] - xmlVersion = xml.find("./feature[@name='" + version.name + "']") - for type in xmlVersion.findall("./require/type"): - name = type.get('name') - if name in self.structs and 'VkPhysicalDeviceProperties2' in self.structs[name].extends: - limitStructNames.append(name) - # VkPhysicalDeviceVulkan11Properties is defined in Vulkan 1.2, but it actually - # contains Vulkan 1.1 limits, so treat it as such - if version.number.major == 1 and version.number.minor == 1: - limitStructNames.append('VkPhysicalDeviceVulkan11Properties') - elif version.number.major == 1 and version.number.minor == 2: - if 'VkPhysicalDeviceVulkan11Properties' in limitStructNames: - limitStructNames.remove('VkPhysicalDeviceVulkan11Properties') - # For each limit collect all property/limit structures containing them, and their aliases - for limitStructName in limitStructNames: - if (limitStructName in self.structs): - structDef = self.structs[limitStructName] - for memberName in structDef.members.keys(): - if not memberName in version.limits: - version.limits[memberName] = VulkanLimit(memberName) - version.limits[memberName].structs.update(structDef.aliases) - - # Then parse properties/limits specific to extensions - for extension in self.extensions.values(): - limitStructNames = [] - xmlExtension = xml.find("./extensions/extension[@name='" + extension.name + "']") - for type in xmlExtension.findall("./require/type"): - name = type.get('name') - if name in self.structs and 'VkPhysicalDeviceProperties2' in self.structs[name].extends: - limitStructNames.append(name) - # For each limit collect all property/limit structures containing them, and their aliases - for limitStructName in limitStructNames: - structDef = self.structs[limitStructName] - for memberName in structDef.members.keys(): - extension.limits[memberName] = VulkanLimit(memberName) - extension.limits[memberName].structs.update(structDef.aliases) - # For each limit we also have to check whether it's part of core so that - # any not strictly alias struct (i.e. the VkPhysicalDeviceVulkanXXProperties) - # get included as well - for version in self.versions.values(): - if memberName in version.limits and version.limits[memberName].structs >= extension.limits[memberName].structs: - extension.limits[memberName].structs = version.limits[memberName].structs - - def parseHeaderVersion(self, xml): - # Find the largest version number - maxVersionNumber = self.versions[max(self.versions, key = lambda version: self.versions[version].number)].number - self.headerVersionNumber = VulkanVersionNumber(str(maxVersionNumber)) - # Add patch from VK_HEADER_VERSION define - for define in xml.findall("./types/type[@category='define']"): - name = define.find('./name') - if name != None and name.text == 'VK_HEADER_VERSION': - self.headerVersionNumber.patch = int(name.tail.lstrip()) - return - - def parseVideoConstants(self, videoxml): - for constant in videoxml.findall("./extensions/extension/require/enum[@value]"): - self.constants[constant.get('name')] = constant.get('value') - - def parseVideoEnums(self, videoxml): - # Find enum definitions - for enum in videoxml.findall("./enums[@name]"): - name = enum.get('name') - - # Only add video enum type if it is a required external type - if name in self.externalTypes: - # Create enum type - enumDef = VulkanEnum(name) - - # First collect base values - for value in enum.findall("./enum"): - if value.get('alias') is None: - enumDef.values.append(value.get('name')) - - # Store video enum type in the registry - self.enums[name] = enumDef - - # Remove video enum type from the set of external types - self.externalTypes.remove(name) - - def parseVideoCodecs(self, xml, videoxml): - self.videoCodecs = dict() - - # Used to look up video codecs based on the video codec op value - self.videoCodecsByValue = dict() - - # Used to reverse look up video codecs by the defined structure names if no video codec op value is available - self.videoCodecsByStructName = dict() - - if videoxml is None: - return - - self.parseVideoConstants(videoxml) - self.parseVideoEnums(videoxml) - - xmlVideoCodecs = xml.find("./videocodecs") - for xmlVideoCodec in xmlVideoCodecs.findall("./videocodec"): - name = xmlVideoCodec.get('name') - extend = xmlVideoCodec.get('extend') - value = xmlVideoCodec.get('value') - if value is None: - # Video codec category - self.videoCodecs[name] = VulkanVideoCodec(name) - else: - # Specific video codec - self.videoCodecs[name] = VulkanVideoCodec(name, self.videoCodecs[extend], value) - self.videoCodecsByValue[value] = self.videoCodecs[name] - videoCodec = self.videoCodecs[name] - - for xmlVideoProfiles in xmlVideoCodec.findall("./videoprofiles"): - videoProfileStructName = xmlVideoProfiles.get('struct') - videoCodec.profileStructs[videoProfileStructName] = VulkanVideoProfileStruct(videoProfileStructName) - videoProfileStruct = videoCodec.profileStructs[videoProfileStructName] - self.videoCodecsByStructName[videoProfileStructName] = videoCodec - - for xmlVideoProfileMember in xmlVideoProfiles.findall("./videoprofilemember"): - memberName = xmlVideoProfileMember.get('name') - videoProfileStruct.members[memberName] = VulkanVideoProfileStructMember(memberName) - videoProfileStructMember = videoProfileStruct.members[memberName] - - for xmlVideoProfile in xmlVideoProfileMember.findall("./videoprofile"): - videoProfileStructMember.values[xmlVideoProfile.get('value')] = xmlVideoProfile.get('name') - - for xmlVideoCapabilities in xmlVideoCodec.findall("./videocapabilities"): - capabilityStructName = xmlVideoCapabilities.get('struct') - videoCodec.capabilities[capabilityStructName] = capabilityStructName - self.videoCodecsByStructName[capabilityStructName] = videoCodec - - for xmlVideoFormat in xmlVideoCodec.findall("./videoformat"): - videoFormatName = xmlVideoFormat.get('name') - videoFormatExtend = xmlVideoFormat.get('extend') - if videoFormatName is not None: - # This is a new video format category - videoFormatUsage = xmlVideoFormat.get('usage') - videoCodec.formats[videoFormatName] = VulkanVideoFormat(videoFormatName, videoFormatUsage) - videoFormat = videoCodec.formats[videoFormatName] - elif videoFormatExtend is not None: - # This is an extension to an already defined video format category - if videoFormatExtend in videoCodec.formats: - videoFormat = videoCodec.formats[videoFormatExtend] - else: - Log.f("Video format category '{0}' not found but it is attempted to be extended".format(videoFormatExtend)) - else: - Log.f('"name" or "extend" is attribute is required for "videoformat" element') - - for xmlVideoFormatProperties in xmlVideoFormat.findall("./videoformatproperties"): - propertiesStructName = xmlVideoFormatProperties.get('struct') - videoFormat.properties[propertiesStructName] = propertiesStructName - self.videoCodecsByStructName[propertiesStructName] = videoCodec - - for xmlVideoFormatRequiredCap in xmlVideoFormat.findall("./videorequirecapabilities"): - requiredCapStruct = xmlVideoFormatRequiredCap.get('struct') - requiredCapMember = xmlVideoFormatRequiredCap.get('member') - requiredCapValue = xmlVideoFormatRequiredCap.get('value') - videoFormat.requiredCaps.append(VulkanVideoRequiredCapabilities(requiredCapStruct, requiredCapMember, requiredCapValue)) - - def getBaseVideoProfileInfoFromVideoProfile(self, videoProfile): - if not 'profile' in videoProfile: - return None - profile = videoProfile['profile'] - if 'VkVideoProfileInfoKHR' in profile: - return profile['VkVideoProfileInfoKHR'] - else: - # Check also for possible aliases - for alias in self.structs['VkVideoProfileInfoKHR'].aliases: - if alias in profile: - return profile[alias] - return None - - def getBaseVideoFormatPropertiesFromVideoFormat(self, format): - if 'VkVideoFormatPropertiesKHR' in format: - return format['VkVideoFormatPropertiesKHR'] - else: - # Check also for possible aliases - for alias in self.structs['VkVideoFormatPropertiesKHR'].aliases: - if alias in format: - return format[alias] - Log.f("Did not find base video format properties in video format:\n{0}".format(json.dumps(format, indent=4))) - return None - - def getVideoCodecFromVideoProfile(self, videoProfile): - base = self.getBaseVideoProfileInfoFromVideoProfile(videoProfile) - if base is not None and 'videoCodecOperation' in base: - if base['videoCodecOperation'] not in self.videoCodecsByValue: - Log.f("Unrecognized videoCodecOperation in video profile:\n{0}".format(json.dumps(videoProfile['profile'], indent=4))) - return self.videoCodecsByValue[base['videoCodecOperation']] - else: - # No VkVideoProfileInfoKHR in the profile definition or no videoCodecOperation specified - # We do a reverse lookup based on the defined structures - videoCodec = None - structNames = set() - if 'profile' in videoProfile: - structNames = structNames.union(set(videoProfile['profile'].keys())) - if 'capabilities' in videoProfile: - structNames = structNames.union(set(videoProfile['capabilities'].keys())) - if 'formats' in videoProfile: - for videoFormat in videoProfile['formats']: - structNames = structNames.union(set(videoFormat.keys())) - for structName in structNames: - if structName in self.videoCodecsByStructName: - newMatchingVideoCodec = self.videoCodecsByStructName[structName] - if videoCodec is None or not videoCodec.isSpecific(): - videoCodec = newMatchingVideoCodec - if videoCodec is None: - # No match found, create an empty video codec to represent general requirements - videoCodec = VulkanVideoCodec("General") - return videoCodec - - - def getVideoProfileNameFromVideoProfile(self, videoProfile): - videoCodec = self.getVideoCodecFromVideoProfile(videoProfile) - base = self.getBaseVideoProfileInfoFromVideoProfile(videoProfile) - - # Video profile name always contains the codec name which is either the specific codec name, - # "General" to indicate no specific codec profile, or one of the codec categories like "Decode" and "Encode" - profileName = videoCodec.name - - if base is not None: - profile = videoProfile['profile'] - - # Helper function populating lookup tables with alias values - def genAliasValues(flagBitsTypeName, map): - flagBitsTypeName = self.enums[self.getNonAliasTypeName(flagBitsTypeName, self.enums)] - for alias, value in flagBitsTypeName.aliasValues.items(): - if alias in map: - map[value] = map[alias] - elif value in map: - map[value] = map[alias] - return map - - formatModifiers = [] - - chromaSubsamplingMap = genAliasValues('VkVideoChromaSubsamplingFlagBitsKHR', { - "VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR": "4:2:0", - "VK_VIDEO_CHROMA_SUBSAMPLING_422_BIT_KHR": "4:2:2", - "VK_VIDEO_CHROMA_SUBSAMPLING_444_BIT_KHR": "4:4:4", - "VK_VIDEO_CHROMA_SUBSAMPLING_MONOCHROME_BIT_KHR": "monochrome" - }) - if 'chromaSubsampling' in base: - # Include chroma subsampling info in the name as it is present - if len(base['chromaSubsampling']) != 1: - Log.f("Expected chromaSubsampling to only contain a single value in video profile:\n{0}".format(json.dumps(profile, indent=4))) - if base['chromaSubsampling'][0] not in chromaSubsamplingMap: - Log.f("Unrecognized chromaSubsampling in video profile:\n%s".format(json.dumps(profile, indent=4))) - chromaSubsampling = chromaSubsamplingMap[base['chromaSubsampling'][0]] - else: - chromaSubsampling = None - - if chromaSubsampling is not None: - formatModifiers.append(chromaSubsampling) - - bitDepthMap = genAliasValues('VkVideoComponentBitDepthFlagBitsKHR', { - "VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR": 8, - "VK_VIDEO_COMPONENT_BIT_DEPTH_10_BIT_KHR": 10, - "VK_VIDEO_COMPONENT_BIT_DEPTH_12_BIT_KHR": 12 - }) - if 'lumaBitDepth' in base: - if len(base['lumaBitDepth']) != 1: - Log.f("Expected lumaBitDepth to only contain a single value in video profile:\n{0}".format(json.dumps(profile, indent=4))) - if base['lumaBitDepth'][0] not in bitDepthMap: - Log.f("Unrecognized lumaBitDepth in profile:\n{0}".format(json.dumps(profile, indent=4))) - lumaBitDepth = bitDepthMap[base['lumaBitDepth'][0]] - else: - lumaBitDepth = None - - if chromaSubsampling != 'monochrome' and 'chromaBitDepth' in base: - if len(base['chromaBitDepth']) != 1: - Log.f("Expected chromaBitDepth to only contain a single value in video profile:\n{0}".format(json.dumps(profile, indent=4))) - if base['chromaBitDepth'][0] not in bitDepthMap: - Log.f("Unrecognized chromaBitDepth in profile:\n{0}".format(json.dumps(profile, indent=4))) - chromaBitDepth = bitDepthMap[base['chromaBitDepth'][0]] - else: - # For monochrome chromaBitDepth is ignored - # This case works also if lumaBitDepth is None because it was not present - chromaBitDepth = lumaBitDepth - - if lumaBitDepth == chromaBitDepth: - if lumaBitDepth is not None: - formatModifiers.append("{0}-bit".format(lumaBitDepth)) - else: - formatModifiers.append("{0}:{1}-bit".format(lumaBitDepth if lumaBitDepth is not None else "*", - chromaBitDepth if chromaBitDepth is not None else "*")) - - # If there is format information, then include it in the video profile name in parantheses - if len(formatModifiers) > 0: - profileName += " ({0})".format(" ".join(formatModifiers)) - - for profileStruct in videoCodec.profileStructs.values(): - profileStructData = None - if profileStruct.struct in profile: - profileStructData = profile[profileStruct.struct] - else: - # Check also for possible aliases - for alias in self.structs[profileStruct.struct].aliases: - if alias in profile: - profileStructData = profile[alias] - - if profileStructData is None: - # Profile struct is not present, this is a "wildcard" video profile definition - continue - - for profileStructMember in profileStruct.members.values(): - if not profileStructMember.name in profileStructData: - # Profile struct member is not present, this is a "wildcard" video profile definition - continue - - profileStructMemberValue = profileStructData[profileStructMember.name] - if isinstance(profileStructMemberValue, bool): - profileStructMemberValue = 'VK_TRUE' if profileStructMemberValue else 'VK_FALSE' - if profileStructMemberValue not in profileStructMember.values: - Log.f("Unrecognized profile struct member value for '{0}::{1}' in video profile:\n{2}".format(profileStruct.struct, profileStructMember.name, json.dumps(profile, indent=4))) - - # Append codec-specific profile information to the profile name - profileName += " {0}".format(profileStructMember.values[profileStructMemberValue]) - - return profileName - - def overwrite(self, structName, memberName, invalid_values, correct_value): - if structName in self.structs: - if (self.structs[structName].members[memberName].limittype == None or - self.structs[structName].members[memberName].limittype in invalid_values): - self.structs[structName].members[memberName].limittype = correct_value - elif (self.structs[structName].members[memberName].limittype != correct_value): - Log.w("Profiles is overwriting {0}::{1} to {2}, but current XML value is {3}".format(structName, memberName, correct_value, self.structs[structName].members[memberName].limittype)) - - def applyWorkarounds(self): - if self.headerVersionNumber.patch < 207: # vk.xml declares maxColorAttachments with 'bitmask' limittype before header 207 - self.structs['VkPhysicalDeviceLimits'].members['maxColorAttachments'].limittype = 'max' - - # TODO: We currently have to apply workarounds due to "noauto" limittypes and other bugs related to limittypes in the vk.xml - # These can only be solved permanently if we make modifications to the registry xml itself - self.overwrite('VkPhysicalDeviceLimits', 'subPixelPrecisionBits', ['noauto'], 'bits') - self.overwrite('VkPhysicalDeviceLimits', 'subTexelPrecisionBits', ['noauto'], 'bits') - self.overwrite('VkPhysicalDeviceLimits', 'mipmapPrecisionBits', ['noauto'], 'bits') - self.overwrite('VkPhysicalDeviceLimits', 'viewportSubPixelBits', ['noauto'], 'bits') - self.overwrite('VkPhysicalDeviceLimits', 'subPixelInterpolationOffsetBits', ['noauto'], 'bits') - self.overwrite('VkPhysicalDeviceLimits', 'minMemoryMapAlignment', ['noauto'], 'max,pot') - self.overwrite('VkPhysicalDeviceLimits', 'minTexelBufferOffsetAlignment', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceLimits', 'minUniformBufferOffsetAlignment', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceLimits', 'minStorageBufferOffsetAlignment', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceLimits', 'optimalBufferCopyOffsetAlignment', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceLimits', 'optimalBufferCopyRowPitchAlignment', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceLimits', 'nonCoherentAtomSize', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceLimits', 'timestampPeriod', ['noauto', 'min,mul'], 'exact') # resolve https://github.com/KhronosGroup/Vulkan-Profiles/issues/769 - self.overwrite('VkPhysicalDeviceLimits', 'bufferImageGranularity', ['noauto'], 'min,mul') - self.overwrite('VkPhysicalDeviceLimits', 'pointSizeGranularity', ['max'], 'min,mul') - self.overwrite('VkPhysicalDeviceLimits', 'lineWidthGranularity', ['max'], 'min,mul') - self.overwrite('VkPhysicalDeviceLimits', 'strictLines', ['noauto', 'bitmask', 'exact'], 'max') - self.overwrite('VkPhysicalDeviceLimits', 'standardSampleLocations', ['noauto', 'bitmask', 'exact'], 'max') - - self.overwrite('VkPhysicalDeviceSparseProperties', 'residencyAlignedMipSize', ['bitmask', 'not'], 'min') - - self.overwrite('VkPhysicalDeviceVulkan11Properties', 'deviceUUID', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan11Properties', 'driverUUID', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan11Properties', 'deviceLUID', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan11Properties', 'deviceNodeMask', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan11Properties', 'deviceLUIDValid', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan11Properties', 'subgroupSize', ['None'], 'max,pot') - self.overwrite('VkPhysicalDeviceVulkan11Properties', 'pointClippingBehavior', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan11Properties', 'protectedNoFault', ['None'], 'exact') - - self.overwrite('VkPhysicalDeviceVulkan12Properties', 'driverID', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceVulkan12Properties', 'driverName', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceVulkan12Properties', 'driverInfo', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceVulkan12Properties', 'conformanceVersion', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceVulkan12Properties', 'denormBehaviorIndependence', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan12Properties', 'roundingModeIndependence', ['None'], 'exact') - - self.overwrite('VkPhysicalDeviceVulkan13Properties', 'storageTexelBufferOffsetAlignmentBytes', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceVulkan13Properties', 'storageTexelBufferOffsetSingleTexelAlignment', ['noauto'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan13Properties', 'uniformTexelBufferOffsetAlignmentBytes', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceVulkan13Properties', 'uniformTexelBufferOffsetSingleTexelAlignment', ['noauto'], 'exact') - self.overwrite('VkPhysicalDeviceVulkan13Properties', 'minSubgroupSize', ['min'], 'min,pot') - self.overwrite('VkPhysicalDeviceVulkan13Properties', 'maxSubgroupSize', ['max'], 'max,pot') - - self.overwrite('VkPhysicalDeviceVulkan14Properties', 'maxCombinedImageSamplerDescriptorCount', ['None'], 'max') - - self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'storageTexelBufferOffsetAlignmentBytes', ['None'], 'min,pot') - self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'storageTexelBufferOffsetSingleTexelAlignment', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'uniformTexelBufferOffsetAlignmentBytes', ['None'], 'min,pot') - self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'uniformTexelBufferOffsetSingleTexelAlignment', ['None'], 'exact') - - self.overwrite('VkPhysicalDeviceProperties', 'apiVersion', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceProperties', 'driverVersion', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceProperties', 'vendorID', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceProperties', 'deviceID', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceProperties', 'deviceType', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceProperties', 'deviceName', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceProperties', 'pipelineCacheUUID', ['None'], 'noauto') - - self.overwrite('VkPhysicalDeviceToolProperties', 'name', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceToolProperties', 'version', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceToolProperties', 'purposes', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceToolProperties', 'description', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceToolProperties', 'layer', ['None'], 'noauto') - - self.overwrite('VkPhysicalDeviceSubgroupSizeControlProperties', 'minSubgroupSize', ['None'], 'min,pot') - self.overwrite('VkPhysicalDeviceSubgroupSizeControlProperties', 'maxSubgroupSize', ['None'], 'max,pot') - - self.overwrite('VkPhysicalDeviceDriverProperties', 'driverID', ['noauto'], 'exact') - self.overwrite('VkPhysicalDeviceDriverProperties', 'driverName', ['noauto'], 'exact') - self.overwrite('VkPhysicalDeviceDriverProperties', 'driverInfo', ['noauto'], 'exact') - self.overwrite('VkPhysicalDeviceDriverProperties', 'conformanceVersion', ['noauto'], 'exact') - - self.overwrite('VkPhysicalDeviceIDProperties', 'deviceUUID', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceIDProperties', 'driverUUID', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceIDProperties', 'deviceLUID', ['None', 'noauto'], 'max') - self.overwrite('VkPhysicalDeviceIDProperties', 'deviceNodeMask', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceIDProperties', 'deviceLUIDValid', ['None', 'noauto'], 'max') - - self.overwrite('VkPhysicalDeviceSubgroupProperties', 'subgroupSize', ['None'], 'max,pot') - - self.overwrite('VkPhysicalDevicePointClippingProperties', 'pointClippingBehavior', ['None'], 'exact') - - self.overwrite('VkPhysicalDeviceProtectedMemoryProperties', 'protectedNoFault', ['None'], 'exact') - - self.overwrite('VkPhysicalDeviceFloatControlsProperties', 'denormBehaviorIndependence', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceFloatControlsProperties', 'roundingModeIndependence', ['None'], 'exact') - - self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'storageTexelBufferOffsetSingleTexelAlignment', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'uniformTexelBufferOffsetSingleTexelAlignment', ['None'], 'exact') - - self.overwrite('VkPhysicalDevicePortabilitySubsetPropertiesKHR', 'minVertexInputBindingStrideAlignment', ['None'], 'min,pot') - - self.overwrite('VkPhysicalDeviceFragmentShadingRatePropertiesKHR', 'maxFragmentShadingRateAttachmentTexelSizeAspectRatio', ['None'], 'max,pot') - self.overwrite('VkPhysicalDeviceFragmentShadingRatePropertiesKHR', 'maxFragmentSizeAspectRatio', ['None'], 'max,pot') - self.overwrite('VkPhysicalDeviceFragmentShadingRatePropertiesKHR', 'maxFragmentShadingRateCoverageSamples', ['None'], 'max') - - self.overwrite('VkPhysicalDeviceRayTracingPipelinePropertiesKHR', 'shaderGroupHandleSize', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceRayTracingPipelinePropertiesKHR', 'shaderGroupBaseAlignment', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceRayTracingPipelinePropertiesKHR', 'shaderGroupHandleCaptureReplaySize', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceRayTracingPipelinePropertiesKHR', 'shaderGroupHandleAlignment', ['None'], 'min,pot') - - self.overwrite('VkPhysicalDeviceFragmentShadingRatePropertiesKHR', 'maxFragmentShadingRateRasterizationSamples', ['None'], 'max') - - self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'primitiveOverestimationSize', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'extraPrimitiveOverestimationSizeGranularity', ['None'], 'min,mul') - self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'conservativePointAndLineRasterization', ['None', 'bitmask'], 'max') - self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'degenerateTrianglesRasterized', ['None'], 'exact') - self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'degenerateLinesRasterized', ['None'], 'exact') - - self.overwrite('VkPhysicalDeviceLineRasterizationPropertiesEXT', 'lineSubPixelPrecisionBits', ['None'], 'bits') - - self.overwrite('VkPhysicalDeviceTransformFeedbackPropertiesEXT', 'maxTransformFeedbackBufferDataStride', ['None'], 'max') - - self.overwrite('VkPhysicalDeviceExternalMemoryHostPropertiesEXT', 'minImportedHostPointerAlignment', ['None'], 'min,pot') - - self.overwrite('VkPhysicalDevicePCIBusInfoPropertiesEXT', 'pciDomain', ['None'], 'noauto') - self.overwrite('VkPhysicalDevicePCIBusInfoPropertiesEXT', 'pciBus', ['None'], 'noauto') - self.overwrite('VkPhysicalDevicePCIBusInfoPropertiesEXT', 'pciDevice', ['None'], 'noauto') - self.overwrite('VkPhysicalDevicePCIBusInfoPropertiesEXT', 'pciFunction', ['None'], 'noauto') - - self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'hasPrimary', ['None', 'bitmask'], 'max') - self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'hasRender', ['None', 'bitmask'], 'max') - self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'primaryMajor', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'primaryMinor', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'renderMajor', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'renderMinor', ['None'], 'noauto') - - self.overwrite('VkPhysicalDeviceFragmentDensityMap2PropertiesEXT', 'subsampledLoads', ['noauto'], 'exact') - self.overwrite('VkPhysicalDeviceFragmentDensityMap2PropertiesEXT', 'subsampledCoarseReconstructionEarlyAccess', ['noauto'], 'exact') - - self.overwrite('VkPhysicalDeviceSampleLocationsPropertiesEXT', 'sampleLocationSubPixelBits', ['noauto'], 'bits') - - self.overwrite('VkPhysicalDeviceRobustness2PropertiesEXT', 'robustStorageBufferAccessSizeAlignment', ['noauto'], 'min,pot') - self.overwrite('VkPhysicalDeviceRobustness2PropertiesEXT', 'robustUniformBufferAccessSizeAlignment', ['noauto'], 'min,pot') - - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'shaderEngineCount', ['max'], 'exact') - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'shaderArraysPerEngineCount', ['max'], 'exact') - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'computeUnitsPerShaderArray', ['max'], 'exact') - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'simdPerComputeUnit', ['max'], 'exact') - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'wavefrontsPerSimd', ['max'], 'exact') - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'sgprsPerSimd', ['max'], 'exact') - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'sgprAllocationGranularity', ['noauto'], 'min,mul') - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'vgprsPerSimd', ['max'], 'exact') - self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'vgprAllocationGranularity', ['noauto'], 'min,mul') - - self.overwrite('VkPhysicalDeviceSubpassShadingPropertiesHUAWEI', 'maxSubpassShadingWorkgroupSizeAspectRatio', ['noauto'], 'max,pot') - - self.overwrite('VkPhysicalDeviceRayTracingPropertiesNV', 'shaderGroupHandleSize', ['noauto'], 'exact') - self.overwrite('VkPhysicalDeviceRayTracingPropertiesNV', 'shaderGroupBaseAlignment', ['noauto'], 'exact') - - self.overwrite('VkPhysicalDeviceShadingRateImagePropertiesNV', 'shadingRateTexelSize', ['noauto'], 'exact') - - self.overwrite('VkPhysicalDeviceMeshShaderPropertiesNV', 'meshOutputPerVertexGranularity', ['noauto'], 'min,mul') - self.overwrite('VkPhysicalDeviceMeshShaderPropertiesNV', 'meshOutputPerPrimitiveGranularity', ['noauto'], 'min,mul') - - self.overwrite('VkPhysicalDevicePipelineRobustnessPropertiesEXT', 'defaultRobustnessStorageBuffers', ['noauto'], 'exact') - self.overwrite('VkPhysicalDevicePipelineRobustnessPropertiesEXT', 'defaultRobustnessUniformBuffers', ['noauto'], 'exact') - self.overwrite('VkPhysicalDevicePipelineRobustnessPropertiesEXT', 'defaultRobustnessVertexInputs', ['noauto'], 'exact') - self.overwrite('VkPhysicalDevicePipelineRobustnessPropertiesEXT', 'defaultRobustnessImages', ['noauto'], 'exact') - - self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV', 'minSequencesCountBufferOffsetAlignment', ['noauto'], 'min') - self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV', 'minSequencesIndexBufferOffsetAlignment', ['noauto'], 'min') - self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV', 'minIndirectCommandsBufferOffsetAlignment', ['noauto'], 'min') - - self.overwrite('VkPhysicalDeviceFragmentDensityMapOffsetPropertiesQCOM', 'fragmentDensityOffsetGranularity', ['max'], 'min,mul') - - self.overwrite('VkPhysicalDeviceSchedulingControlsPropertiesARM', 'schedulingControlsFlags', ['None'], 'bitmask') - - self.overwrite('VkPhysicalDeviceExternalFormatResolvePropertiesANDROID', 'nullColorAttachmentWithExternalFormatResolve', ['noauto', 'not'], 'min') - - self.overwrite('VkPhysicalDeviceRenderPassStripedPropertiesARM', 'renderPassStripeGranularity', ['None', 'min', 'max,mul'], 'min,mul') - self.overwrite('VkPhysicalDeviceRenderPassStripedPropertiesARM', 'maxRenderPassStripes', ['None'], 'max') - - self.overwrite('VkPhysicalDeviceMaintenance6PropertiesKHR', 'maxCombinedImageSamplerDescriptorCount', ['None'], 'max') - - self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT', 'supportedIndirectCommandsInputModes', ['None'], 'bitmask') - self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT', 'supportedIndirectCommandsShaderStages', ['None'], 'bitmask') - self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT', 'supportedIndirectCommandsShaderStagesPipelineBinding', ['None'], 'bitmask') - self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT', 'supportedIndirectCommandsShaderStagesShaderBinding', ['None'], 'bitmask') - - self.overwrite('VkPhysicalDeviceCooperativeVectorPropertiesNV', 'maxCooperativeVectorComponents', ['None'], 'max') - - self.overwrite('VkPhysicalDeviceGpaPropertiesAMD', 'flags', ['noauto'], 'bitmask') - - # TODO: The registry xml is also missing limittype definitions for format and queue family properties - # For now we just add the important ones, this needs a larger overhaul in the vk.xml - self.overwrite('VkFormatProperties', 'linearTilingFeatures', ['None'], 'bitmask') - self.overwrite('VkFormatProperties', 'optimalTilingFeatures', ['None'], 'bitmask') - self.overwrite('VkFormatProperties', 'bufferFeatures', ['None'], 'bitmask') - self.overwrite('VkFormatProperties3', 'linearTilingFeatures', ['None'], 'bitmask') - self.overwrite('VkFormatProperties3', 'optimalTilingFeatures', ['None'], 'bitmask') - self.overwrite('VkFormatfProperties3', 'bufferFeatures', ['None'], 'bitmask') - - self.overwrite('VkQueueFamilyProperties', 'queueFlags', ['None'], 'bitmask') - self.overwrite('VkQueueFamilyProperties', 'queueCount', ['None'], 'max') - self.overwrite('VkQueueFamilyProperties', 'timestampValidBits', ['None'], 'bits') - self.overwrite('VkQueueFamilyProperties', 'minImageTransferGranularity', ['None'], 'min,mul') - - self.overwrite('VkSparseImageFormatProperties', 'aspectMask', ['None'], 'bitmask') - self.overwrite('VkSparseImageFormatProperties', 'imageGranularity', ['None'], 'min,mul') - self.overwrite('VkSparseImageFormatProperties', 'flags', ['None'], 'bitmask') - - self.overwrite('VkPhysicalDeviceDescriptorBufferTensorPropertiesARM', 'tensorCaptureReplayDescriptorDataSize', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceDescriptorBufferTensorPropertiesARM', 'tensorViewCaptureReplayDescriptorDataSize', ['None'], 'noauto') - self.overwrite('VkPhysicalDeviceDescriptorBufferTensorPropertiesARM', 'tensorDescriptorSize', ['None'], 'max') - - # TODO: The registry xml contains some return structures that contain count + pointers to arrays - # While the script itself is prepared to drop those, as they are ill-formed, as return structures - # should never contain such pointers, some of the structures (e.g. 'VkVideoProfilesKHR') actually - # doesn't even have the proper 'len' attribute to be able to detect the dynamic array - # Hence here we simply remove such "disallow-listed" structs so that they don't get in the way - self.structs.pop('VkDrmFormatModifierPropertiesListEXT', None) - self.structs.pop('VkDrmFormatModifierPropertiesList2EXT', None) - - def getExtensionPromotedToVersion(self, extensionName): - promotedTo = self.extensions[extensionName].promotedTo.copy() - version = None - while len(promotedTo) > 0: - target = promotedTo[0] - if target in self.extensions: - # Functionality was promoted to another extension, continue with that - promotedTo.remove(target) - promotedTo.extend(self.extensions[target].promotedTo) - elif target in self.versions: - # Found extension in a core API version, we're done - version = self.versions[target] - break - else: - # Version or extension is not included in the target API - promotedTo.remove(target) - return version - - def getExtensionPromotedToExtensionList(self, extensionName): - promotedTo = self.extensions[extensionName].promotedTo.copy() - extensions = [] - while len(promotedTo) > 0: - target = promotedTo[0] - if target in self.extensions: - # Functionality was promoted to another extension, add to list and continue with that - extensions.append(target) - promotedTo.remove(target) - promotedTo.extend(self.extensions[target].promotedTo) - else: - # Extension is not included in the target API or is a version, skip - promotedTo.remove(target) - return extensions - - def getChainableStructDef(self, name, extends): - structDef = self.structs.get(name) - if structDef == None: - Log.f("Structure '{0}' does not exist".format(name)) - if structDef.sType == None: - Log.f("Structure '{0}' is not chainable".format(name)) - if not extends in structDef.extends + [ name ]: - Log.f("Structure '{0}' does not extend '{1}'".format(name, extends)) - return structDef - - def evalArraySize(self, arraySize): - if isinstance(arraySize, str): - if arraySize in self.constants: - return int(self.constants[arraySize]) - else: - Log.f("Invalid array size '{0}'".format(arraySize)) - else: - return arraySize - - def getNonAliasTypeName(self, alias, types): - typeDef = types[alias] - if typeDef.isAlias: - for alias in typeDef.aliases: - if not types[alias].isAlias: - return alias - else: - return alias - - class VulkanProfileCapabilities(): def __init__(self, registry, json_profile_key, json_profile_value, json_capability_key, json_capabilities_list, merge_mode, doc_mode): self.blockName = json_capability_key @@ -6160,596 +4644,6 @@ def gen_publicImpl(self): gen = PUBLIC_IMPL_BODY return self.patch_code(gen) - -class VulkanProfilesSchemaGenerator(): - def __init__(self, registry): - self.registry = registry - self.schema = self.gen_schema() - - - def validate(self): - try: - import jsonschema - Log.i("Validating JSON profiles schema...") - jsonschema.Draft7Validator.check_schema(self.schema) - except ModuleNotFoundError: - Log.w("`jsonschema` module is not installed, schema validation skip") - - def generate(self, outSchema): - Log.i("Generating '{0}'...".format(outSchema)) - with open(outSchema, 'w') as f: - f.write(json.dumps(self.schema, indent=4)) - - - def gen_schema(self): - definitions = self.gen_baseDefinitions() - extensions = self.gen_extensions() - features = self.gen_features(definitions) - properties = self.gen_properties(definitions) - formats = self.gen_formats(definitions) - queueFamilies = self.gen_queueFamilies(definitions) - videoProfiles = self.gen_videoProfiles(definitions) - videoCapabilities = self.gen_videoCapabilities(definitions) - videoFormats = self.gen_videoFormats(definitions) - versionStr = str(self.registry.headerVersionNumber) - - return OrderedDict({ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.khronos.org/vulkan/profiles-0.8.2-{0}.json#".format(str(self.registry.headerVersionNumber.patch)), - "title": "Vulkan Profiles Schema for Vulkan {0}".format(versionStr), - "additionalProperties": True, - "required": [ - "capabilities", - "profiles" - ], - "definitions": definitions, - "properties": OrderedDict({ - "capabilities": OrderedDict({ - "description": "The block that specifies the list of capabilities sets.", - "type": "object", - "additionalProperties": OrderedDict({ - "type": "object", - "additionalProperties": False, - "properties": OrderedDict({ - "extensions": OrderedDict({ - "description": "The block that stores required extensions.", - "type": "object", - "additionalProperties": False, - "properties": extensions - }), - "features": OrderedDict({ - "description": "The block that stores features requirements.", - "type": "object", - "additionalProperties": False, - "properties": features - }), - "properties": OrderedDict({ - "description": "The block that stores properties requirements.", - "type": "object", - "additionalProperties": False, - "properties": properties - }), - "formats": OrderedDict({ - "description": "The block that store formats capabilities definitions.", - "type": "object", - "additionalProperties": False, - "properties": formats - }), - "queueFamiliesProperties": OrderedDict({ - "type": "array", - "uniqueItems": True, - "items": OrderedDict({ - "type": "object", - "additionalProperties": False, - "properties": queueFamilies - }) - }), - "videoProfiles": OrderedDict({ - "type": "array", - "uniqueItems": True, - "items": OrderedDict({ - "type": "object", - "additionalProperties": False, - "properties": OrderedDict({ - "profile": OrderedDict({ - "type": "object", - "additionalProperties": False, - "properties": videoProfiles - }), - "capabilities": OrderedDict({ - "type": "object", - "addationalProperties": False, - "properties": videoCapabilities - }), - "formats": OrderedDict({ - "type": "array", - "uniqueItems": True, - "items": OrderedDict({ - "type": "object", - "additionalProperties": False, - "properties": videoFormats - }) - }) - }) - }) - }) - }) - }) - }), - "profiles": OrderedDict({ - "description": "The list of profile definitions.", - "type": "object", - "additionalProperties": False, - "patternProperties": OrderedDict({ - "^VP_[A-Z0-9]+_[A-Za-z0-9_]+": OrderedDict({ - "type": "object", - "additionalProperties": False, - "required": [ - "label", - "description", - "version", - "api-version", - "capabilities" - ], - "properties": OrderedDict({ - "version": OrderedDict({ - "description": "The revision of the profile.", - "type": "integer" - }), - "label": OrderedDict({ - "description": "The label used to present the profile to the Vulkan developer.", - "type": "string" - }), - "description": OrderedDict({ - "description": "The description of the profile.", - "type": "string" - }), - "status": OrderedDict({ - "description": "The developmet status of the profile: ALPHA, BETA, STABLE or DEPRECATED.", - "$ref": "#/definitions/status" - }), - "api-version": OrderedDict({ - "description": "The Vulkan API version against which the profile is written.", - "type": "string", - "pattern": "^[0-9]+.[0-9]+.[0-9]+$" - }), - "contributors": OrderedDict({ - "type": "object", - "description": "The list of contributors of the profile.", - "additionalProperties": OrderedDict({ - "$ref": "#/definitions/contributor" - }) - }), - "history": OrderedDict({ - "description": "The version history of the profile file", - "type": "array", - "uniqueItems": True, - "minItems": 1, - "items": OrderedDict({ - "type": "object", - "required": [ - "revision", - "date", - "author", - "comment" - ], - "properties": OrderedDict({ - "revision": OrderedDict({ - "type": "integer" - }), - "date": OrderedDict({ - "type": "string", - "pattern": "((?:19|20)\\d\\d)-(0?[1-9]|1[012])-([12][0-9]|3[01]|0?[1-9])" - }), - "author": OrderedDict({ - "type": "string" - }), - "comment": OrderedDict({ - "type": "string" - }) - }) - }) - }), - "profiles": OrderedDict({ - "description": "The list of required profiles by the profile.", - "type": "array", - "additionalProperties": False, - "uniqueItems": True, - "items": OrderedDict({ - "type": "string" - }) - }), - "capabilities": OrderedDict({ - "description": "The list of required capability sets that can be referenced by a profile.", - "type": "array", - "uniqueItems": True, - "items": OrderedDict({ - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "uniqueItems": True, - "items": OrderedDict({ - "type": "string" - }) - } - ] - }) - }), - "optionals": OrderedDict({ - "description": "The list of optional capability sets that can be referenced by a profile.", - "type": "array", - "uniqueItems": True, - "items": OrderedDict({ - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "uniqueItems": True, - "items": OrderedDict({ - "type": "string" - }) - } - ] - }) - }), - "fallback": OrderedDict({ - "description": "The list of profiles recommended if the checked profile is not supported by the platform.", - "type": "array", - "additionalProperties": False, - "uniqueItems": True, - "items": OrderedDict({ - "type": "string" - }) - }), - "contributors": OrderedDict({ - "type": "object", - "description": "The list of contributors of the profile.", - "additionalProperties": OrderedDict({ - "$ref": "#/definitions/contributor" - }) - }), - "history": OrderedDict({ - "description": "The version history of the profile file", - "type": "array", - "uniqueItems": True, - "minItems": 1, - "items": OrderedDict({ - "type": "object", - "required": [ - "revision", - "date", - "author", - "comment" - ], - "properties": OrderedDict({ - "revision": OrderedDict({ - "type": "integer" - }), - "date": OrderedDict({ - "type": "string", - "pattern": "((?:19|20)\\d\\d)-(0?[1-9]|1[012])-([12][0-9]|3[01]|0?[1-9])" - }), - "author": OrderedDict({ - "type": "string" - }), - "comment": OrderedDict({ - "type": "string" - }) - }) - }) - }), - }) - }) - }) - }) - }) - }) - - - def gen_baseDefinitions(self): - gen = OrderedDict({ - "status": OrderedDict({ - "description": "The development status of the setting. When missing, this property is inherited from parent nodes. If no parent node defines it, the default value is 'STABLE'.", - "type": "string", - "enum": [ "ALPHA", "BETA", "STABLE", "DEPRECATED" ] - }), - "contributor": OrderedDict({ - "type": "object", - "additionalProperties": False, - "required": [ - "company" - ], - "properties": OrderedDict({ - "company": OrderedDict({ - "type": "string" - }), - "email": OrderedDict({ - "type": "string", - "pattern": "^[A-Za-z0-9_.]+@[a-zA-Z0-9-].[a-zA-Z0-9-.]+$" - }), - "github": OrderedDict({ - "type": "string", - "pattern": "^[A-Za-z0-9_-]+$" - }), - "contact": OrderedDict({ - "type": "boolean" - }) - }) - }), - "uint8_t": OrderedDict({ - "type": "integer", - "minimum": 0, - "maximum": 255 - }), - "int32_t": OrderedDict({ - "type": "integer", - "minimum": -2147483648, - "maximum": 2147483647 - }), - "uint32_t": OrderedDict({ - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }), - "int64_t": OrderedDict({ - "type": "integer" - }), - "uint64_t": OrderedDict({ - "type": "integer", - "minimum": 0 - }), - "VkDeviceSize": OrderedDict({ - "type": "integer", - "minimum": 0 - }), - "char": { - "type": "string" - }, - "float": { - "type": "number" - }, - "size_t": OrderedDict({ - "type": "integer", - "minimum": 0 - }) - }) - return gen - - - def gen_extensions(self): - gen = OrderedDict() - for extName in sorted(self.registry.extensions.keys()): - gen[extName] = { "type": "integer" } - return gen - - - def gen_type(self, type, definitions): - if type == 'VkBool32': - # Simple boolean - gen = { "type": "boolean" } - else: - # All other types are referenced - gen = { "$ref": "#/definitions/" + type } - - if gen.get("$ref") != None: - # Generate referenced type, if needed - if type in definitions: - # Nothing to do, already defined - pass - elif type in self.registry.structs: - # Generate structure definition - self.gen_struct(type, definitions) - elif type in self.registry.enums: - # Generate enum definition - self.gen_enum(type, definitions) - elif type in self.registry.bitmasks: - # Generate bitmask definition - self.gen_bitmask(type, definitions) - else: - Log.f("Unknown type '{0}'".format(type)) - - return gen - - - def gen_array(self, type, size, sizeCap, definitions): - arraySize = self.registry.evalArraySize(size) - if isinstance(arraySize, list) and len(arraySize) == 1: - # This is the last dimension of a multi-dimensional array - # Treat it as one-dimensional from here on - arraySize = arraySize[0] - - if type == 'char': - # Character arrays should be handled as strings - # We assume all are null-terminated, even though the vk.xml doesn't specify that - # everywhere, but that's probably a bug rather than intentional - return OrderedDict({ - "type": "string", - "maxLength": arraySize - 1 - }) - elif isinstance(arraySize, list): - # Multi-dimensional array - return OrderedDict({ - "type": "array", - "items": self.gen_array(type, arraySize[1:], None, definitions), - "uniqueItems": False, - # We don't have information from vk.xml to be able to tell what's the minimum - # number of items that may need to be specified - # "minItems": arraySize[0], - "maxItems": arraySize[0] - }) - else: - # One-dimensional array - return OrderedDict({ - "type": "array", - "items": self.gen_type(type, definitions), - "uniqueItems": False, - # We don't have information from vk.xml to be able to tell what's the minimum - # number of items that may need to be specified - # "minItems": arraySize, - "maxItems": arraySize if arraySize is not None else sizeCap - }) - - - def gen_enum(self, name, definitions): - enumDef = self.registry.enums[name] - - if len(enumDef.values) > 0: - values = sorted(enumDef.values) - else: - # If the enum has no values then we must add a dummy one - # in order to produce a valid JSON schema - values = [ 0 ] - - # Generate definition - definitions[name] = OrderedDict({ - "enum": values - }) - - - def gen_bitmask(self, name, definitions): - bitmaskDef = self.registry.bitmasks[name] - - if bitmaskDef.bitsType != None: - # Also generate corresponding bits enum - self.gen_enum(bitmaskDef.bitsType.name, definitions) - itemType = { "$ref": "#/definitions/" + bitmaskDef.bitsType.name } - else: - # If the bitmask has no bits type then we must add a dummy - # item type with a single dummy value - itemType = { "enum": [ 0 ] } - - # Generate definition - definitions[name] = OrderedDict({ - "type": "array", - "items": itemType, - "uniqueItems": True - }) - - - def gen_struct(self, name, definitions): - structDef = self.registry.structs[name] - - # Generate member data - members = OrderedDict() - for memberName in sorted(structDef.members.keys()): - memberDef = structDef.members[memberName] - - if memberDef.type in self.registry.externalTypes and not memberDef.type in definitions: - # Members with types defined externally and aren't manually defined are ignored - Log.w("Ignoring member '{0}' in struct '{1}' with external type '{2}'".format(memberName, name, memberDef.type)) - continue - - if memberDef.isArray: - if memberDef.arraySizeMember != None and name not in struct_with_valid_dynamic_array and name not in struct_with_dynamic_array_size_cap: - # This array is a dynamic one (count + pointer to array) which is not allowed - # for return structures. Such structures hence are ill-formed and shouldn't - # be included in the schema - Log.w("Ignoring member '{0}' in struct '{1}' containing ill-formed pointer to array".format(memberName, name)) - else: - if memberDef.arraySizeMember != None and name in struct_with_dynamic_array_size_cap: - Log.w("Member '{0}' in struct '{1}' is a pointer to array with a known maximum size, it will be ignored in the API library, but supported in the layer".format(memberName, name)) - members[memberDef.name] = self.gen_array(memberDef.type, memberDef.arraySize, memberDef.arraySizeCap, definitions) - else: - members[memberDef.name] = self.gen_type(memberDef.type, definitions) - - # Generate definition - definitions[name] = OrderedDict({ - "type": "object", - "additionalProperties": False, - "properties": members - }) - - - def gen_structChainDefinitions(self, basename, definitions): - structNames = [ basename ] - if basename + '2' in self.registry.structs: - # Structure has version 2 which is extensible - basename += '2' - structNames.append(basename) - - # Collect unique chainable structures (ignoring aliases) - for structName in sorted(self.registry.structs.keys()): - structDef = self.registry.structs[structName] - if not structDef.isAlias and basename in structDef.extends: - structNames.append(structName) - - # Generate structure definitions and references - gen = OrderedDict() - for structName in structNames: - # Add structure definition and reference - self.gen_struct(structName, definitions) - gen[structName] = { "$ref": "#/definitions/" + structName } - - # Add structure references for all alises - for alias in self.registry.structs[structName].aliases: - if alias != structName: - gen[alias] = gen[structName] - - return gen - - - def gen_features(self, definitions): - return self.gen_structChainDefinitions("VkPhysicalDeviceFeatures", definitions) - - - def gen_properties(self, definitions): - return self.gen_structChainDefinitions("VkPhysicalDeviceProperties", definitions) - - - def gen_formats(self, definitions): - # Add definition for format properties - definitions['formatProperties'] = OrderedDict({ - "type": "object", - "additionalProperties": False, - "properties": self.gen_structChainDefinitions("VkFormatProperties", definitions) - }) - - # Generate references to the format properties definition for each format - gen = OrderedDict() - for format in sorted(self.registry.enums['VkFormat'].values): - gen[format] = OrderedDict({ - "$ref": "#/definitions/formatProperties" - }) - return gen - - - def gen_queueFamilies(self, definitions): - return self.gen_structChainDefinitions("VkQueueFamilyProperties", definitions) - - - def gen_videoProfiles(self, definitions): - # We do not want to include usage hint structures in the schema - # as those are only usage scenario customizations and do not affect capabilities - excludedSet = { - "VkVideoDecodeUsageInfoKHR", - "VkVideoEncodeUsageInfoKHR" - } - videoProfiles = self.gen_structChainDefinitions("VkVideoProfileInfoKHR", definitions) - for excluded in excludedSet: - excluded = self.registry.getNonAliasTypeName(excluded, self.registry.structs) - if excluded in videoProfiles: - del videoProfiles[excluded] - # Check also any aliases - for alias in self.registry.structs[excluded].aliases: - if alias in videoProfiles: - del videoProfiles[excluded] - - return videoProfiles - - - def gen_videoCapabilities(self, definitions): - return self.gen_structChainDefinitions("VkVideoCapabilitiesKHR", definitions) - - - def gen_videoFormats(self, definitions): - return self.gen_structChainDefinitions("VkVideoFormatPropertiesKHR", definitions) - - DOC_MD_HEADER = '''

LunarG

@@ -6763,7 +4657,6 @@ def gen_videoFormats(self, definitions): [4]: https://creativecommons.org/licenses/by-nd/4.0/ ''' - class VulkanProfilesDocGenerator(): def __init__(self, registry, profiles_files): self.registry = registry diff --git a/scripts/source/generate_schema.py b/scripts/source/generate_schema.py new file mode 100644 index 00000000..c0d401a8 --- /dev/null +++ b/scripts/source/generate_schema.py @@ -0,0 +1,613 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2021-2026 LunarG, Inc. +# Copyright (c) 2023-2024 RasterGrid Kft. +# +# 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. +# +# Authors: +# - Daniel Rakos +# - Christophe Riccio + +import json +from typing import OrderedDict + +from source.vulkan_registry import VulkanRegistry, struct_with_valid_dynamic_array, struct_with_dynamic_array_size_cap +from source.log import Log + +class VulkanProfilesSchemaGenerator(): + def __init__(self, registry): + self.registry = registry + self.schema = self.gen_schema() + + def validate(self): + try: + import jsonschema + Log.i("Validating JSON profiles schema...") + jsonschema.Draft7Validator.check_schema(self.schema) + except ModuleNotFoundError: + Log.w("`jsonschema` module is not installed, schema validation skip") + + def generate(self, outSchema): + Log.i("Generating '{0}'...".format(outSchema)) + with open(outSchema, 'w') as f: + f.write(json.dumps(self.schema, indent=4)) + + + def gen_schema(self): + definitions = self.gen_baseDefinitions() + extensions = self.gen_extensions() + features = self.gen_features(definitions) + properties = self.gen_properties(definitions) + formats = self.gen_formats(definitions) + queueFamilies = self.gen_queueFamilies(definitions) + videoProfiles = self.gen_videoProfiles(definitions) + videoCapabilities = self.gen_videoCapabilities(definitions) + videoFormats = self.gen_videoFormats(definitions) + versionStr = str(self.registry.headerVersionNumber) + + return OrderedDict({ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.khronos.org/vulkan/profiles-0.8.2-{0}.json#".format(str(self.registry.headerVersionNumber.patch)), + "title": "Vulkan Profiles Schema for Vulkan {0}".format(versionStr), + "additionalProperties": True, + "required": [ + "capabilities", + "profiles" + ], + "definitions": definitions, + "properties": OrderedDict({ + "capabilities": OrderedDict({ + "description": "The block that specifies the list of capabilities sets.", + "type": "object", + "additionalProperties": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": OrderedDict({ + "extensions": OrderedDict({ + "description": "The block that stores required extensions.", + "type": "object", + "additionalProperties": False, + "properties": extensions + }), + "features": OrderedDict({ + "description": "The block that stores features requirements.", + "type": "object", + "additionalProperties": False, + "properties": features + }), + "properties": OrderedDict({ + "description": "The block that stores properties requirements.", + "type": "object", + "additionalProperties": False, + "properties": properties + }), + "formats": OrderedDict({ + "description": "The block that store formats capabilities definitions.", + "type": "object", + "additionalProperties": False, + "properties": formats + }), + "queueFamiliesProperties": OrderedDict({ + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": queueFamilies + }) + }), + "videoProfiles": OrderedDict({ + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": OrderedDict({ + "profile": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": videoProfiles + }), + "capabilities": OrderedDict({ + "type": "object", + "addationalProperties": False, + "properties": videoCapabilities + }), + "formats": OrderedDict({ + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": videoFormats + }) + }) + }) + }) + }) + }) + }) + }), + "profiles": OrderedDict({ + "description": "The list of profile definitions.", + "type": "object", + "additionalProperties": False, + "patternProperties": OrderedDict({ + "^VP_[A-Z0-9]+_[A-Za-z0-9_]+": OrderedDict({ + "type": "object", + "additionalProperties": False, + "required": [ + "label", + "description", + "version", + "api-version", + "capabilities" + ], + "properties": OrderedDict({ + "version": OrderedDict({ + "description": "The revision of the profile.", + "type": "integer" + }), + "label": OrderedDict({ + "description": "The label used to present the profile to the Vulkan developer.", + "type": "string" + }), + "description": OrderedDict({ + "description": "The description of the profile.", + "type": "string" + }), + "status": OrderedDict({ + "description": "The developmet status of the profile: ALPHA, BETA, STABLE or DEPRECATED.", + "$ref": "#/definitions/status" + }), + "api-version": OrderedDict({ + "description": "The Vulkan API version against which the profile is written.", + "type": "string", + "pattern": "^[0-9]+.[0-9]+.[0-9]+$" + }), + "contributors": OrderedDict({ + "type": "object", + "description": "The list of contributors of the profile.", + "additionalProperties": OrderedDict({ + "$ref": "#/definitions/contributor" + }) + }), + "history": OrderedDict({ + "description": "The version history of the profile file", + "type": "array", + "uniqueItems": True, + "minItems": 1, + "items": OrderedDict({ + "type": "object", + "required": [ + "revision", + "date", + "author", + "comment" + ], + "properties": OrderedDict({ + "revision": OrderedDict({ + "type": "integer" + }), + "date": OrderedDict({ + "type": "string", + "pattern": "((?:19|20)\\d\\d)-(0?[1-9]|1[012])-([12][0-9]|3[01]|0?[1-9])" + }), + "author": OrderedDict({ + "type": "string" + }), + "comment": OrderedDict({ + "type": "string" + }) + }) + }) + }), + "profiles": OrderedDict({ + "description": "The list of required profiles by the profile.", + "type": "array", + "additionalProperties": False, + "uniqueItems": True, + "items": OrderedDict({ + "type": "string" + }) + }), + "capabilities": OrderedDict({ + "description": "The list of required capability sets that can be referenced by a profile.", + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "string" + }) + } + ] + }) + }), + "optionals": OrderedDict({ + "description": "The list of optional capability sets that can be referenced by a profile.", + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "string" + }) + } + ] + }) + }), + "fallback": OrderedDict({ + "description": "The list of profiles recommended if the checked profile is not supported by the platform.", + "type": "array", + "additionalProperties": False, + "uniqueItems": True, + "items": OrderedDict({ + "type": "string" + }) + }), + "contributors": OrderedDict({ + "type": "object", + "description": "The list of contributors of the profile.", + "additionalProperties": OrderedDict({ + "$ref": "#/definitions/contributor" + }) + }), + "history": OrderedDict({ + "description": "The version history of the profile file", + "type": "array", + "uniqueItems": True, + "minItems": 1, + "items": OrderedDict({ + "type": "object", + "required": [ + "revision", + "date", + "author", + "comment" + ], + "properties": OrderedDict({ + "revision": OrderedDict({ + "type": "integer" + }), + "date": OrderedDict({ + "type": "string", + "pattern": "((?:19|20)\\d\\d)-(0?[1-9]|1[012])-([12][0-9]|3[01]|0?[1-9])" + }), + "author": OrderedDict({ + "type": "string" + }), + "comment": OrderedDict({ + "type": "string" + }) + }) + }) + }), + }) + }) + }) + }) + }) + }) + + + def gen_baseDefinitions(self): + gen = OrderedDict({ + "status": OrderedDict({ + "description": "The development status of the setting. When missing, this property is inherited from parent nodes. If no parent node defines it, the default value is 'STABLE'.", + "type": "string", + "enum": [ "ALPHA", "BETA", "STABLE", "DEPRECATED" ] + }), + "contributor": OrderedDict({ + "type": "object", + "additionalProperties": False, + "required": [ + "company" + ], + "properties": OrderedDict({ + "company": OrderedDict({ + "type": "string" + }), + "email": OrderedDict({ + "type": "string", + "pattern": "^[A-Za-z0-9_.]+@[a-zA-Z0-9-].[a-zA-Z0-9-.]+$" + }), + "github": OrderedDict({ + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$" + }), + "contact": OrderedDict({ + "type": "boolean" + }) + }) + }), + "uint8_t": OrderedDict({ + "type": "integer", + "minimum": 0, + "maximum": 255 + }), + "int32_t": OrderedDict({ + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }), + "uint32_t": OrderedDict({ + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }), + "int64_t": OrderedDict({ + "type": "integer" + }), + "uint64_t": OrderedDict({ + "type": "integer", + "minimum": 0 + }), + "VkDeviceSize": OrderedDict({ + "type": "integer", + "minimum": 0 + }), + "char": { + "type": "string" + }, + "float": { + "type": "number" + }, + "size_t": OrderedDict({ + "type": "integer", + "minimum": 0 + }) + }) + return gen + + + def gen_extensions(self): + gen = OrderedDict() + for extName in sorted(self.registry.extensions.keys()): + gen[extName] = { "type": "integer" } + return gen + + + def gen_type(self, type, definitions): + if type == 'VkBool32': + # Simple boolean + gen = { "type": "boolean" } + else: + # All other types are referenced + gen = { "$ref": "#/definitions/" + type } + + if gen.get("$ref") != None: + # Generate referenced type, if needed + if type in definitions: + # Nothing to do, already defined + pass + elif type in self.registry.structs: + # Generate structure definition + self.gen_struct(type, definitions) + elif type in self.registry.enums: + # Generate enum definition + self.gen_enum(type, definitions) + elif type in self.registry.bitmasks: + # Generate bitmask definition + self.gen_bitmask(type, definitions) + else: + Log.f("Unknown type '{0}'".format(type)) + + return gen + + + def gen_array(self, type, size, sizeCap, definitions): + arraySize = self.registry.evalArraySize(size) + if isinstance(arraySize, list) and len(arraySize) == 1: + # This is the last dimension of a multi-dimensional array + # Treat it as one-dimensional from here on + arraySize = arraySize[0] + + if type == 'char': + # Character arrays should be handled as strings + # We assume all are null-terminated, even though the vk.xml doesn't specify that + # everywhere, but that's probably a bug rather than intentional + return OrderedDict({ + "type": "string", + "maxLength": arraySize - 1 + }) + elif isinstance(arraySize, list): + # Multi-dimensional array + return OrderedDict({ + "type": "array", + "items": self.gen_array(type, arraySize[1:], None, definitions), + "uniqueItems": False, + # We don't have information from vk.xml to be able to tell what's the minimum + # number of items that may need to be specified + # "minItems": arraySize[0], + "maxItems": arraySize[0] + }) + else: + # One-dimensional array + return OrderedDict({ + "type": "array", + "items": self.gen_type(type, definitions), + "uniqueItems": False, + # We don't have information from vk.xml to be able to tell what's the minimum + # number of items that may need to be specified + # "minItems": arraySize, + "maxItems": arraySize if arraySize is not None else sizeCap + }) + + + def gen_enum(self, name, definitions): + enumDef = self.registry.enums[name] + + if len(enumDef.values) > 0: + values = sorted(enumDef.values) + else: + # If the enum has no values then we must add a dummy one + # in order to produce a valid JSON schema + values = [ 0 ] + + # Generate definition + definitions[name] = OrderedDict({ + "enum": values + }) + + + def gen_bitmask(self, name, definitions): + bitmaskDef = self.registry.bitmasks[name] + + if bitmaskDef.bitsType != None: + # Also generate corresponding bits enum + self.gen_enum(bitmaskDef.bitsType.name, definitions) + itemType = { "$ref": "#/definitions/" + bitmaskDef.bitsType.name } + else: + # If the bitmask has no bits type then we must add a dummy + # item type with a single dummy value + itemType = { "enum": [ 0 ] } + + # Generate definition + definitions[name] = OrderedDict({ + "type": "array", + "items": itemType, + "uniqueItems": True + }) + + + def gen_struct(self, name, definitions): + structDef = self.registry.structs[name] + + # Generate member data + members = OrderedDict() + for memberName in sorted(structDef.members.keys()): + memberDef = structDef.members[memberName] + + if memberDef.type in self.registry.externalTypes and not memberDef.type in definitions: + # Members with types defined externally and aren't manually defined are ignored + Log.w("Ignoring member '{0}' in struct '{1}' with external type '{2}'".format(memberName, name, memberDef.type)) + continue + + if memberDef.isArray: + if memberDef.arraySizeMember != None and name not in struct_with_valid_dynamic_array and name not in struct_with_dynamic_array_size_cap: + # This array is a dynamic one (count + pointer to array) which is not allowed + # for return structures. Such structures hence are ill-formed and shouldn't + # be included in the schema + Log.w("Ignoring member '{0}' in struct '{1}' containing ill-formed pointer to array".format(memberName, name)) + else: + if memberDef.arraySizeMember != None and name in struct_with_dynamic_array_size_cap: + Log.w("Member '{0}' in struct '{1}' is a pointer to array with a known maximum size, it will be ignored in the API library, but supported in the layer".format(memberName, name)) + members[memberDef.name] = self.gen_array(memberDef.type, memberDef.arraySize, memberDef.arraySizeCap, definitions) + else: + members[memberDef.name] = self.gen_type(memberDef.type, definitions) + + # Generate definition + definitions[name] = OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": members + }) + + + def gen_structChainDefinitions(self, basename, definitions): + structNames = [ basename ] + if basename + '2' in self.registry.structs: + # Structure has version 2 which is extensible + basename += '2' + structNames.append(basename) + + # Collect unique chainable structures (ignoring aliases) + for structName in sorted(self.registry.structs.keys()): + structDef = self.registry.structs[structName] + if not structDef.isAlias and basename in structDef.extends: + structNames.append(structName) + + # Generate structure definitions and references + gen = OrderedDict() + for structName in structNames: + # Add structure definition and reference + self.gen_struct(structName, definitions) + gen[structName] = { "$ref": "#/definitions/" + structName } + + # Add structure references for all alises + for alias in self.registry.structs[structName].aliases: + if alias != structName: + gen[alias] = gen[structName] + + return gen + + + def gen_features(self, definitions): + return self.gen_structChainDefinitions("VkPhysicalDeviceFeatures", definitions) + + + def gen_properties(self, definitions): + return self.gen_structChainDefinitions("VkPhysicalDeviceProperties", definitions) + + + def gen_formats(self, definitions): + # Add definition for format properties + definitions['formatProperties'] = OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": self.gen_structChainDefinitions("VkFormatProperties", definitions) + }) + + # Generate references to the format properties definition for each format + gen = OrderedDict() + for format in sorted(self.registry.enums['VkFormat'].values): + gen[format] = OrderedDict({ + "$ref": "#/definitions/formatProperties" + }) + return gen + + + def gen_queueFamilies(self, definitions): + return self.gen_structChainDefinitions("VkQueueFamilyProperties", definitions) + + + def gen_videoProfiles(self, definitions): + # We do not want to include usage hint structures in the schema + # as those are only usage scenario customizations and do not affect capabilities + excludedSet = { + "VkVideoDecodeUsageInfoKHR", + "VkVideoEncodeUsageInfoKHR" + } + videoProfiles = self.gen_structChainDefinitions("VkVideoProfileInfoKHR", definitions) + for excluded in excludedSet: + excluded = self.registry.getNonAliasTypeName(excluded, self.registry.structs) + if excluded in videoProfiles: + del videoProfiles[excluded] + # Check also any aliases + for alias in self.registry.structs[excluded].aliases: + if alias in videoProfiles: + del videoProfiles[excluded] + + return videoProfiles + + + def gen_videoCapabilities(self, definitions): + return self.gen_structChainDefinitions("VkVideoCapabilitiesKHR", definitions) + + + def gen_videoFormats(self, definitions): + return self.gen_structChainDefinitions("VkVideoFormatPropertiesKHR", definitions) diff --git a/scripts/source/log.py b/scripts/source/log.py new file mode 100644 index 00000000..63075f6a --- /dev/null +++ b/scripts/source/log.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2021-2026 LunarG, Inc. +# Copyright (c) 2023-2024 RasterGrid Kft. +# +# 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. +# +# Authors: +# - Daniel Rakos +# - Christophe Riccio + +class Log(): + def f(msg): + print('FATAL: ' + msg) + raise Exception(msg) + + def e(msg): + print('ERROR: ' + msg) + + def w(msg): + print('WARNING: ' + msg) + + def i(msg): + print(msg) + diff --git a/scripts/source/vulkan_registry.py b/scripts/source/vulkan_registry.py new file mode 100644 index 00000000..8bc57647 --- /dev/null +++ b/scripts/source/vulkan_registry.py @@ -0,0 +1,1521 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2021-2026 LunarG, Inc. +# Copyright (c) 2023-2024 RasterGrid Kft. +# +# 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. +# +# Authors: +# - Daniel Rakos +# - Christophe Riccio + +import os +import re +import copy +import xml.etree.ElementTree as etree +from collections import deque +from typing import OrderedDict + +from source.log import Log + +class VulkanVersionNumber(): + def __init__(self, versionStr, targetApi = None, versionName = None): + match = re.search(r"^([1-9][0-9]*)\.([0-9]+)$", versionStr) + if match != None: + # Only major and minor version specified + self.major = int(match.group(1)) + self.minor = int(match.group(2)) + self.patch = None + else: + # Otherwise expect major, minor, and patch version + match = re.search(r"^([1-9][0-9]*)\.([0-9]+)\.([0-9]+)$", versionStr) + if match != None: + self.major = int(match.group(1)) + self.minor = int(match.group(2)) + self.patch = int(match.group(3)) + else: + Log.f("Invalid API version string: '{0}'".format(versionStr)) + + # Construct version number pre-processor definition's name + if targetApi == 'vulkan': + self.versionName = 'VK_VERSION_{0}_{1}'.format(self.major, self.minor) + self.versionMacro = 'VK_API_VERSION_{0}_{1}'.format(self.major, self.minor) + self.versionStructSuffic = '{0}{1}'.format(self.major, self.minor) + + elif targetApi is not None: + Log.f("Unknown target API '{0}'".format(targetApi)) + + def get_api_version_string(self): + return 'VK_API_VERSION_' + str(self.major) + '_' + str(self.minor) + + def __eq__(self, other): + if isinstance(other, VulkanVersionNumber): + # Only consider major and minor version in comparison + return self.major == other.major and self.minor == other.minor + else: + return False + + def __gt__(self, other): + # Only consider major and minor version in comparison + return self.major > other.major or (self.major == other.major and self.minor > other.minor) + + def __lt__(self, other): + # Only consider major and minor version in comparison + return self.major < other.major or (self.major == other.major and self.minor < other.minor) + + def __ne__(self, other): + return not self.__eq__(other) + + def __ge__(self, other): + return self.__eq__(other) or self.__gt__(other) + + def __le__(self, other): + return self.__eq__(other) or self.__lt__(other) + + def __str__(self): + if self.patch != None: + return '{0}.{1}.{2}'.format(self.major, self.minor, self.patch) + else: + return '{0}.{1}'.format(self.major, self.minor) + +class VulkanPlatform(): + def __init__(self, data): + self.name = data.get('name') + self.protect = data.get('protect') + +class VulkanDefinitionScope(): + def parseAliases(self, xml): + self.sTypeAliases = dict() + for sTypeAlias in xml.findall("./require/enum[@alias]"): + if re.search(r'^VK_STRUCTURE_TYPE_.*', sTypeAlias.get('name')): + self.sTypeAliases[sTypeAlias.get('alias')] = sTypeAlias.get('name') + +class VulkanVersion(VulkanDefinitionScope): + def __init__(self, xml, targetApi): + self.name = xml.get('name') + self.number = VulkanVersionNumber(xml.get('number'), targetApi, self.name) + self.extensions = [] + self.features = dict() + self.limits = dict() + self.parseAliases(xml) + +class VulkanExtension(VulkanDefinitionScope): + def __init__(self, xml, upperCaseName): + self.name = xml.get('name') + self.upperCaseName = upperCaseName + self.type = xml.get('type') + self.features = dict() + self.limits = dict() + self.platform = xml.get('platform') + self.provisional = xml.get('provisional') + self.promotedTo = xml.get('promotedto').split(',') if xml.get('promotedto') is not None else [] + self.obsoletedBy = xml.get('obsoletedby') + self.deprecatedBy = xml.get('deprecatedby') + self.spec_version = 1 + for e in xml.findall("./require/enum"): + if (e.get('name').endswith("SPEC_VERSION")): + self.spec_version = e.get('value') + break + self.parseAliases(xml) + + +class VulkanStructMember(): + def __init__(self, name, type, limittype, isArray = False): + self.name = name + self.type = type + self.limittype = limittype + self.isArray = isArray + self.arraySizeMember = None + self.nullTerminated = False + self.arraySize = None + self.arraySizeCap = None + + def isDynamicallySizedArrayWithCap(self): + return self.isArray and self.arraySizeCap is not None + +class VulkanStruct(): + def __init__(self, name): + self.name = name + self.sType = None + self.extends = [] + self.members = OrderedDict() + self.aliases = [ name ] + self.isAlias = False + self.definedByVersion = None + self.definedByExtensions = [] + self.isBeta = None + + +class VulkanEnum(): + def __init__(self, name): + self.name = name + self.aliases = [ name ] + self.isAlias = False + self.values = [] + self.aliasValues = dict() + + +class VulkanBitmask(): + def __init__(self, name): + self.name = name + self.aliases = [ name ] + self.isAlias = False + self.bitsType = None + + +class VulkanFeature(): + def __init__(self, name): + self.name = name + self.structs = set() + + +class VulkanLimit(): + def __init__(self, name): + self.name = name + self.structs = set() + + +class VulkanVideoRequiredCapabilities(): + def __init__(self, struct, member, value): + self.struct = struct + self.member = member + self.value = value + + +class VulkanVideoFormat(): + def __init__(self, name, usage): + self.name = name + self.usage = usage + self.properties = OrderedDict() + self.requiredCaps = list() + super().__init__() + + def matchesImageUsageFlags(self, flags): + # Check if the specified list of VkImageUsageFlags matches the usage criteria + # for this video format category + return evalConditionFromList(self.usage, flags) + + def hasRequiredCapabilities(self, videoCapabilities, registry): + hasAllRequiredCaps = True + for requiredCap in self.requiredCaps: + capabilitiesData = None + if requiredCap.struct in videoCapabilities: + capabilitiesData = videoCapabilities[requiredCap.struct] + else: + # Check also for possible aliases + for alias in registry.structs[requiredCap.struct].aliases: + if alias in videoCapabilities: + capabilitiesData = videoCapabilities[alias] + + if capabilitiesData is not None: + if requiredCap.member in capabilitiesData: + value = capabilitiesData[requiredCap.member] + if isinstance(value, list): + hasAllRequiredCaps = evalConditionFromList(requiredCap.value, value) + else: + hasAllRequiredCaps = (requiredCap.value == value) + else: + # Required capability structure member is missing + hasAllRequiredCaps = False + else: + # Entire required capability structure is missing + hasAllRequiredCaps = False + return hasAllRequiredCaps + +class VulkanVideoProfileStructMember(): + def __init__(self, name): + self.name = name + self.values = OrderedDict() + +class VulkanVideoProfileStruct(): + def __init__(self, struct): + self.struct = struct + self.members = OrderedDict() + +class VulkanVideoCodec(): + def __init__(self, name, extend = None, value = None): + self.name = name + self.value = value + self.profileStructs = OrderedDict() + self.capabilities = OrderedDict() + self.formats = OrderedDict() + if extend is not None: + self.profileStructs = copy.deepcopy(extend.profileStructs) + self.capabilities = copy.deepcopy(extend.capabilities) + self.formats = copy.deepcopy(extend.formats) + + def isSpecific(self): + return self.value is not None + + def getVideoFormatCategoriesForFormat(self, videoFormat, videoCapabilities, registry): + result = list() + + baseProps = registry.getBaseVideoFormatPropertiesFromVideoFormat(videoFormat) + + # Find the the video format categories the video format belongs to + if 'imageUsageFlags' in baseProps: + foundVideoFormatCategory = False + hadMatchWithMissingPrerequisities = None + for videoFormatCategory in self.formats.values(): + # Check if the video format matches the image usage requirements of the video format category + if not videoFormatCategory.matchesImageUsageFlags(baseProps['imageUsageFlags']): + continue + # Make sure that the video profile has the required capabilites for this video format category + if not videoFormatCategory.hasRequiredCapabilities(videoCapabilities, registry): + hadMatchWithMissingPrerequisities = videoFormatCategory + continue + # This video format does indeed fall into this video format category + foundVideoFormatCategory = True + result.append(videoFormatCategory) + if not foundVideoFormatCategory: + if hadMatchWithMissingPrerequisities is not None: + Log.e("Video format from category {0} with missing prerequisites:\n{1}".format(hadMatchWithMissingPrerequisities.name, json.dumps(videoFormat, indent=4))) + else: + Log.e("Unrecognized video format category for imageUsageFlags in video format:\n{0}".format(json.dumps(videoFormat, indent=4))) + else: + Log.f("Missing imageUsageFlags from video format:\n{0}".format(json.dumps(videoFormat, indent=4))) + + return result + +class VulkanDefinitions(): + def __init__(self): + self.enums = set() + self.types = set() + + def add(self, elements): + for element in elements: + for enum in element.findall("./enum"): + self.enums.add(enum.get('name')) + for type in element.findall("./type"): + self.types.add(type.get('name')) + + def addDependencies(self, xml, targetApi): + # Add types that are required by required types as dependency + for type in xml.findall("./types/type[@requires]"): + apiList = type.get('api') + + # Skip dependency if it does not apply to the target API + if apiList is not None and not targetApi in apiList.split(','): + continue + + name = type.find('./name') + if name is not None and name.text in self.types: + self.types.add(type.get('requires')) + + # Add types that contain the definition of required alias types as dependency + for type in xml.findall("./types/type[@alias]"): + + # Skip dependency if it does not apply to the target API + if apiList is not None and not targetApi in apiList.split(','): + continue + + name = type.get('name') + if name in self.types: + self.types.add(type.get('alias')) + +def apiNameMatch(str, supported): + """Return whether a required api name matches a pattern specified for an + XML 'api' attribute or 'supported' attribute. + - str - API name such as 'vulkan' or 'openxr'. May be None, in which + case it never matches (this should not happen). + - supported - comma-separated list of XML API names. May be None, in + which case str always matches (this is the usual case).""" + + if str is not None: + return supported is None or str in supported.split(',') + + # Fallthrough case - either str is None or the test failed + return False + +def stripNonmatchingAPIs(tree, apiName, actuallyDelete = True): + """Remove tree Elements with 'api' attributes matching apiName. + tree - Element at the root of the hierarchy to strip. Only its + children can actually be removed, not the tree itself. + apiName - string which much match a command-separated component of + the 'api' attribute. + actuallyDelete - only delete matching elements if True.""" + + stack = deque() + stack.append(tree) + + while len(stack) > 0: + parent = stack.pop() + + for child in parent.findall('*'): + api = child.get('api') + + if apiNameMatch(apiName, api): + # Add child to the queue + stack.append(child) + elif not apiNameMatch(apiName, api): + # Child does not match requested api. Remove it. + if actuallyDelete: + parent.remove(child) + +# Dynamic arrays are ill-formed, but some of them still have a maximum size that can be used +struct_with_valid_dynamic_array = ["VkQueueFamilyGlobalPriorityProperties"] +# These dynamic arrays have a known maximum possible size +struct_with_dynamic_array_size_cap = ["VkPhysicalDeviceHostImageCopyProperties", "VkPhysicalDeviceHostImageCopyPropertiesEXT", "VkPhysicalDeviceVulkan14Properties"] + +# Evaluates that a condition is satisfied per the specified list of values +# e.g.: +# condition = '(A+B),C' +# evaluates to True for values = [ 'A', 'B' ] +# evaluates to True for values = [ 'C' ] +# evaluates to False for values = [ 'A' ] +# evaluates to False for values = [ 'B' ] +def evalConditionFromList(condition, values): + evalstr = "" + value = "" + + def genExpressionFromValue(value): + return value if value == "" else "('{0}' in values)".format(value) + + for char in condition: + if char in ['(', ')', '+', ',']: + evalstr += genExpressionFromValue(value) + value = "" + if char == '+': + # '+' means AND + evalstr += ' and ' + elif char == ',': + # ',' means OR + evalstr += ' or ' + else: + evalstr += char + else: + value += char + evalstr += genExpressionFromValue(value) + + return eval(evalstr) + +class VulkanRegistry(): + def __init__(self, registryFile, api = 'vulkan'): + Log.i("Loading registry file: '{0}'".format(registryFile)) + xml = etree.parse(registryFile) + stripNonmatchingAPIs(xml.getroot(), api, actuallyDelete = True) + + videoRegistryFile = registryFile.replace('vk.xml', 'video.xml') + if os.path.isfile(videoRegistryFile): + Log.i("Loading video registry file: '{0}'".format(videoRegistryFile)) + videoxml = etree.parse(videoRegistryFile) + else: + Log.w("Video registry file '{0}' does not exist, building without video support".format(videoRegistryFile)) + videoxml = None + + self.api = api + self.require = VulkanDefinitions() + self.remove = VulkanDefinitions() + + self.parsePlatformInfo(xml) + self.parseVersionInfo(xml) + self.parseExtensionInfo(xml) + + self.require.addDependencies(xml, self.api) + + self.parseStructInfo(xml) + self.parsePrerequisites(xml) + self.parseEnums(xml) + self.parseFormats(xml) + self.parseBitmasks(xml) + self.parseConstants(xml) + self.parseAliases(xml) + self.parseExternalTypes(xml) + self.parseFeatures(xml) + self.parseLimits(xml) + self.parseHeaderVersion(xml) + self.parseVideoCodecs(xml, videoxml) + self.applyWorkarounds() + + def findAllFeatures(self, xml, xpath = None): + results = [] + for feature in xml.findall("./feature"): + apiList = feature.get('api') + if self.api in apiList.split(','): + if xpath is None: + results.append(feature) + else: + results.extend(feature.findall(xpath)) + return results + + def findAllExtensions(self, xml, xpath = None): + results = [] + for extension in xml.findall("./extensions/extension"): + apiList = extension.get('supported') + if self.api in apiList.split(','): + if xpath is None: + results.append(extension) + else: + results.extend(extension.findall(xpath)) + return results + + def parseRequireRemove(self, xml): + self.require.add(xml.findall("./require")) + self.remove.add(xml.findall("./remove")) + + def parsePlatformInfo(self, xml): + self.platforms = dict() + for plat in xml.findall("./platforms/platform"): + self.platforms[plat.get('name')] = VulkanPlatform(plat) + + def parseVersionInfo(self, xml): + self.versions = dict() + for feature in self.findAllFeatures(xml): + if re.search(r"^[1-9][0-9]*\.[0-9]+$", feature.get('number')): + self.versions[feature.get('name')] = VulkanVersion(feature, self.api) + self.parseRequireRemove(feature) + else: + Log.f("Unsupported feature with number '{0}'".format(feature.get('number'))) + + def parseExtensionInfo(self, xml): + self.extensions = dict() + for ext in self.findAllExtensions(xml): + name = ext.get('name') + + # Find name enum (due to inconsistencies in lower case and upper case names this is non-trivial) + foundNameEnum = False + matches = ext.findall("./require/enum[@value='\"" + name + "\"']") + for match in matches: + if match.get('name').endswith("_EXTENSION_NAME"): + # Add extension definition + self.extensions[name] = VulkanExtension(ext, match.get('name')[:-len("_EXTENSION_NAME")]) + foundNameEnum = True + break + if not foundNameEnum: + Log.f("Cannot find name enum for extension '{0}'".format(name)) + + self.parseRequireRemove(ext) + + def parseStructInfo(self, xml): + self.structs = dict() + for struct in xml.findall("./types/type[@category='struct']"): + name = struct.get('name') + + # Don't process structure if it is not required or if it is removed + if name not in self.require.types or name in self.remove.types: + continue + + # Define base struct information + structDef = VulkanStruct(name) + + # Find out whether it's an extension structure + extends = struct.get('structextends') + if extends != None: + structDef.extends = extends.split(',') + + # Find sType value + sType = struct.find("./member[name='sType']") + if sType != None: + structDef.sType = sType.get('values') + + # Parse struct members + for member in struct.findall('./member'): + name = member.find('./name').text + tail = member.find('./name').tail + type = member.find('./type').text + + # Only add real members (skip sType and pNext) + if name != 'sType' and name != 'pNext': + # Define base member information + structDef.members[name] = VulkanStructMember( + name, + type, + member.get('limittype') + ) + + # Detect if it's an array + if tail != None and tail[0] == '[': + structDef.members[name].isArray = True + match1D = re.search(r"^\[([0-9]+)\]$", tail) + match2D = re.search(r"^\[([0-9]+)\]\[([0-9]+)\]$", tail) + enum = member.find('./enum') + if match1D != None: + # [] case + structDef.members[name].arraySize = int(match1D.group(1)) + elif match2D != None: + # [][] case + structDef.members[name].arraySize = [ int(match2D.group(1)), int(match2D.group(2)) ] + elif tail == '[' and enum != None and enum.tail == ']': + # [] case + structDef.members[name].arraySize = enum.text + elif structDef.name == 'VkPhysicalDeviceDataGraphOperationSupportARM': + # Handle xml bug + structDef.members['name'].arraySize = 'VK_MAX_PHYSICAL_DEVICE_DATA_GRAPH_OPERATION_SET_NAME_SIZE_ARM' + else: + Log.f("Unsupported array format for struct member '{0}::{1}'".format(structDef.name, name)) + + # If it has a "len" attribute then it's also an array, just a dynamically sized one + if member.get('len') != None: + lenMeta = member.get('len').split(',') + for len in lenMeta: + if len == 'null-terminated': + # Values are null-terminated + structDef.members[name].nullTerminated = True + else: + # This is a pointer to an array with a corresponding count member + structDef.members[name].isArray = True + structDef.members[name].arraySizeMember = len + + # Some arrays have a natural maximum size even if they are dynamic. For example, a list + # of VkImageLayouts, because that enum itself is limited. + if structDef.members[name].type == 'VkImageLayout': + structDef.members[name].arraySizeCap = 64 + + # If any of the members is a dynamic array then we should remove the corresponding count member + for member in list(structDef.members.values()): + if member.isArray and member.arraySizeMember != None and struct.get('name') not in struct_with_valid_dynamic_array and struct.get('name') not in struct_with_dynamic_array_size_cap: + structDef.members.pop(member.arraySizeMember, None) + + # Store struct definition + self.structs[struct.get('name')] = structDef + + def parsePrerequisites(self, xml): + # Check features (i.e. API versions) + for feature in self.findAllFeatures(xml): + for requireType in feature.findall('./require/type'): + # Add feature as the source of the definition of a struct + if requireType.get('name') in self.structs: + self.structs[requireType.get('name')].definedByVersion = VulkanVersionNumber(feature.get('number'), self.api, feature.get('name')) + + # Check extensions + for extension in self.findAllExtensions(xml): + for requireType in extension.findall('./require/type'): + # Add extension as the source of the definition of a struct + if requireType.get('name') in self.structs: + self.structs[requireType.get('name')].definedByExtensions.append(extension.get('name')) + + def parseEnums(self, xml): + self.enums = dict() + # Find enum definitions + for enum in xml.findall("./types/type[@category='enum']"): + name = enum.get('name') + + # Don't process enum type if it is not required or if it is removed + if name not in self.require.types or name in self.remove.types: + continue + + # Create enum type + enumDef = VulkanEnum(name) + + # First collect base values + values = xml.find("./enums[@name='" + enumDef.name + "']") + if values is not None: + for value in values.findall("./enum"): + if value.get('alias') is None: + enumDef.values.append(value.get('name')) + + # Then find extension values + for value in self.findAllFeatures(xml, "./require/enum[@extends='" + enumDef.name + "']"): + if value.get('alias') is None: + enumDef.values.append(value.get('name')) + for value in self.findAllExtensions(xml, "./require/enum[@extends='" + enumDef.name + "']"): + if value.get('alias') is None: + enumDef.values.append(value.get('name')) + + # Remove any values that are marked as removed + removedValues = [] + for name in enumDef.values: + if name in self.remove.enums: + removedValues.append(name) + for name in removedValues: + enumDef.values.remove(name) + + # Finally store it in the registry + self.enums[enumDef.name] = enumDef + + def parseFormats(self, xml): + self.formatCompression = dict() + for enum in xml.findall("./formats/format"): + if enum.get('compressed'): + self.formatCompression[enum.get('name')] = enum.get('compressed') + + self.aliasFormats = list() + for format in self.findAllExtensions(xml, "./require/enum[@extends='VkFormat'][@alias]"): + self.aliasFormats.append(format.attrib["name"]) + + self.betaFormatFeatures = list() + for format_feature in self.findAllExtensions(xml, "./require/enum[@protect='VK_ENABLE_BETA_EXTENSIONS']"): + self.betaFormatFeatures.append(format_feature.attrib["name"]) + + def parseBitmasks(self, xml): + self.bitmasks = dict() + # Find bitmask definitions + for bitmask in xml.findall("./types/type[@category='bitmask']"): + # Only consider non-alias bitmasks + name = bitmask.find("./name") + if bitmask.get('alias') is None and name != None: + # Don't process bitmask type if it is not required or if it is removed + if name.text not in self.require.types or name.text in self.remove.types: + continue + + bitmaskDef = VulkanBitmask(name.text) + + # Get the name of the corresponding FlagBits type + bitsName = bitmask.get('bitvalues') + if bitsName is None: + # Currently some definitions use "requires", not "bitvalues" + bitsName = bitmask.get('requires') + + if bitsName != None: + if bitsName in self.enums: + bitmaskDef.bitsType = self.enums[bitsName] + else: + Log.f("Could not find bits enum '{0}' for bitmask '{1}'".format(bitsName, bitmaskDef.name)) + else: + # This bitmask doesn't have any bits defined + pass + + # Finally store it in the registry + self.bitmasks[bitmaskDef.name] = bitmaskDef + + def parseConstants(self, xml): + self.constants = dict() + # Find constant definitions + constants = xml.find("./enums[@name='API Constants']").findall("./enum[@value]") + if constants != None: + for constant in constants: + self.constants[constant.get('name')] = constant.get('value') + else: + Log.f("Failed to find API constants in the registry") + + def parseAliases(self, xml): + # Find any struct aliases + for struct in xml.findall("./types/type[@category='struct']"): + name = struct.get('name') + + # Don't process structure if it is not required or if it is removed + if name not in self.require.types or name in self.remove.types: + continue + + alias = struct.get('alias') + if alias != None: + # Don't process alias if it is not required or if it is removed + if alias not in self.require.types or alias in self.remove.types: + continue + + if alias in self.structs: + baseStructDef = self.structs[alias] + aliasStructDef = self.structs[name] + + # Set as alias + aliasStructDef.isAlias = True + + # Fill missing struct information for the alias + aliasStructDef.extends = baseStructDef.extends + aliasStructDef.members = baseStructDef.members + aliasStructDef.aliases = baseStructDef.aliases + aliasStructDef.aliases.append(name) + + # Use alias structure dependencies as the structure dependencies if the latter has none + # This is needed to handle the case when the structure is not part of the target API + # but is a dependency of the alias + if baseStructDef.definedByVersion is None and len(baseStructDef.definedByExtensions) == 0: + baseStructDef.definedByVersion = aliasStructDef.definedByVersion + baseStructDef.definedByExtensions = aliasStructDef.definedByExtensions + + if baseStructDef.sType != None: + sTypeAlias = None + + # First try to find sType alias in core versions + if aliasStructDef.definedByVersion != None: + for versionName in self.versions: + version = self.versions[versionName] + if version.number <= aliasStructDef.definedByVersion: + sTypeAlias = version.sTypeAliases.get(baseStructDef.sType) + if sTypeAlias != None: + break + + # Otherwise need to find sType alias in extension + if sTypeAlias == None: + for extName in aliasStructDef.definedByExtensions: + sTypeAlias = self.extensions[extName].sTypeAliases.get(baseStructDef.sType) + if sTypeAlias != None: + break + + #Workaround due to a vk.xml issue that was resolved with 1.1.119 + if alias == 'VkPhysicalDeviceVariablePointersFeatures': + sTypeAlias = 'VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES' + + if sTypeAlias != None: + aliasStructDef.sType = sTypeAlias + + # Find any enum aliases + for enum in xml.findall("./types/type[@category='enum']"): + name = enum.get('name') + + # Don't process enum type if it is not required or if it is removed + if name not in self.require.types or name in self.remove.types: + continue + + alias = enum.get('alias') + if alias != None: + # Don't process alias if it is not required or if it is removed + if alias not in self.require.types or alias in self.remove.types: + continue + + if alias in self.enums: + baseEnumDef = self.enums[alias] + aliasEnumDef = self.enums[name] + + # Set as alias + aliasEnumDef.isAlias = True + + # Merge aliases + aliasEnumDef.aliases = baseEnumDef.aliases + aliasEnumDef.aliases.append(name) + + # Merge values respecting original order + for value in aliasEnumDef.values: + if not value in baseEnumDef.values: + baseEnumDef.values.append(value) + aliasEnumDef.values = baseEnumDef.values + else: + Log.f("Failed to find alias '{0}' of enum '{1}'".format(alias, enum.get('name'))) + + # Find any enum value aliases + for enum in xml.findall("./enums"): + if enum.get('name') in self.enums.keys(): + enumDef = self.enums[enum.get('name')] + for aliasValue in enum.findall("./enum[@alias]"): + name = aliasValue.get('name') + alias = aliasValue.get('alias') + enumDef.values.append(name) + enumDef.aliasValues[name] = alias + for aliasValue in self.findAllExtensions(xml, "./require/enum[@alias]"): + if aliasValue.get('extends'): + enumDef = self.enums[aliasValue.get('extends')] + name = aliasValue.get('name') + alias = aliasValue.get('alias') + enumDef.values.append(name) + enumDef.aliasValues[name] = alias + + # Find any bitmask (flags) aliases + for bitmask in xml.findall("./types/type[@category='bitmask']"): + name = bitmask.get('name') + + # Don't process bitmask if it is not required or if it is removed + if name not in self.require.types or name in self.remove.types: + continue + + alias = bitmask.get('alias') + if alias != None: + # Don't process alias if it is not required or if it is removed + if alias not in self.require.types or alias in self.remove.types: + continue + + if alias in self.bitmasks: + # Duplicate bitmask definition + baseBitmaskDef = self.bitmasks[alias] + aliasBitmaskDef = VulkanBitmask(name) + aliasBitmaskDef.bitsType = baseBitmaskDef.bitsType + + # Set as alias + aliasBitmaskDef.isAlias = True + + # Merge aliases + aliasBitmaskDef.aliases = baseBitmaskDef.aliases + aliasBitmaskDef.aliases.append(name) + else: + Log.f("Failed to find alias '{0}' of bitmask '{1}'".format(alias, bitmask.get('name'))) + + # Find any constant aliases + for constant in xml.find("./enums[@name='API Constants']").findall("./enum[@alias]"): + self.constants[constant.get('name')] = self.constants[constant.get('alias')] + + def parseExternalTypes(self, xml): + self.includes = set() + self.externalTypes = set() + + # Find all include definitions + for include in xml.findall("./types/type[@category='include']"): + self.includes.add(include.get('name')) + + # Find all types depending on the includes + for type in xml.findall("./types/type[@requires]"): + if type.get('requires') in self.includes: + self.externalTypes.add(type.get('name')) + + def parseFeatures(self, xml): + # First, parse features specific to Vulkan versions + for version in self.versions.values(): + if version.number.major == 1 and version.number.minor == 0: + # For version 1.0 use VkPhysicalDeviceFeatures + structDef = self.structs['VkPhysicalDeviceFeatures'] + for memberDef in structDef.members.values(): + version.features[memberDef.name] = VulkanFeature(memberDef.name) + version.features[memberDef.name].structs.add('VkPhysicalDeviceFeatures') + else: + # For all other versions use the feature structures required by it + featureStructNames = [] + xmlVersion = xml.find("./feature[@name='" + version.name + "']") + for type in xmlVersion.findall("./require/type"): + name = type.get('name') + if name in self.structs and 'VkPhysicalDeviceFeatures2' in self.structs[name].extends: + featureStructNames.append(name) + # VkPhysicalDeviceVulkan11Features is defined in Vulkan 1.2, but it actually + # contains Vulkan 1.1 features, so treat it as such + if version.number.major == 1 and version.number.minor == 1: + featureStructNames.append('VkPhysicalDeviceVulkan11Features') + elif version.number.major == 1 and version.number.minor == 2: + if 'VkPhysicalDeviceVulkan11Features' in featureStructNames: + featureStructNames.remove('VkPhysicalDeviceVulkan11Features') + # For each feature collect all feature structures containing them, and their aliases + for featureStructName in featureStructNames: + if (featureStructName in self.structs): + structDef = self.structs[featureStructName] + for memberName in structDef.members.keys(): + if not memberName in version.features: + version.features[memberName] = VulkanFeature(memberName) + version.features[memberName].structs.update(structDef.aliases) + + # Then parse features specific to extensions + for extension in self.extensions.values(): + featureStructNames = [] + xmlExtension = xml.find("./extensions/extension[@name='" + extension.name + "']") + for type in xmlExtension.findall("./require/type"): + name = type.get('name') + if name in self.structs and 'VkPhysicalDeviceFeatures2' in self.structs[name].extends: + featureStructNames.append(name) + # For each feature collect all feature structures containing them, and their aliases + for featureStructName in featureStructNames: + structDef = self.structs[featureStructName] + for memberName in structDef.members.keys(): + extension.features[memberName] = VulkanFeature(memberName) + extension.features[memberName].structs.update(structDef.aliases) + # For each feature we also have to check whether it's part of core so that + # any not strictly alias struct (i.e. the VkPhysicalDeviceVulkanXXFeatures) + # get included as well + for version in self.versions.values(): + if memberName in version.features and version.features[memberName].structs >= extension.features[memberName].structs: + extension.features[memberName].structs = version.features[memberName].structs + + def parseLimits(self, xml): + # First, parse properties/limits specific to Vulkan versions + for version in self.versions.values(): + if version.number.major == 1 and version.number.minor == 0: + # The properties extension structures are a misnomer, as they contain limits, + # however, the naming will stay with us, so in order to avoid nested + # "properties" (limits), we simply use VkPhysicalDeviceLimits directly here + # for version 1.0 limits, plus, not having a better place to put them, we + # also include VkPhysicalDeviceSparseProperties here (even though they are + # more like features) + limitStructNames = [ 'VkPhysicalDeviceLimits', 'VkPhysicalDeviceSparseProperties' ] + else: + # For all other versions use the property structures required by it + limitStructNames = [] + xmlVersion = xml.find("./feature[@name='" + version.name + "']") + for type in xmlVersion.findall("./require/type"): + name = type.get('name') + if name in self.structs and 'VkPhysicalDeviceProperties2' in self.structs[name].extends: + limitStructNames.append(name) + # VkPhysicalDeviceVulkan11Properties is defined in Vulkan 1.2, but it actually + # contains Vulkan 1.1 limits, so treat it as such + if version.number.major == 1 and version.number.minor == 1: + limitStructNames.append('VkPhysicalDeviceVulkan11Properties') + elif version.number.major == 1 and version.number.minor == 2: + if 'VkPhysicalDeviceVulkan11Properties' in limitStructNames: + limitStructNames.remove('VkPhysicalDeviceVulkan11Properties') + # For each limit collect all property/limit structures containing them, and their aliases + for limitStructName in limitStructNames: + if (limitStructName in self.structs): + structDef = self.structs[limitStructName] + for memberName in structDef.members.keys(): + if not memberName in version.limits: + version.limits[memberName] = VulkanLimit(memberName) + version.limits[memberName].structs.update(structDef.aliases) + + # Then parse properties/limits specific to extensions + for extension in self.extensions.values(): + limitStructNames = [] + xmlExtension = xml.find("./extensions/extension[@name='" + extension.name + "']") + for type in xmlExtension.findall("./require/type"): + name = type.get('name') + if name in self.structs and 'VkPhysicalDeviceProperties2' in self.structs[name].extends: + limitStructNames.append(name) + # For each limit collect all property/limit structures containing them, and their aliases + for limitStructName in limitStructNames: + structDef = self.structs[limitStructName] + for memberName in structDef.members.keys(): + extension.limits[memberName] = VulkanLimit(memberName) + extension.limits[memberName].structs.update(structDef.aliases) + # For each limit we also have to check whether it's part of core so that + # any not strictly alias struct (i.e. the VkPhysicalDeviceVulkanXXProperties) + # get included as well + for version in self.versions.values(): + if memberName in version.limits and version.limits[memberName].structs >= extension.limits[memberName].structs: + extension.limits[memberName].structs = version.limits[memberName].structs + + def parseHeaderVersion(self, xml): + # Find the largest version number + maxVersionNumber = self.versions[max(self.versions, key = lambda version: self.versions[version].number)].number + self.headerVersionNumber = VulkanVersionNumber(str(maxVersionNumber)) + # Add patch from VK_HEADER_VERSION define + for define in xml.findall("./types/type[@category='define']"): + name = define.find('./name') + if name != None and name.text == 'VK_HEADER_VERSION': + self.headerVersionNumber.patch = int(name.tail.lstrip()) + return + + def parseVideoConstants(self, videoxml): + for constant in videoxml.findall("./extensions/extension/require/enum[@value]"): + self.constants[constant.get('name')] = constant.get('value') + + def parseVideoEnums(self, videoxml): + # Find enum definitions + for enum in videoxml.findall("./enums[@name]"): + name = enum.get('name') + + # Only add video enum type if it is a required external type + if name in self.externalTypes: + # Create enum type + enumDef = VulkanEnum(name) + + # First collect base values + for value in enum.findall("./enum"): + if value.get('alias') is None: + enumDef.values.append(value.get('name')) + + # Store video enum type in the registry + self.enums[name] = enumDef + + # Remove video enum type from the set of external types + self.externalTypes.remove(name) + + def parseVideoCodecs(self, xml, videoxml): + self.videoCodecs = dict() + + # Used to look up video codecs based on the video codec op value + self.videoCodecsByValue = dict() + + # Used to reverse look up video codecs by the defined structure names if no video codec op value is available + self.videoCodecsByStructName = dict() + + if videoxml is None: + return + + self.parseVideoConstants(videoxml) + self.parseVideoEnums(videoxml) + + xmlVideoCodecs = xml.find("./videocodecs") + for xmlVideoCodec in xmlVideoCodecs.findall("./videocodec"): + name = xmlVideoCodec.get('name') + extend = xmlVideoCodec.get('extend') + value = xmlVideoCodec.get('value') + if value is None: + # Video codec category + self.videoCodecs[name] = VulkanVideoCodec(name) + else: + # Specific video codec + self.videoCodecs[name] = VulkanVideoCodec(name, self.videoCodecs[extend], value) + self.videoCodecsByValue[value] = self.videoCodecs[name] + videoCodec = self.videoCodecs[name] + + for xmlVideoProfiles in xmlVideoCodec.findall("./videoprofiles"): + videoProfileStructName = xmlVideoProfiles.get('struct') + videoCodec.profileStructs[videoProfileStructName] = VulkanVideoProfileStruct(videoProfileStructName) + videoProfileStruct = videoCodec.profileStructs[videoProfileStructName] + self.videoCodecsByStructName[videoProfileStructName] = videoCodec + + for xmlVideoProfileMember in xmlVideoProfiles.findall("./videoprofilemember"): + memberName = xmlVideoProfileMember.get('name') + videoProfileStruct.members[memberName] = VulkanVideoProfileStructMember(memberName) + videoProfileStructMember = videoProfileStruct.members[memberName] + + for xmlVideoProfile in xmlVideoProfileMember.findall("./videoprofile"): + videoProfileStructMember.values[xmlVideoProfile.get('value')] = xmlVideoProfile.get('name') + + for xmlVideoCapabilities in xmlVideoCodec.findall("./videocapabilities"): + capabilityStructName = xmlVideoCapabilities.get('struct') + videoCodec.capabilities[capabilityStructName] = capabilityStructName + self.videoCodecsByStructName[capabilityStructName] = videoCodec + + for xmlVideoFormat in xmlVideoCodec.findall("./videoformat"): + videoFormatName = xmlVideoFormat.get('name') + videoFormatExtend = xmlVideoFormat.get('extend') + if videoFormatName is not None: + # This is a new video format category + videoFormatUsage = xmlVideoFormat.get('usage') + videoCodec.formats[videoFormatName] = VulkanVideoFormat(videoFormatName, videoFormatUsage) + videoFormat = videoCodec.formats[videoFormatName] + elif videoFormatExtend is not None: + # This is an extension to an already defined video format category + if videoFormatExtend in videoCodec.formats: + videoFormat = videoCodec.formats[videoFormatExtend] + else: + Log.f("Video format category '{0}' not found but it is attempted to be extended".format(videoFormatExtend)) + else: + Log.f('"name" or "extend" is attribute is required for "videoformat" element') + + for xmlVideoFormatProperties in xmlVideoFormat.findall("./videoformatproperties"): + propertiesStructName = xmlVideoFormatProperties.get('struct') + videoFormat.properties[propertiesStructName] = propertiesStructName + self.videoCodecsByStructName[propertiesStructName] = videoCodec + + for xmlVideoFormatRequiredCap in xmlVideoFormat.findall("./videorequirecapabilities"): + requiredCapStruct = xmlVideoFormatRequiredCap.get('struct') + requiredCapMember = xmlVideoFormatRequiredCap.get('member') + requiredCapValue = xmlVideoFormatRequiredCap.get('value') + videoFormat.requiredCaps.append(VulkanVideoRequiredCapabilities(requiredCapStruct, requiredCapMember, requiredCapValue)) + + def getBaseVideoProfileInfoFromVideoProfile(self, videoProfile): + if not 'profile' in videoProfile: + return None + profile = videoProfile['profile'] + if 'VkVideoProfileInfoKHR' in profile: + return profile['VkVideoProfileInfoKHR'] + else: + # Check also for possible aliases + for alias in self.structs['VkVideoProfileInfoKHR'].aliases: + if alias in profile: + return profile[alias] + return None + + def getBaseVideoFormatPropertiesFromVideoFormat(self, format): + if 'VkVideoFormatPropertiesKHR' in format: + return format['VkVideoFormatPropertiesKHR'] + else: + # Check also for possible aliases + for alias in self.structs['VkVideoFormatPropertiesKHR'].aliases: + if alias in format: + return format[alias] + Log.f("Did not find base video format properties in video format:\n{0}".format(json.dumps(format, indent=4))) + return None + + def getVideoCodecFromVideoProfile(self, videoProfile): + base = self.getBaseVideoProfileInfoFromVideoProfile(videoProfile) + if base is not None and 'videoCodecOperation' in base: + if base['videoCodecOperation'] not in self.videoCodecsByValue: + Log.f("Unrecognized videoCodecOperation in video profile:\n{0}".format(json.dumps(videoProfile['profile'], indent=4))) + return self.videoCodecsByValue[base['videoCodecOperation']] + else: + # No VkVideoProfileInfoKHR in the profile definition or no videoCodecOperation specified + # We do a reverse lookup based on the defined structures + videoCodec = None + structNames = set() + if 'profile' in videoProfile: + structNames = structNames.union(set(videoProfile['profile'].keys())) + if 'capabilities' in videoProfile: + structNames = structNames.union(set(videoProfile['capabilities'].keys())) + if 'formats' in videoProfile: + for videoFormat in videoProfile['formats']: + structNames = structNames.union(set(videoFormat.keys())) + for structName in structNames: + if structName in self.videoCodecsByStructName: + newMatchingVideoCodec = self.videoCodecsByStructName[structName] + if videoCodec is None or not videoCodec.isSpecific(): + videoCodec = newMatchingVideoCodec + if videoCodec is None: + # No match found, create an empty video codec to represent general requirements + videoCodec = VulkanVideoCodec("General") + return videoCodec + + + def getVideoProfileNameFromVideoProfile(self, videoProfile): + videoCodec = self.getVideoCodecFromVideoProfile(videoProfile) + base = self.getBaseVideoProfileInfoFromVideoProfile(videoProfile) + + # Video profile name always contains the codec name which is either the specific codec name, + # "General" to indicate no specific codec profile, or one of the codec categories like "Decode" and "Encode" + profileName = videoCodec.name + + if base is not None: + profile = videoProfile['profile'] + + # Helper function populating lookup tables with alias values + def genAliasValues(flagBitsTypeName, map): + flagBitsTypeName = self.enums[self.getNonAliasTypeName(flagBitsTypeName, self.enums)] + for alias, value in flagBitsTypeName.aliasValues.items(): + if alias in map: + map[value] = map[alias] + elif value in map: + map[value] = map[alias] + return map + + formatModifiers = [] + + chromaSubsamplingMap = genAliasValues('VkVideoChromaSubsamplingFlagBitsKHR', { + "VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR": "4:2:0", + "VK_VIDEO_CHROMA_SUBSAMPLING_422_BIT_KHR": "4:2:2", + "VK_VIDEO_CHROMA_SUBSAMPLING_444_BIT_KHR": "4:4:4", + "VK_VIDEO_CHROMA_SUBSAMPLING_MONOCHROME_BIT_KHR": "monochrome" + }) + if 'chromaSubsampling' in base: + # Include chroma subsampling info in the name as it is present + if len(base['chromaSubsampling']) != 1: + Log.f("Expected chromaSubsampling to only contain a single value in video profile:\n{0}".format(json.dumps(profile, indent=4))) + if base['chromaSubsampling'][0] not in chromaSubsamplingMap: + Log.f("Unrecognized chromaSubsampling in video profile:\n%s".format(json.dumps(profile, indent=4))) + chromaSubsampling = chromaSubsamplingMap[base['chromaSubsampling'][0]] + else: + chromaSubsampling = None + + if chromaSubsampling is not None: + formatModifiers.append(chromaSubsampling) + + bitDepthMap = genAliasValues('VkVideoComponentBitDepthFlagBitsKHR', { + "VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR": 8, + "VK_VIDEO_COMPONENT_BIT_DEPTH_10_BIT_KHR": 10, + "VK_VIDEO_COMPONENT_BIT_DEPTH_12_BIT_KHR": 12 + }) + if 'lumaBitDepth' in base: + if len(base['lumaBitDepth']) != 1: + Log.f("Expected lumaBitDepth to only contain a single value in video profile:\n{0}".format(json.dumps(profile, indent=4))) + if base['lumaBitDepth'][0] not in bitDepthMap: + Log.f("Unrecognized lumaBitDepth in profile:\n{0}".format(json.dumps(profile, indent=4))) + lumaBitDepth = bitDepthMap[base['lumaBitDepth'][0]] + else: + lumaBitDepth = None + + if chromaSubsampling != 'monochrome' and 'chromaBitDepth' in base: + if len(base['chromaBitDepth']) != 1: + Log.f("Expected chromaBitDepth to only contain a single value in video profile:\n{0}".format(json.dumps(profile, indent=4))) + if base['chromaBitDepth'][0] not in bitDepthMap: + Log.f("Unrecognized chromaBitDepth in profile:\n{0}".format(json.dumps(profile, indent=4))) + chromaBitDepth = bitDepthMap[base['chromaBitDepth'][0]] + else: + # For monochrome chromaBitDepth is ignored + # This case works also if lumaBitDepth is None because it was not present + chromaBitDepth = lumaBitDepth + + if lumaBitDepth == chromaBitDepth: + if lumaBitDepth is not None: + formatModifiers.append("{0}-bit".format(lumaBitDepth)) + else: + formatModifiers.append("{0}:{1}-bit".format(lumaBitDepth if lumaBitDepth is not None else "*", + chromaBitDepth if chromaBitDepth is not None else "*")) + + # If there is format information, then include it in the video profile name in parantheses + if len(formatModifiers) > 0: + profileName += " ({0})".format(" ".join(formatModifiers)) + + for profileStruct in videoCodec.profileStructs.values(): + profileStructData = None + if profileStruct.struct in profile: + profileStructData = profile[profileStruct.struct] + else: + # Check also for possible aliases + for alias in self.structs[profileStruct.struct].aliases: + if alias in profile: + profileStructData = profile[alias] + + if profileStructData is None: + # Profile struct is not present, this is a "wildcard" video profile definition + continue + + for profileStructMember in profileStruct.members.values(): + if not profileStructMember.name in profileStructData: + # Profile struct member is not present, this is a "wildcard" video profile definition + continue + + profileStructMemberValue = profileStructData[profileStructMember.name] + if isinstance(profileStructMemberValue, bool): + profileStructMemberValue = 'VK_TRUE' if profileStructMemberValue else 'VK_FALSE' + if profileStructMemberValue not in profileStructMember.values: + Log.f("Unrecognized profile struct member value for '{0}::{1}' in video profile:\n{2}".format(profileStruct.struct, profileStructMember.name, json.dumps(profile, indent=4))) + + # Append codec-specific profile information to the profile name + profileName += " {0}".format(profileStructMember.values[profileStructMemberValue]) + + return profileName + + def overwrite(self, structName, memberName, invalid_values, correct_value): + if structName in self.structs: + if (self.structs[structName].members[memberName].limittype == None or + self.structs[structName].members[memberName].limittype in invalid_values): + self.structs[structName].members[memberName].limittype = correct_value + elif (self.structs[structName].members[memberName].limittype != correct_value): + Log.w("Profiles is overwriting {0}::{1} to {2}, but current XML value is {3}".format(structName, memberName, correct_value, self.structs[structName].members[memberName].limittype)) + + def applyWorkarounds(self): + if self.headerVersionNumber.patch < 207: # vk.xml declares maxColorAttachments with 'bitmask' limittype before header 207 + self.structs['VkPhysicalDeviceLimits'].members['maxColorAttachments'].limittype = 'max' + + # TODO: We currently have to apply workarounds due to "noauto" limittypes and other bugs related to limittypes in the vk.xml + # These can only be solved permanently if we make modifications to the registry xml itself + self.overwrite('VkPhysicalDeviceLimits', 'subPixelPrecisionBits', ['noauto'], 'bits') + self.overwrite('VkPhysicalDeviceLimits', 'subTexelPrecisionBits', ['noauto'], 'bits') + self.overwrite('VkPhysicalDeviceLimits', 'mipmapPrecisionBits', ['noauto'], 'bits') + self.overwrite('VkPhysicalDeviceLimits', 'viewportSubPixelBits', ['noauto'], 'bits') + self.overwrite('VkPhysicalDeviceLimits', 'subPixelInterpolationOffsetBits', ['noauto'], 'bits') + self.overwrite('VkPhysicalDeviceLimits', 'minMemoryMapAlignment', ['noauto'], 'max,pot') + self.overwrite('VkPhysicalDeviceLimits', 'minTexelBufferOffsetAlignment', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceLimits', 'minUniformBufferOffsetAlignment', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceLimits', 'minStorageBufferOffsetAlignment', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceLimits', 'optimalBufferCopyOffsetAlignment', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceLimits', 'optimalBufferCopyRowPitchAlignment', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceLimits', 'nonCoherentAtomSize', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceLimits', 'timestampPeriod', ['noauto', 'min,mul'], 'exact') # resolve https://github.com/KhronosGroup/Vulkan-Profiles/issues/769 + self.overwrite('VkPhysicalDeviceLimits', 'bufferImageGranularity', ['noauto'], 'min,mul') + self.overwrite('VkPhysicalDeviceLimits', 'pointSizeGranularity', ['max'], 'min,mul') + self.overwrite('VkPhysicalDeviceLimits', 'lineWidthGranularity', ['max'], 'min,mul') + self.overwrite('VkPhysicalDeviceLimits', 'strictLines', ['noauto', 'bitmask', 'exact'], 'max') + self.overwrite('VkPhysicalDeviceLimits', 'standardSampleLocations', ['noauto', 'bitmask', 'exact'], 'max') + + self.overwrite('VkPhysicalDeviceSparseProperties', 'residencyAlignedMipSize', ['bitmask', 'not'], 'min') + + self.overwrite('VkPhysicalDeviceVulkan11Properties', 'deviceUUID', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan11Properties', 'driverUUID', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan11Properties', 'deviceLUID', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan11Properties', 'deviceNodeMask', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan11Properties', 'deviceLUIDValid', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan11Properties', 'subgroupSize', ['None'], 'max,pot') + self.overwrite('VkPhysicalDeviceVulkan11Properties', 'pointClippingBehavior', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan11Properties', 'protectedNoFault', ['None'], 'exact') + + self.overwrite('VkPhysicalDeviceVulkan12Properties', 'driverID', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceVulkan12Properties', 'driverName', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceVulkan12Properties', 'driverInfo', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceVulkan12Properties', 'conformanceVersion', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceVulkan12Properties', 'denormBehaviorIndependence', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan12Properties', 'roundingModeIndependence', ['None'], 'exact') + + self.overwrite('VkPhysicalDeviceVulkan13Properties', 'storageTexelBufferOffsetAlignmentBytes', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceVulkan13Properties', 'storageTexelBufferOffsetSingleTexelAlignment', ['noauto'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan13Properties', 'uniformTexelBufferOffsetAlignmentBytes', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceVulkan13Properties', 'uniformTexelBufferOffsetSingleTexelAlignment', ['noauto'], 'exact') + self.overwrite('VkPhysicalDeviceVulkan13Properties', 'minSubgroupSize', ['min'], 'min,pot') + self.overwrite('VkPhysicalDeviceVulkan13Properties', 'maxSubgroupSize', ['max'], 'max,pot') + + self.overwrite('VkPhysicalDeviceVulkan14Properties', 'maxCombinedImageSamplerDescriptorCount', ['None'], 'max') + + self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'storageTexelBufferOffsetAlignmentBytes', ['None'], 'min,pot') + self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'storageTexelBufferOffsetSingleTexelAlignment', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'uniformTexelBufferOffsetAlignmentBytes', ['None'], 'min,pot') + self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'uniformTexelBufferOffsetSingleTexelAlignment', ['None'], 'exact') + + self.overwrite('VkPhysicalDeviceProperties', 'apiVersion', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceProperties', 'driverVersion', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceProperties', 'vendorID', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceProperties', 'deviceID', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceProperties', 'deviceType', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceProperties', 'deviceName', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceProperties', 'pipelineCacheUUID', ['None'], 'noauto') + + self.overwrite('VkPhysicalDeviceToolProperties', 'name', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceToolProperties', 'version', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceToolProperties', 'purposes', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceToolProperties', 'description', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceToolProperties', 'layer', ['None'], 'noauto') + + self.overwrite('VkPhysicalDeviceSubgroupSizeControlProperties', 'minSubgroupSize', ['None'], 'min,pot') + self.overwrite('VkPhysicalDeviceSubgroupSizeControlProperties', 'maxSubgroupSize', ['None'], 'max,pot') + + self.overwrite('VkPhysicalDeviceDriverProperties', 'driverID', ['noauto'], 'exact') + self.overwrite('VkPhysicalDeviceDriverProperties', 'driverName', ['noauto'], 'exact') + self.overwrite('VkPhysicalDeviceDriverProperties', 'driverInfo', ['noauto'], 'exact') + self.overwrite('VkPhysicalDeviceDriverProperties', 'conformanceVersion', ['noauto'], 'exact') + + self.overwrite('VkPhysicalDeviceIDProperties', 'deviceUUID', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceIDProperties', 'driverUUID', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceIDProperties', 'deviceLUID', ['None', 'noauto'], 'max') + self.overwrite('VkPhysicalDeviceIDProperties', 'deviceNodeMask', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceIDProperties', 'deviceLUIDValid', ['None', 'noauto'], 'max') + + self.overwrite('VkPhysicalDeviceSubgroupProperties', 'subgroupSize', ['None'], 'max,pot') + + self.overwrite('VkPhysicalDevicePointClippingProperties', 'pointClippingBehavior', ['None'], 'exact') + + self.overwrite('VkPhysicalDeviceProtectedMemoryProperties', 'protectedNoFault', ['None'], 'exact') + + self.overwrite('VkPhysicalDeviceFloatControlsProperties', 'denormBehaviorIndependence', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceFloatControlsProperties', 'roundingModeIndependence', ['None'], 'exact') + + self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'storageTexelBufferOffsetSingleTexelAlignment', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceTexelBufferAlignmentProperties', 'uniformTexelBufferOffsetSingleTexelAlignment', ['None'], 'exact') + + self.overwrite('VkPhysicalDevicePortabilitySubsetPropertiesKHR', 'minVertexInputBindingStrideAlignment', ['None'], 'min,pot') + + self.overwrite('VkPhysicalDeviceFragmentShadingRatePropertiesKHR', 'maxFragmentShadingRateAttachmentTexelSizeAspectRatio', ['None'], 'max,pot') + self.overwrite('VkPhysicalDeviceFragmentShadingRatePropertiesKHR', 'maxFragmentSizeAspectRatio', ['None'], 'max,pot') + self.overwrite('VkPhysicalDeviceFragmentShadingRatePropertiesKHR', 'maxFragmentShadingRateCoverageSamples', ['None'], 'max') + + self.overwrite('VkPhysicalDeviceRayTracingPipelinePropertiesKHR', 'shaderGroupHandleSize', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceRayTracingPipelinePropertiesKHR', 'shaderGroupBaseAlignment', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceRayTracingPipelinePropertiesKHR', 'shaderGroupHandleCaptureReplaySize', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceRayTracingPipelinePropertiesKHR', 'shaderGroupHandleAlignment', ['None'], 'min,pot') + + self.overwrite('VkPhysicalDeviceFragmentShadingRatePropertiesKHR', 'maxFragmentShadingRateRasterizationSamples', ['None'], 'max') + + self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'primitiveOverestimationSize', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'extraPrimitiveOverestimationSizeGranularity', ['None'], 'min,mul') + self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'conservativePointAndLineRasterization', ['None', 'bitmask'], 'max') + self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'degenerateTrianglesRasterized', ['None'], 'exact') + self.overwrite('VkPhysicalDeviceConservativeRasterizationPropertiesEXT', 'degenerateLinesRasterized', ['None'], 'exact') + + self.overwrite('VkPhysicalDeviceLineRasterizationPropertiesEXT', 'lineSubPixelPrecisionBits', ['None'], 'bits') + + self.overwrite('VkPhysicalDeviceTransformFeedbackPropertiesEXT', 'maxTransformFeedbackBufferDataStride', ['None'], 'max') + + self.overwrite('VkPhysicalDeviceExternalMemoryHostPropertiesEXT', 'minImportedHostPointerAlignment', ['None'], 'min,pot') + + self.overwrite('VkPhysicalDevicePCIBusInfoPropertiesEXT', 'pciDomain', ['None'], 'noauto') + self.overwrite('VkPhysicalDevicePCIBusInfoPropertiesEXT', 'pciBus', ['None'], 'noauto') + self.overwrite('VkPhysicalDevicePCIBusInfoPropertiesEXT', 'pciDevice', ['None'], 'noauto') + self.overwrite('VkPhysicalDevicePCIBusInfoPropertiesEXT', 'pciFunction', ['None'], 'noauto') + + self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'hasPrimary', ['None', 'bitmask'], 'max') + self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'hasRender', ['None', 'bitmask'], 'max') + self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'primaryMajor', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'primaryMinor', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'renderMajor', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceDrmPropertiesEXT', 'renderMinor', ['None'], 'noauto') + + self.overwrite('VkPhysicalDeviceFragmentDensityMap2PropertiesEXT', 'subsampledLoads', ['noauto'], 'exact') + self.overwrite('VkPhysicalDeviceFragmentDensityMap2PropertiesEXT', 'subsampledCoarseReconstructionEarlyAccess', ['noauto'], 'exact') + + self.overwrite('VkPhysicalDeviceSampleLocationsPropertiesEXT', 'sampleLocationSubPixelBits', ['noauto'], 'bits') + + self.overwrite('VkPhysicalDeviceRobustness2PropertiesEXT', 'robustStorageBufferAccessSizeAlignment', ['noauto'], 'min,pot') + self.overwrite('VkPhysicalDeviceRobustness2PropertiesEXT', 'robustUniformBufferAccessSizeAlignment', ['noauto'], 'min,pot') + + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'shaderEngineCount', ['max'], 'exact') + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'shaderArraysPerEngineCount', ['max'], 'exact') + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'computeUnitsPerShaderArray', ['max'], 'exact') + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'simdPerComputeUnit', ['max'], 'exact') + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'wavefrontsPerSimd', ['max'], 'exact') + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'sgprsPerSimd', ['max'], 'exact') + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'sgprAllocationGranularity', ['noauto'], 'min,mul') + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'vgprsPerSimd', ['max'], 'exact') + self.overwrite('VkPhysicalDeviceShaderCorePropertiesAMD', 'vgprAllocationGranularity', ['noauto'], 'min,mul') + + self.overwrite('VkPhysicalDeviceSubpassShadingPropertiesHUAWEI', 'maxSubpassShadingWorkgroupSizeAspectRatio', ['noauto'], 'max,pot') + + self.overwrite('VkPhysicalDeviceRayTracingPropertiesNV', 'shaderGroupHandleSize', ['noauto'], 'exact') + self.overwrite('VkPhysicalDeviceRayTracingPropertiesNV', 'shaderGroupBaseAlignment', ['noauto'], 'exact') + + self.overwrite('VkPhysicalDeviceShadingRateImagePropertiesNV', 'shadingRateTexelSize', ['noauto'], 'exact') + + self.overwrite('VkPhysicalDeviceMeshShaderPropertiesNV', 'meshOutputPerVertexGranularity', ['noauto'], 'min,mul') + self.overwrite('VkPhysicalDeviceMeshShaderPropertiesNV', 'meshOutputPerPrimitiveGranularity', ['noauto'], 'min,mul') + + self.overwrite('VkPhysicalDevicePipelineRobustnessPropertiesEXT', 'defaultRobustnessStorageBuffers', ['noauto'], 'exact') + self.overwrite('VkPhysicalDevicePipelineRobustnessPropertiesEXT', 'defaultRobustnessUniformBuffers', ['noauto'], 'exact') + self.overwrite('VkPhysicalDevicePipelineRobustnessPropertiesEXT', 'defaultRobustnessVertexInputs', ['noauto'], 'exact') + self.overwrite('VkPhysicalDevicePipelineRobustnessPropertiesEXT', 'defaultRobustnessImages', ['noauto'], 'exact') + + self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV', 'minSequencesCountBufferOffsetAlignment', ['noauto'], 'min') + self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV', 'minSequencesIndexBufferOffsetAlignment', ['noauto'], 'min') + self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV', 'minIndirectCommandsBufferOffsetAlignment', ['noauto'], 'min') + + self.overwrite('VkPhysicalDeviceFragmentDensityMapOffsetPropertiesQCOM', 'fragmentDensityOffsetGranularity', ['max'], 'min,mul') + + self.overwrite('VkPhysicalDeviceSchedulingControlsPropertiesARM', 'schedulingControlsFlags', ['None'], 'bitmask') + + self.overwrite('VkPhysicalDeviceExternalFormatResolvePropertiesANDROID', 'nullColorAttachmentWithExternalFormatResolve', ['noauto', 'not'], 'min') + + self.overwrite('VkPhysicalDeviceRenderPassStripedPropertiesARM', 'renderPassStripeGranularity', ['None', 'min', 'max,mul'], 'min,mul') + self.overwrite('VkPhysicalDeviceRenderPassStripedPropertiesARM', 'maxRenderPassStripes', ['None'], 'max') + + self.overwrite('VkPhysicalDeviceMaintenance6PropertiesKHR', 'maxCombinedImageSamplerDescriptorCount', ['None'], 'max') + + self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT', 'supportedIndirectCommandsInputModes', ['None'], 'bitmask') + self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT', 'supportedIndirectCommandsShaderStages', ['None'], 'bitmask') + self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT', 'supportedIndirectCommandsShaderStagesPipelineBinding', ['None'], 'bitmask') + self.overwrite('VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT', 'supportedIndirectCommandsShaderStagesShaderBinding', ['None'], 'bitmask') + + self.overwrite('VkPhysicalDeviceCooperativeVectorPropertiesNV', 'maxCooperativeVectorComponents', ['None'], 'max') + + self.overwrite('VkPhysicalDeviceGpaPropertiesAMD', 'flags', ['noauto'], 'bitmask') + + # TODO: The registry xml is also missing limittype definitions for format and queue family properties + # For now we just add the important ones, this needs a larger overhaul in the vk.xml + self.overwrite('VkFormatProperties', 'linearTilingFeatures', ['None'], 'bitmask') + self.overwrite('VkFormatProperties', 'optimalTilingFeatures', ['None'], 'bitmask') + self.overwrite('VkFormatProperties', 'bufferFeatures', ['None'], 'bitmask') + self.overwrite('VkFormatProperties3', 'linearTilingFeatures', ['None'], 'bitmask') + self.overwrite('VkFormatProperties3', 'optimalTilingFeatures', ['None'], 'bitmask') + self.overwrite('VkFormatfProperties3', 'bufferFeatures', ['None'], 'bitmask') + + self.overwrite('VkQueueFamilyProperties', 'queueFlags', ['None'], 'bitmask') + self.overwrite('VkQueueFamilyProperties', 'queueCount', ['None'], 'max') + self.overwrite('VkQueueFamilyProperties', 'timestampValidBits', ['None'], 'bits') + self.overwrite('VkQueueFamilyProperties', 'minImageTransferGranularity', ['None'], 'min,mul') + + self.overwrite('VkSparseImageFormatProperties', 'aspectMask', ['None'], 'bitmask') + self.overwrite('VkSparseImageFormatProperties', 'imageGranularity', ['None'], 'min,mul') + self.overwrite('VkSparseImageFormatProperties', 'flags', ['None'], 'bitmask') + + self.overwrite('VkPhysicalDeviceDescriptorBufferTensorPropertiesARM', 'tensorCaptureReplayDescriptorDataSize', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceDescriptorBufferTensorPropertiesARM', 'tensorViewCaptureReplayDescriptorDataSize', ['None'], 'noauto') + self.overwrite('VkPhysicalDeviceDescriptorBufferTensorPropertiesARM', 'tensorDescriptorSize', ['None'], 'max') + + # TODO: The registry xml contains some return structures that contain count + pointers to arrays + # While the script itself is prepared to drop those, as they are ill-formed, as return structures + # should never contain such pointers, some of the structures (e.g. 'VkVideoProfilesKHR') actually + # doesn't even have the proper 'len' attribute to be able to detect the dynamic array + # Hence here we simply remove such "disallow-listed" structs so that they don't get in the way + self.structs.pop('VkDrmFormatModifierPropertiesListEXT', None) + self.structs.pop('VkDrmFormatModifierPropertiesList2EXT', None) + + def getExtensionPromotedToVersion(self, extensionName): + promotedTo = self.extensions[extensionName].promotedTo.copy() + version = None + while len(promotedTo) > 0: + target = promotedTo[0] + if target in self.extensions: + # Functionality was promoted to another extension, continue with that + promotedTo.remove(target) + promotedTo.extend(self.extensions[target].promotedTo) + elif target in self.versions: + # Found extension in a core API version, we're done + version = self.versions[target] + break + else: + # Version or extension is not included in the target API + promotedTo.remove(target) + return version + + def getExtensionPromotedToExtensionList(self, extensionName): + promotedTo = self.extensions[extensionName].promotedTo.copy() + extensions = [] + while len(promotedTo) > 0: + target = promotedTo[0] + if target in self.extensions: + # Functionality was promoted to another extension, add to list and continue with that + extensions.append(target) + promotedTo.remove(target) + promotedTo.extend(self.extensions[target].promotedTo) + else: + # Extension is not included in the target API or is a version, skip + promotedTo.remove(target) + return extensions + + def getChainableStructDef(self, name, extends): + structDef = self.structs.get(name) + if structDef == None: + Log.f("Structure '{0}' does not exist".format(name)) + if structDef.sType == None: + Log.f("Structure '{0}' is not chainable".format(name)) + if not extends in structDef.extends + [ name ]: + Log.f("Structure '{0}' does not extend '{1}'".format(name, extends)) + return structDef + + def evalArraySize(self, arraySize): + if isinstance(arraySize, str): + if arraySize in self.constants: + return int(self.constants[arraySize]) + else: + Log.f("Invalid array size '{0}'".format(arraySize)) + else: + return arraySize + + def getNonAliasTypeName(self, alias, types): + typeDef = types[alias] + if typeDef.isAlias: + for alias in typeDef.aliases: + if not types[alias].isAlias: + return alias + else: + return alias + + From d6ad1eca5552953e7e5dd4d7904e10fdbe64404e Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 15 Jul 2026 22:13:05 +0200 Subject: [PATCH 03/17] profiles: Rewrite schema generator with VulkanObject, bugfixing and optimizations --- CHANGELOG.md | 8 +- profiles/CMakeLists.txt | 3 +- profiles/test/CMakeLists.txt | 3 + scripts/gen_profiles_solution.py | 8 +- scripts/source/generate_profiles_schema.py | 590 +++++++++++++++++++++ scripts/source/generate_schema.py | 2 +- scripts/source/vulkan_object_utils.py | 12 + scripts/tests/test_vulkan_object_utils.py | 27 +- 8 files changed, 648 insertions(+), 5 deletions(-) create mode 100644 scripts/source/generate_profiles_schema.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4498abcd..863e7ca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,17 @@ ### Features: - Implement `VK_NO_PROTOTYPES` support #734 +- Add `vkprofiles` executable which bundles the python scripts in a standalone executable + +### Improvements: +- Improve profiles schema to support capabilities dynamic structures +- Optimize profiles schema to avoid duplicated values ### Deprecation: +- `gen_profiles*.py` file are all deprecated and replaced by `vkprofiles` - Require Vulkan 1.1 -## [Vulkan Profiles Tools 1.4.356](https://github.com/KhronosGroup/Vulkan-Profiles/tree/sdk-1.4.356.0) - July 2026 +## [Vulkan Profiles Tools 1.4.357](https://github.com/KhronosGroup/Vulkan-Profiles/tree/sdk-1.4.357.0) - July 2026 ### Features: - Add `profiles.py` python script, to pull dependent extensions into a source profiles file (ALPHA) diff --git a/profiles/CMakeLists.txt b/profiles/CMakeLists.txt index 7e471347..6326247e 100644 --- a/profiles/CMakeLists.txt +++ b/profiles/CMakeLists.txt @@ -52,7 +52,8 @@ set(PROFILES_FILES_FOR_ANDROID_DOC # Generate profiles schema add_custom_target(VpGenerate-ProfilesSchema COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/schema - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --output-schema ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} diff --git a/profiles/test/CMakeLists.txt b/profiles/test/CMakeLists.txt index 4860639e..0cbc5494 100644 --- a/profiles/test/CMakeLists.txt +++ b/profiles/test/CMakeLists.txt @@ -20,6 +20,8 @@ set(TEST_FILE test_validate.cpp) set(TEST_NAME VpProfile_test_schema_validation) +set(REGISTRY_PATH "${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry") + file(GLOB FILES_UNIT_TEST ${CMAKE_CURRENT_SOURCE_DIR}/data/*.json) file(GLOB FILES_PROFILE ${CMAKE_CURRENT_SOURCE_DIR}/../*.json) file(GLOB SCHEMA_PROFILE ${CMAKE_CURRENT_SOURCE_DIR}/../../schema/${PROFILES_SCHEMA_FILENAME}) @@ -37,5 +39,6 @@ target_compile_definitions(${TEST_NAME} PRIVATE target_link_libraries(${TEST_NAME} PRIVATE GTest::gtest GTest::gtest_main jsoncpp_static valijson) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME} PROPERTIES FOLDER "Profiles schema") +set_tests_properties(${TEST_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${REGISTRY_PATH}") add_dependencies(${TEST_NAME} VpGenerate-ProfilesDesktopMax VpGenerate-ProfilesDesktopBaseline) diff --git a/scripts/gen_profiles_solution.py b/scripts/gen_profiles_solution.py index d225054f..647f482c 100644 --- a/scripts/gen_profiles_solution.py +++ b/scripts/gen_profiles_solution.py @@ -30,8 +30,11 @@ from string import Template from source.generate_schema import VulkanProfilesSchemaGenerator +from source.generate_profiles_schema import VulkanProfilesSchemaGenerator2 from source.vulkan_registry import VulkanRegistry, VulkanVersionNumber from source.log import Log +from vulkan_object import VulkanObject +from source.vulkan_object_utils import initVulkanObject, VK_VERSION, gatherDependentExtensions COPYRIGHT_HEADER = ''' /* @@ -5794,11 +5797,14 @@ def gen_videoFormats(self, videoProfileName, videoCodec): if args.registry != None: registry = VulkanRegistry(args.registry, args.api) + vk: VulkanObject = initVulkanObject(args.registry, True) if args.output_schema != None or args.validate: generator = VulkanProfilesSchemaGenerator(registry) + generator2 = VulkanProfilesSchemaGenerator2(vk) if args.output_schema is not None: - generator.generate(args.output_schema) + #generator.generate(args.output_schema) + generator2.generate(args.output_schema) if args.validate: generator.validate() schema = generator.schema diff --git a/scripts/source/generate_profiles_schema.py b/scripts/source/generate_profiles_schema.py new file mode 100644 index 00000000..d11b9618 --- /dev/null +++ b/scripts/source/generate_profiles_schema.py @@ -0,0 +1,590 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2021-2026 LunarG, Inc. +# Copyright (c) 2023-2024 RasterGrid Kft. +# +# 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. +# +# Authors: +# - Daniel Rakos +# - Christophe Riccio + +import json +from collections import OrderedDict + +from source.vulkan_object_utils import gatherDynamicStructs +from source.log import Log + +EXTERNAL_TYPES = { + "Display", "VisualID", "Window", "ANativeWindow", "wl_display", "wl_surface", + "_XDisplay", "HINSTANCE", "HWND", "HANDLE", "DWORD", "LPCWSTR", "zx_handle_t", + "GgpStreamDescriptor", "GgpFrameToken", "CAMetalLayer", "SECURITY_ATTRIBUTES" +} + +class VulkanProfilesSchemaGenerator2(): + def __init__(self, vk): + """ + :param vk: An instance of VulkanObject from vulkan_object.py + """ + self.vk = vk + + # Call the global discovery helper passing the VulkanObject parameter + self.valid_dynamic_structs = gatherDynamicStructs(vk) + + self.schema = self.gen_schema() + + def validate(self): + try: + import jsonschema + Log.i("Validating JSON profiles schema...") + jsonschema.Draft7Validator.check_schema(self.schema) + except ModuleNotFoundError: + Log.w("`jsonschema` module is not installed, schema validation skip") + + def generate(self, outSchema): + Log.i("Generating '{0}'...".format(outSchema)) + with open(outSchema, 'w') as f: + f.write(json.dumps(self.schema, indent=4)) + + def getNonAliasTypeName(self, name): + if name in self.vk.structs: + return name + for struct_name, struct_def in self.vk.structs.items(): + if name in struct_def.aliases: + return struct_name + return name + + def evalArraySize(self, size): + if size is None: + return None + if isinstance(size, list): + return [self.evalArraySize(s) for s in size] + if isinstance(size, int): + return size + if isinstance(size, str): + if size in self.vk.constants: + val = self.vk.constants[size].value + try: + return int(val) + except (ValueError, TypeError): + return val + try: + clean_str = size.rstrip('UuLl') + if clean_str.startswith('0x') or clean_str.startswith('0X'): + return int(clean_str, 16) + return int(clean_str) + except ValueError: + return size + return size + + def gen_schema(self): + definitions = self.gen_baseDefinitions() + extensions = self.gen_extensions() + features = self.gen_features(definitions) + properties = self.gen_properties(definitions) + formats = self.gen_formats(definitions) + queueFamilies = self.gen_queueFamilies(definitions) + videoProfiles = self.gen_videoProfiles(definitions) + videoCapabilities = self.gen_videoCapabilities(definitions) + videoFormats = self.gen_videoFormats(definitions) + + versionStr = self.vk.headerVersionComplete + + return OrderedDict({ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.khronos.org/vulkan/profiles-0.8.2-{0}.json#".format(self.vk.headerVersion), + "title": "Vulkan Profiles Schema for Vulkan {0}".format(versionStr), + "additionalProperties": True, + "required": [ + "capabilities", + "profiles" + ], + "definitions": definitions, + "properties": OrderedDict({ + "capabilities": OrderedDict({ + "description": "The block that specifies the list of capabilities sets.", + "type": "object", + "additionalProperties": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": OrderedDict({ + "extensions": OrderedDict({ + "description": "The block that stores required extensions.", + "type": "object", + "additionalProperties": False, + "properties": extensions + }), + "features": OrderedDict({ + "description": "The block that stores features requirements.", + "type": "object", + "additionalProperties": False, + "properties": features + }), + "properties": OrderedDict({ + "description": "The block that stores properties requirements.", + "type": "object", + "additionalProperties": False, + "properties": properties + }), + "formats": OrderedDict({ + "description": "The block that store formats capabilities definitions.", + "type": "object", + "additionalProperties": False, + "properties": formats + }), + "queueFamiliesProperties": OrderedDict({ + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": queueFamilies + }) + }), + "videoProfiles": OrderedDict({ + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": OrderedDict({ + "profile": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": videoProfiles + }), + "capabilities": OrderedDict({ + "type": "object", + "addationalProperties": False, + "properties": videoCapabilities + }), + "formats": OrderedDict({ + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": videoFormats + }) + }) + }) + }) + }) + }) + }) + }), + "profiles": OrderedDict({ + "description": "The list of profile definitions.", + "type": "object", + "additionalProperties": False, + "patternProperties": OrderedDict({ + "^VP_[A-Z0-9]+_[A-Za-z0-9_]+": OrderedDict({ + "type": "object", + "additionalProperties": False, + "required": [ + "label", + "description", + "version", + "api-version", + "capabilities" + ], + "properties": OrderedDict({ + "version": OrderedDict({ + "description": "The revision of the profile.", + "type": "integer" + }), + "label": OrderedDict({ + "description": "The label used to present the profile to the Vulkan developer.", + "type": "string" + }), + "description": OrderedDict({ + "description": "The description of the profile.", + "type": "string" + }), + "status": OrderedDict({ + "description": "The developmet status of the profile: ALPHA, BETA, STABLE or DEPRECATED.", + "$ref": "#/definitions/status" + }), + "api-version": OrderedDict({ + "description": "The Vulkan API version against which the profile is written.", + "type": "string", + "pattern": "^[0-9]+.[0-9]+.[0-9]+$" + }), + "contributors": OrderedDict({ + "type": "object", + "description": "The list of contributors of the profile.", + "additionalProperties": OrderedDict({ + "$ref": "#/definitions/contributor" + }) + }), + "history": OrderedDict({ + "description": "The version history of the profile file", + "type": "array", + "uniqueItems": True, + "minItems": 1, + "items": OrderedDict({ + "type": "object", + "required": [ + "revision", + "date", + "author", + "comment" + ], + "properties": OrderedDict({ + "revision": OrderedDict({ + "type": "integer" + }), + "date": OrderedDict({ + "type": "string", + "pattern": "((?:19|20)\\d\\d)-(0?[1-9]|1[012])-([12][0-9]|3[01]|0?[1-9])" + }), + "author": OrderedDict({ + "type": "string" + }), + "comment": OrderedDict({ + "type": "string" + }) + }) + }) + }), + "profiles": OrderedDict({ + "description": "The list of required profiles by the profile.", + "type": "array", + "additionalProperties": False, + "uniqueItems": True, + "items": OrderedDict({ + "type": "string" + }) + }), + "capabilities": OrderedDict({ + "description": "The list of required capability sets that can be referenced by a profile.", + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "string" + }) + } + ] + }) + }), + "optionals": OrderedDict({ + "description": "The list of optional capability sets that can be referenced by a profile.", + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "uniqueItems": True, + "items": OrderedDict({ + "type": "string" + }) + } + ] + }) + }), + "fallback": OrderedDict({ + "description": "The list of profiles recommended if the checked profile is not supported by the platform.", + "type": "array", + "additionalProperties": False, + "uniqueItems": True, + "items": OrderedDict({ + "type": "string" + }) + }) + }) + }) + }) + }) + }) + }) + + def gen_baseDefinitions(self): + return OrderedDict({ + "status": OrderedDict({ + "description": "The development status of the setting. When missing, this property is inherited from parent nodes. If no parent node defines it, the default value is 'STABLE'.", + "type": "string", + "enum": [ "ALPHA", "BETA", "STABLE", "DEPRECATED" ] + }), + "contributor": OrderedDict({ + "type": "object", + "additionalProperties": False, + "required": [ "company" ], + "properties": OrderedDict({ + "company": OrderedDict({ "type": "string" }), + "email": OrderedDict({ "type": "string", "pattern": "^[A-Za-z0-9_.]+@[a-zA-Z0-9-].[a-zA-Z0-9-.]+$" }), + "github": OrderedDict({ "type": "string", "pattern": "^[A-Za-z0-9_-]+$" }), + "contact": OrderedDict({ "type": "boolean" }) + }) + }), + "uint8_t": OrderedDict({ "type": "integer", "minimum": 0, "maximum": 255 }), + "int32_t": OrderedDict({ "type": "integer", "minimum": -2147483648, "maximum": 2147483647 }), + "uint32_t": OrderedDict({ "type": "integer", "minimum": 0, "maximum": 4294967295 }), + "int64_t": OrderedDict({ "type": "integer" }), + "uint64_t": OrderedDict({ "type": "integer", "minimum": 0 }), + "VkDeviceSize": OrderedDict({ "type": "integer", "minimum": 0 }), + "char": { "type": "string" }, + "float": { "type": "number" }, + "size_t": OrderedDict({ "type": "integer", "minimum": 0 }) + }) + + def gen_extensions(self): + gen = OrderedDict() + for extName in sorted(self.vk.extensions.keys()): + gen[extName] = { "type": "integer" } + return gen + + def gen_type(self, type_name, definitions): + if type_name == 'VkBool32': + return { "type": "boolean" } + + gen = { "$ref": "#/definitions/" + type_name } + + if type_name in definitions: + pass + elif type_name in self.vk.structs: + self.gen_struct(type_name, definitions) + elif self.vk.videoStd and type_name in self.vk.videoStd.structs: + self.gen_struct(type_name, definitions, is_video_std=True) + elif type_name in self.vk.enums: + self.gen_enum(type_name, definitions) + elif self.vk.videoStd and type_name in self.vk.videoStd.enums: + self.gen_enum(type_name, definitions, is_video_std=True) + elif type_name in self.vk.bitmasks or type_name in self.vk.flags: + self.gen_bitmask(type_name, definitions) + else: + if type_name not in definitions: + return { "type": "integer" } + + return gen + + def gen_array(self, type_name, size, sizeCap, definitions, len_attribute=None): + arraySize = self.evalArraySize(size) + if isinstance(arraySize, list) and len(arraySize) == 1: + arraySize = arraySize[0] + + if type_name == 'char': + max_len = arraySize - 1 if isinstance(arraySize, int) else 256 + return OrderedDict({ + "type": "string", + "maxLength": max(0, max_len) + }) + elif isinstance(arraySize, list): + return OrderedDict({ + "type": "array", + "items": self.gen_array(type_name, arraySize[1:], None, definitions, len_attribute), + "uniqueItems": False, + "maxItems": arraySize[0] + }) + else: + is_enum_group = (type_name in self.vk.enums or + type_name in self.vk.bitmasks or + type_name in self.vk.flags or + (self.vk.videoStd and (type_name in self.vk.videoStd.enums))) + + res = OrderedDict({ + "type": "array", + "items": self.gen_type(type_name, definitions), + "uniqueItems": True if is_enum_group else False + }) + + max_items = arraySize if isinstance(arraySize, int) else sizeCap + if max_items is not None and len_attribute is None: + res["maxItems"] = max_items + + if len_attribute is not None: + res["description"] = f"The number of items is determined by {len_attribute}" + + return res + + def gen_enum(self, name, definitions, is_video_std=False): + if name in definitions: + return + enumDef = self.vk.videoStd.enums[name] if is_video_std else self.vk.enums[name] + values = [] + for field in enumDef.fields: + values.append(field.name) + for alias in field.aliases: + values.append(alias) + + values = sorted(list(set(values))) + if len(values) == 0: + values = [ 0 ] + + definitions[name] = OrderedDict({ "enum": values }) + + def gen_bitmask_enum(self, name, definitions): + if name in definitions: + return + bitmaskDef = self.vk.bitmasks[name] + values = [] + for flag in bitmaskDef.flags: + values.append(flag.name) + for alias in flag.aliases: + values.append(alias) + + values = sorted(list(set(values))) + if len(values) == 0: + values = [ 0 ] + + definitions[name] = OrderedDict({ "enum": values }) + + def gen_bitmask(self, name, definitions): + if name in definitions: + return + + if name in self.vk.flags: + bitmask_name = self.vk.flags[name].bitmaskName + if bitmask_name and bitmask_name in self.vk.bitmasks: + self.gen_bitmask_enum(bitmask_name, definitions) + itemType = { "$ref": "#/definitions/" + bitmask_name } + else: + itemType = { "enum": [ 0 ] } + + definitions[name] = OrderedDict({ + "type": "array", + "items": itemType, + "uniqueItems": True + }) + elif name in self.vk.bitmasks: + self.gen_bitmask_enum(name, definitions) + + def gen_struct(self, name, definitions, is_video_std=False): + if name in definitions: + return + structDef = self.vk.videoStd.structs[name] if is_video_std else self.vk.structs[name] + + # Parity Rule 1: Skip structures containing unhandled dynamic pointer arrays using our automated helper list + for memberDef in structDef.members: + if memberDef.length is not None and memberDef.pointer: + if name not in self.valid_dynamic_structs: + return + + members = OrderedDict() + for memberDef in sorted(structDef.members, key=lambda m: m.name): + # Parity Rule 3: Skip structural runtime pointer metadata + if memberDef.name in ['sType', 'pNext']: + continue + + if memberDef.type in EXTERNAL_TYPES and not memberDef.type in definitions: + continue + + # Parity Rule 2: Discern arrays accurately using vulkan_object definitions + is_fixed_array = len(memberDef.fixedSizeArray) > 0 + is_dynamic_array = memberDef.length is not None and memberDef.pointer + + if is_fixed_array: + size = memberDef.fixedSizeArray + members[memberDef.name] = self.gen_array(memberDef.type, size, None, definitions) + elif is_dynamic_array: + array_size_member = memberDef.length + size_cap = 1 if name in self.valid_dynamic_structs else None + + members[memberDef.name] = self.gen_array( + memberDef.type, + array_size_member, + size_cap, + definitions, + len_attribute=array_size_member + ) + else: + members[memberDef.name] = self.gen_type(memberDef.type, definitions) + + definitions[name] = OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": members + }) + + def gen_structChainDefinitions(self, basename, definitions): + structNames = [ basename ] + if basename + '2' in self.vk.structs: + basename += '2' + structNames.append(basename) + + for structName in sorted(self.vk.structs.keys()): + structDef = self.vk.structs[structName] + if basename in structDef.extends: + structNames.append(structName) + + gen = OrderedDict() + for structName in structNames: + self.gen_struct(structName, definitions) + if structName in definitions: + gen[structName] = { "$ref": "#/definitions/" + structName } + + for alias in self.vk.structs[structName].aliases: + if alias != structName: + gen[alias] = gen[structName] + + return gen + + def gen_features(self, definitions): + return self.gen_structChainDefinitions("VkPhysicalDeviceFeatures", definitions) + + def gen_properties(self, definitions): + return self.gen_structChainDefinitions("VkPhysicalDeviceProperties", definitions) + + def gen_formats(self, definitions): + definitions['formatProperties'] = OrderedDict({ + "type": "object", + "additionalProperties": False, + "properties": self.gen_structChainDefinitions("VkFormatProperties", definitions) + }) + + gen = OrderedDict() + format_values = [] + if 'VkFormat' in self.vk.enums: + for field in self.vk.enums['VkFormat'].fields: + format_values.append(field.name) + for alias in field.aliases: + format_values.append(alias) + + for format_name in sorted(list(set(format_values))): + gen[format_name] = OrderedDict({ + "$ref": "#/definitions/formatProperties" + }) + return gen + + def gen_queueFamilies(self, definitions): + return self.gen_structChainDefinitions("VkQueueFamilyProperties", definitions) + + def gen_videoProfiles(self, definitions): + excludedSet = { "VkVideoDecodeUsageInfoKHR", "VkVideoEncodeUsageInfoKHR" } + videoProfiles = self.gen_structChainDefinitions("VkVideoProfileInfoKHR", definitions) + for excluded in excludedSet: + excluded = self.getNonAliasTypeName(excluded) + if excluded in videoProfiles: + del videoProfiles[excluded] + if excluded in self.vk.structs: + for alias in self.vk.structs[excluded].aliases: + if alias in videoProfiles: + del videoProfiles[alias] + return videoProfiles + + def gen_videoCapabilities(self, definitions): + return self.gen_structChainDefinitions("VkVideoCapabilitiesKHR", definitions) + + def gen_videoFormats(self, definitions): + return self.gen_structChainDefinitions("VkVideoFormatPropertiesKHR", definitions) diff --git a/scripts/source/generate_schema.py b/scripts/source/generate_schema.py index c0d401a8..5561da33 100644 --- a/scripts/source/generate_schema.py +++ b/scripts/source/generate_schema.py @@ -305,7 +305,7 @@ def gen_schema(self): }) }) }) - }), + }) }) }) }) diff --git a/scripts/source/vulkan_object_utils.py b/scripts/source/vulkan_object_utils.py index 33f9f208..76394932 100644 --- a/scripts/source/vulkan_object_utils.py +++ b/scripts/source/vulkan_object_utils.py @@ -234,3 +234,15 @@ def gatherDependentExtensions(vk: VulkanObject, version: VK_VERSION, ignore_exte result[extension] = extension_data.specVersionValue return result + +def gatherDynamicStructs(vk: VulkanObject): + """ + Global discovery function that automatically identifies all Vulkan structures + containing variable-length pointer arrays by scanning metadata within a VulkanObject. + """ + discovered = set() + for struct_name, struct_def in vk.structs.items(): + for member in struct_def.members: + if member.pointer and member.length is not None: + discovered.add(struct_name) + return sorted(list(discovered)) diff --git a/scripts/tests/test_vulkan_object_utils.py b/scripts/tests/test_vulkan_object_utils.py index 24ca21d0..51b639be 100644 --- a/scripts/tests/test_vulkan_object_utils.py +++ b/scripts/tests/test_vulkan_object_utils.py @@ -29,7 +29,7 @@ sys.path.insert(0, str(scripts_dir)) from vulkan_object import VulkanObject, StructCapabilityAlias, ExtensionCapabilityAlias -from source.vulkan_object_utils import initVulkanObject, VK_VERSION, gatherCapabilityAliases, gatherDependentExtensions, findExtensionVersion +from source.vulkan_object_utils import initVulkanObject, VK_VERSION, gatherCapabilityAliases, gatherDependentExtensions, findExtensionVersion, gatherDynamicStructs #from source.vulkan_object_version import buildVulkanVersionEnum class TestVulkanObjectUtils(unittest.TestCase): @@ -330,6 +330,31 @@ def testGatherDependentExtensions(self): self.assertEqual(dependent_extensions3, expected_extensions3) return + + def testGatherDynamicStructs(self): + """ + Verifies that gatherDynamicStructs correctly builds an automated layout + of valid dynamic array properties directly from the parsed VulkanObject. + """ + vk: VulkanObject = initVulkanObject(self.registry_path) + + # Programmatically discover all extensible dynamic array property containers + dynamic_structs = gatherDynamicStructs(vk) + + # Verify the list type and sorting + self.assertIsInstance(dynamic_structs, list) + + # Check for traditional dynamic property containers + self.assertIn("VkPhysicalDeviceHostImageCopyProperties", dynamic_structs) + + # Verify that the modern structures missing from the legacy path are discovered correctly + self.assertIn("VkPhysicalDeviceLayeredApiPropertiesListKHR", dynamic_structs) + self.assertIn("VkPhysicalDeviceGpaPropertiesAMD", dynamic_structs) + + # Ensure standard flat properties are NOT misclassified as dynamic arrays + self.assertNotIn("VkPhysicalDeviceFeatures2", dynamic_structs) + self.assertNotIn("VkPhysicalDeviceProperties2", dynamic_structs) + self.assertNotIn("VkPhysicalDeviceVulkan11Properties", dynamic_structs) if __name__ == '__main__': parser = argparse.ArgumentParser() From 47c00cb62c36335f98256cb60be2652c943a9fbf Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 14 Jul 2026 18:51:50 +0200 Subject: [PATCH 04/17] profiles: Fix dependencies and alone schema generation only --- layer/tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layer/tests/CMakeLists.txt b/layer/tests/CMakeLists.txt index 5eb12a5e..81ae9f8e 100644 --- a/layer/tests/CMakeLists.txt +++ b/layer/tests/CMakeLists.txt @@ -92,7 +92,7 @@ function(LayerTestAndroid NAME) vktestframework.cpp) add_test(NAME ${ANDROID_APK_NAME} COMMAND profiles_${NAME}) - add_dependencies(${ANDROID_APK_NAME} ProfilesLayer VpCreateDesktopBaseline) + add_dependencies(${ANDROID_APK_NAME} ProfilesLayer VpGenerate-Libraries) if (NOT ANDROID_SDK_HOME) set(ANDROID_SDK_HOME $ENV{ANDROID_SDK_HOME}) From 400cf8e802a46b7bfa43d9365859efae1ea7b5a0 Mon Sep 17 00:00:00 2001 From: Bob Ellison Date: Thu, 16 Jul 2026 13:36:35 -0600 Subject: [PATCH 05/17] build: use python venv The Vulkan-Profiles build requires unusual Python modules jsonschema and pyinstaller to be installed in whatever Python is used to build the standalone executables. Rather than rely on the system Python including these modules, this code change causes the build to create its on Python virtual environment, and install the necessary Python modules in the virtual environment, before using it to build the executables. CMakeLists.txt: - require Python 3.10, to insure pyinstaller 6 can be installed - create a Python virtual environment using requirements.txt - use the Python virtual environment to make FINAL_EXECUTABLE requirements.txt: new file - include all the Python modules that aren't part of the Python Standard Library --- CMakeLists.txt | 36 +++++++++++++++++++++++++++++++----- requirements.txt | 3 +++ 2 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 requirements.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bfa2aac..433ff1ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,34 @@ set(MERGE_SCRIPT ${PROJECT_SOURCE_DIR}/scripts/gen_profiles_file.py) set(SOLUTION_SCRIPT ${PROJECT_SOURCE_DIR}/scripts/gen_profiles_solution.py) set(PROFILES_SCRIPT ${PROJECT_SOURCE_DIR}/scripts/profiles.py) -find_package(Python3 REQUIRED COMPONENTS Interpreter) +# We need Python 3.10 or better to use pyinstaller 6. +find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter) + +# Some Python-based build operations (e.g. turning a Python script into a standalone +# executable) require custom Python modules. Here we'll create a custom +# Python virtual environment with the modules we need. +set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/venv") +if(WIN32) + set(VENV_PYTHON_EXECUTABLE "${VENV_DIR}/Scripts/python.exe") +else() + set(VENV_PYTHON_EXECUTABLE "${VENV_DIR}/bin/python3") +endif() + +# VENV_STAMP is used as a dependency for any target using the venv, to ensure +# that if the venv's dependencies are changed, the venv will be updated. +set(VENV_STAMP "${VENV_DIR}/venv.stamp") + +add_custom_command( + OUTPUT "${VENV_STAMP}" + COMMAND ${Python3_EXECUTABLE} -m venv "${VENV_DIR}" + COMMAND "${VENV_PYTHON_EXECUTABLE}" -m pip install --no-cache-dir -r "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt" + COMMAND ${CMAKE_COMMAND} -E touch "${VENV_STAMP}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt" + COMMENT "Creating Python virtual environment and installing dependencies..." + VERBATIM +) +# ============================================================================== + find_package(VulkanHeaders REQUIRED CONFIG QUIET) find_package(VulkanUtilityLibraries REQUIRED CONFIG QUIET) find_package(valijson REQUIRED CONFIG) @@ -96,11 +123,11 @@ function(add_pyinstaller_target TARGET_NAME) endif() set(FINAL_EXECUTABLE "${ARG_OUTPUT_DIR}/${EXE_NAME}") - # Generate the executable via PyInstaller + # Generate the executable via PyInstaller using the VENV python executable add_custom_command( OUTPUT "${FINAL_EXECUTABLE}" - COMMAND ${Python3_EXECUTABLE} -m PyInstaller --onefile --name "${ARG_OUTPUT_NAME}" --distpath "${ARG_OUTPUT_DIR}" "${ARG_SCRIPT}" - DEPENDS ${ARG_DEPENDS} + COMMAND "${VENV_PYTHON_EXECUTABLE}" -m PyInstaller --onefile --name "${ARG_OUTPUT_NAME}" --distpath "${ARG_OUTPUT_DIR}" "${ARG_SCRIPT}" + DEPENDS ${ARG_DEPENDS} "${VENV_STAMP}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMENT "Packaging ${TARGET_NAME} into a standalone executable..." VERBATIM @@ -142,4 +169,3 @@ add_pyinstaller_target(VpProfilesProcessor add_subdirectory(profiles) add_subdirectory(library) add_subdirectory(layer) - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..eb191857 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# Python modules needed for project builds +jsonschema == 4.* +pyinstaller == 6.* From df28a4c6c7ac77888da4bef17044a90ef21848c2 Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 20 Jul 2026 18:07:47 +0200 Subject: [PATCH 06/17] cmake: Fix registry python modules localisation --- .github/workflows/ci.yml | 8 +++++++ layer/CMakeLists.txt | 6 +++-- profiles/CMakeLists.txt | 48 ++++++++++++++++++++++++++-------------- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b654ac55..4357ab9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,8 @@ jobs: python-version: '3.11' - name: Install jsonschema run: python3 -m pip install jsonschema + - name: Install pyparsing + run: python3 -m pip install pyparsing - name: Install pyinstaller run: python3 -m pip install pyinstaller - name: Install Dependencies @@ -79,8 +81,12 @@ jobs: - uses: actions/setup-python@v7 with: python-version: '3.11' + - name: pip update + run: python3 -m pip install --upgrade pip - name: Install jsonschema run: python3 -m pip install jsonschema + - name: Install pyparsing + run: python3 -m pip install pyparsing - name: Install pyinstaller run: python3 -m pip install pyinstaller - name: Setup ccache @@ -120,6 +126,8 @@ jobs: python-version: '3.11' - name: Install jsonschema run: python3 -m pip install jsonschema + - name: Install pyparsing + run: python3 -m pip install pyparsing - name: Install pyinstaller run: python3 -m pip install pyinstaller - uses: lukka/get-cmake@latest diff --git a/layer/CMakeLists.txt b/layer/CMakeLists.txt index eab5c7d9..20199f90 100644 --- a/layer/CMakeLists.txt +++ b/layer/CMakeLists.txt @@ -168,7 +168,8 @@ if(ANDROID) endif() add_custom_target(VpLayer_generate ALL - COMMAND Python3::Interpreter ${LAYER_PYTHON_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${LAYER_PYTHON_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --out-layer ${CMAKE_SOURCE_DIR}/layer/profiles_generated.cpp @@ -182,7 +183,8 @@ add_dependencies(ProfilesLayer VpLayer_generate) source_group("Python Files" FILES ${TESTS_PYTHON_SCRIPT}) add_custom_target(VpLayer_generate_tests ALL - COMMAND Python3::Interpreter ${TESTS_PYTHON_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${TESTS_PYTHON_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --out-profile ${CMAKE_SOURCE_DIR}/profiles/test/data/VP_LUNARG_test_api_generated.json diff --git a/profiles/CMakeLists.txt b/profiles/CMakeLists.txt index 6326247e..15409d66 100644 --- a/profiles/CMakeLists.txt +++ b/profiles/CMakeLists.txt @@ -74,7 +74,8 @@ set(PROFILE_DESKTOP_MAX_2026_API_VERSION "1.4.353") # Generate profiles files add_custom_target(VpGenerate-ProfilesDesktopMax ALL - COMMAND Python3::Interpreter ${MERGE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_max_2024 --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_desktop_max_2024.json @@ -84,7 +85,8 @@ add_custom_target(VpGenerate-ProfilesDesktopMax ALL --profile-date 2023-11-01 --profile-stage BETA --profile-api-version ${PROFILE_DESKTOP_MAX_2024_API_VERSION} - COMMAND Python3::Interpreter ${MERGE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_max_2026 --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_desktop_max_2026.json @@ -101,7 +103,8 @@ set_target_properties(VpGenerate-ProfilesDesktopMax PROPERTIES FOLDER "Profiles add_dependencies(VpGenerate-ProfilesDesktopMax VpGenerate-ProfilesSchema) add_custom_target(VpGenerate-ProfilesDesktopBaseline ALL - COMMAND Python3::Interpreter ${MERGE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --config ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline_config.json --output-path ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline.json @@ -124,14 +127,16 @@ add_custom_target(VpGenerate-Libraries ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/profiles/VP_KHR_roadmap.json # ${CMAKE_CURRENT_LIST_DIR}/Khronos/VP_KHR_roadmap.json ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_minimum_requirements.json ${CMAKE_CURRENT_LIST_DIR} - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} --input-filenames ${PROFILES_FILES_FOR_API_LIBRARY} --output-schema ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} --validate - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -140,7 +145,8 @@ add_custom_target(VpGenerate-Libraries --output-library-src ${PROJECT_SOURCE_DIR}/library/source --output-library-filename "vulkan_profiles" --config release - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -149,7 +155,8 @@ add_custom_target(VpGenerate-Libraries --output-library-src ${PROJECT_SOURCE_DIR}/library/source/debug --output-library-filename "vulkan_profiles" --config debug - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/Android @@ -158,7 +165,8 @@ add_custom_target(VpGenerate-Libraries --output-library-src ${PROJECT_SOURCE_DIR}/library/test --output-library-filename "generated_vulkan_profiles_android" --config release - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -167,7 +175,8 @@ add_custom_target(VpGenerate-Libraries --output-library-src ${PROJECT_SOURCE_DIR}/library/test --output-library-filename "generated_vulkan_profiles" --config release - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -183,19 +192,22 @@ set_target_properties(VpGenerate-Libraries PROPERTIES FOLDER "Profiles generator add_dependencies(VpGenerate-Libraries VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesDesktopMax) add_custom_target(VpGenerate-Markdown - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} --input-filenames ${PROFILES_FILES_FOR_VULKAN_HEADER_DOC} --output-doc ${PROJECT_SOURCE_DIR}/PROFILES.md - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} --input-filenames ${PROFILES_FILES_FOR_API_LIBRARY} --output-doc ${PROJECT_SOURCE_DIR}/PROFILES_ALL.md - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/Android @@ -208,7 +220,8 @@ set_target_properties(VpGenerate-Markdown PROPERTIES FOLDER "Profiles generator" add_dependencies(VpGenerate-Markdown VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesDesktopMax) add_custom_target(VpTestIntersect ALL - COMMAND Python3::Interpreter ${MERGE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_intersect --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_intersect.json @@ -221,7 +234,8 @@ set_target_properties(VpTestIntersect PROPERTIES FOLDER "Profiles generator/Test add_dependencies(VpTestIntersect VpProfilesProcessor) add_custom_target(VpTestUnion ALL - COMMAND Python3::Interpreter ${MERGE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_union --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_union.json @@ -234,7 +248,8 @@ set_target_properties(VpTestUnion PROPERTIES FOLDER "Profiles generator/Tests") add_dependencies(VpTestUnion VpProfilesProcessor) add_custom_target(VpTestGeneratedName ALL - COMMAND Python3::Interpreter ${MERGE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_intersect --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_generated_name.json @@ -245,7 +260,8 @@ set_target_properties(VpTestGeneratedName PROPERTIES FOLDER "Profiles generator/ add_dependencies(VpTestGeneratedName VpProfilesProcessor) add_custom_target(VpTestHostImageCopy ALL - COMMAND Python3::Interpreter ${MERGE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_host_image_copy --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_host_image_copy.json From 147292c94b61e5b0a463dd681dada5461dbcea51 Mon Sep 17 00:00:00 2001 From: Bob Ellison Date: Mon, 20 Jul 2026 13:46:28 -0600 Subject: [PATCH 07/17] add pyparsing module needed for codegen --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index eb191857..c9e4f3ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ # Python modules needed for project builds jsonschema == 4.* pyinstaller == 6.* +pyparsing == 3.* From 96d15afa0ee1a2450150d3978d28ed4f45187637 Mon Sep 17 00:00:00 2001 From: Bob Ellison Date: Mon, 20 Jul 2026 13:55:33 -0600 Subject: [PATCH 08/17] adding vulkan-object module --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index c9e4f3ad..a0fa7fce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ jsonschema == 4.* pyinstaller == 6.* pyparsing == 3.* +vulkan-object == 1.* From 5f4b80b7c2cd0429da9327a711eccad7d881d714 Mon Sep 17 00:00:00 2001 From: Bob Ellison Date: Mon, 20 Jul 2026 17:16:06 -0600 Subject: [PATCH 09/17] fix VpLibrary_test_generated_library The Python script needs to access the Vulkan-Headers version of vulkan_object.py. It doesn't need the vulkan_object module. --- library/test/CMakeLists.txt | 3 ++- requirements.txt | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/test/CMakeLists.txt b/library/test/CMakeLists.txt index c8b2fb28..583c8189 100644 --- a/library/test/CMakeLists.txt +++ b/library/test/CMakeLists.txt @@ -25,7 +25,8 @@ else() endif() add_custom_target(VpLibrary_test_generated_library - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${PROJECT_SOURCE_DIR}/library/test/profiles diff --git a/requirements.txt b/requirements.txt index a0fa7fce..c9e4f3ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,3 @@ jsonschema == 4.* pyinstaller == 6.* pyparsing == 3.* -vulkan-object == 1.* From 159be97dead03bc76a26284d3793da06dd6ecf70 Mon Sep 17 00:00:00 2001 From: Bob Ellison Date: Mon, 20 Jul 2026 17:49:11 -0600 Subject: [PATCH 10/17] Subdirectory builds also have to use the python virtual environment Or they end up complaining about missing "pyparsing" modules We also need a python_venv dependency so a subdirectory can reference it --- CMakeLists.txt | 6 ++++- layer/CMakeLists.txt | 8 +++--- library/test/CMakeLists.txt | 5 ++-- profiles/CMakeLists.txt | 52 ++++++++++++++++++------------------ scripts/tests/CMakeLists.txt | 2 +- 5 files changed, 40 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 433ff1ec..8847c75e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter) # Some Python-based build operations (e.g. turning a Python script into a standalone # executable) require custom Python modules. Here we'll create a custom # Python virtual environment with the modules we need. -set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/venv") +set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/python-venv") if(WIN32) set(VENV_PYTHON_EXECUTABLE "${VENV_DIR}/Scripts/python.exe") else() @@ -69,6 +69,10 @@ add_custom_command( COMMENT "Creating Python virtual environment and installing dependencies..." VERBATIM ) + +# This target works in subdirectories as a dependency. +add_custom_target(python_venv DEPENDS "${VENV_STAMP}") + # ============================================================================== find_package(VulkanHeaders REQUIRED CONFIG QUIET) diff --git a/layer/CMakeLists.txt b/layer/CMakeLists.txt index 20199f90..11b162b8 100644 --- a/layer/CMakeLists.txt +++ b/layer/CMakeLists.txt @@ -169,7 +169,7 @@ endif() add_custom_target(VpLayer_generate ALL COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${LAYER_PYTHON_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${LAYER_PYTHON_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --out-layer ${CMAKE_SOURCE_DIR}/layer/profiles_generated.cpp @@ -177,14 +177,15 @@ add_custom_target(VpLayer_generate ALL SOURCES ${LAYER_PYTHON_SCRIPT} DEPENDS ${LAYER_PYTHON_SCRIPT} ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml - ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/video.xml) + ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/video.xml + python_venv) set_target_properties(VpLayer_generate PROPERTIES FOLDER "Profiles layer") add_dependencies(ProfilesLayer VpLayer_generate) source_group("Python Files" FILES ${TESTS_PYTHON_SCRIPT}) add_custom_target(VpLayer_generate_tests ALL COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${TESTS_PYTHON_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${TESTS_PYTHON_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --out-profile ${CMAKE_SOURCE_DIR}/profiles/test/data/VP_LUNARG_test_api_generated.json @@ -194,6 +195,7 @@ add_custom_target(VpLayer_generate_tests ALL DEPENDS ${TESTS_PYTHON_SCRIPT} ProfilesLayer VpLayer_generate + python_venv ) set_target_properties(VpLayer_generate_tests PROPERTIES FOLDER "Profiles layer") diff --git a/library/test/CMakeLists.txt b/library/test/CMakeLists.txt index 583c8189..884052e6 100644 --- a/library/test/CMakeLists.txt +++ b/library/test/CMakeLists.txt @@ -26,7 +26,7 @@ endif() add_custom_target(VpLibrary_test_generated_library COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${PROJECT_SOURCE_DIR}/library/test/profiles @@ -36,7 +36,8 @@ add_custom_target(VpLibrary_test_generated_library VERBATIM SOURCES ${SOLUTION_SCRIPT} ${CMAKE_CURRENT_LIST_DIR}/profiles DEPENDS ${SOLUTION_SCRIPT} ${CMAKE_CURRENT_LIST_DIR}/profiles - ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml) + ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml + python_venv) set_target_properties(VpLibrary_test_generated_library PROPERTIES FOLDER "Profiles API library") add_dependencies(VpLibrary_test_generated_library VpGenerate-Libraries) diff --git a/profiles/CMakeLists.txt b/profiles/CMakeLists.txt index 15409d66..8ff0b89c 100644 --- a/profiles/CMakeLists.txt +++ b/profiles/CMakeLists.txt @@ -53,14 +53,14 @@ set(PROFILES_FILES_FOR_ANDROID_DOC add_custom_target(VpGenerate-ProfilesSchema COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/schema COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --output-schema ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} --validate VERBATIM SOURCES ${SOLUTION_SCRIPT} - DEPENDS ${SOLUTION_SCRIPT}) + DEPENDS ${SOLUTION_SCRIPT} python_venv) set_target_properties(VpGenerate-ProfilesSchema PROPERTIES FOLDER "Profiles schema") add_dependencies(VpGenerate-ProfilesSchema VpProfilesProcessor) @@ -75,7 +75,7 @@ set(PROFILE_DESKTOP_MAX_2026_API_VERSION "1.4.353") # Generate profiles files add_custom_target(VpGenerate-ProfilesDesktopMax ALL COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${MERGE_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_max_2024 --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_desktop_max_2024.json @@ -86,7 +86,7 @@ add_custom_target(VpGenerate-ProfilesDesktopMax ALL --profile-stage BETA --profile-api-version ${PROFILE_DESKTOP_MAX_2024_API_VERSION} COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${MERGE_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_max_2026 --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_desktop_max_2026.json @@ -98,13 +98,13 @@ add_custom_target(VpGenerate-ProfilesDesktopMax ALL --profile-api-version ${PROFILE_DESKTOP_MAX_2026_API_VERSION} VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} - DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) + DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES} python_venv) set_target_properties(VpGenerate-ProfilesDesktopMax PROPERTIES FOLDER "Profiles generator") add_dependencies(VpGenerate-ProfilesDesktopMax VpGenerate-ProfilesSchema) add_custom_target(VpGenerate-ProfilesDesktopBaseline ALL COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${MERGE_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --config ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline_config.json --output-path ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline.json @@ -113,7 +113,7 @@ add_custom_target(VpGenerate-ProfilesDesktopBaseline ALL ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_desktop_baseline.json ${CMAKE_CURRENT_LIST_DIR} VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} - DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) + DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES} python_venv) set_target_properties(VpGenerate-ProfilesDesktopBaseline PROPERTIES FOLDER "Profiles generator") add_dependencies(VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesSchema) @@ -128,7 +128,7 @@ add_custom_target(VpGenerate-Libraries # ${CMAKE_CURRENT_LIST_DIR}/Khronos/VP_KHR_roadmap.json ${CMAKE_CURRENT_LIST_DIR}/LunarG/VP_LUNARG_minimum_requirements.json ${CMAKE_CURRENT_LIST_DIR} COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -136,7 +136,7 @@ add_custom_target(VpGenerate-Libraries --output-schema ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} --validate COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -146,7 +146,7 @@ add_custom_target(VpGenerate-Libraries --output-library-filename "vulkan_profiles" --config release COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -156,7 +156,7 @@ add_custom_target(VpGenerate-Libraries --output-library-filename "vulkan_profiles" --config debug COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/Android @@ -166,7 +166,7 @@ add_custom_target(VpGenerate-Libraries --output-library-filename "generated_vulkan_profiles_android" --config release COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -176,7 +176,7 @@ add_custom_target(VpGenerate-Libraries --output-library-filename "generated_vulkan_profiles" --config release COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} @@ -187,27 +187,27 @@ add_custom_target(VpGenerate-Libraries --config debug VERBATIM SOURCES ${SOLUTION_SCRIPT} ${PROFILES_FILES} - DEPENDS ${SOLUTION_SCRIPT} ${PROFILES_FILES}) + DEPENDS ${SOLUTION_SCRIPT} ${PROFILES_FILES} python_venv) set_target_properties(VpGenerate-Libraries PROPERTIES FOLDER "Profiles generator") add_dependencies(VpGenerate-Libraries VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesDesktopMax) add_custom_target(VpGenerate-Markdown COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} --input-filenames ${PROFILES_FILES_FOR_VULKAN_HEADER_DOC} --output-doc ${PROJECT_SOURCE_DIR}/PROFILES.md COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR} --input-filenames ${PROFILES_FILES_FOR_API_LIBRARY} --output-doc ${PROJECT_SOURCE_DIR}/PROFILES_ALL.md COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${SOLUTION_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} --api ${API_TYPE} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/Android @@ -215,13 +215,13 @@ add_custom_target(VpGenerate-Markdown --output-doc ${PROJECT_SOURCE_DIR}/PROFILES_ANDROID.md VERBATIM SOURCES ${SOLUTION_SCRIPT} ${PROFILES_FILES} - DEPENDS ${SOLUTION_SCRIPT} ${PROFILES_FILES}) + DEPENDS ${SOLUTION_SCRIPT} ${PROFILES_FILES} python_venv) set_target_properties(VpGenerate-Markdown PROPERTIES FOLDER "Profiles generator") add_dependencies(VpGenerate-Markdown VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesDesktopMax) add_custom_target(VpTestIntersect ALL COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${MERGE_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_intersect --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_intersect.json @@ -229,13 +229,13 @@ add_custom_target(VpTestIntersect ALL --mode intersection VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} - DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) + DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES} python_venv) set_target_properties(VpTestIntersect PROPERTIES FOLDER "Profiles generator/Tests") add_dependencies(VpTestIntersect VpProfilesProcessor) add_custom_target(VpTestUnion ALL COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${MERGE_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_union --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_union.json @@ -243,31 +243,31 @@ add_custom_target(VpTestUnion ALL --mode union VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} - DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) + DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES} python_venv) set_target_properties(VpTestUnion PROPERTIES FOLDER "Profiles generator/Tests") add_dependencies(VpTestUnion VpProfilesProcessor) add_custom_target(VpTestGeneratedName ALL COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${MERGE_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_combine_intersect --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_generated_name.json VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} - DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) + DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES} python_venv) set_target_properties(VpTestGeneratedName PROPERTIES FOLDER "Profiles generator/Tests") add_dependencies(VpTestGeneratedName VpProfilesProcessor) add_custom_target(VpTestHostImageCopy ALL COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${Python3_EXECUTABLE} ${MERGE_SCRIPT} + ${VENV_PYTHON_EXECUTABLE} ${MERGE_SCRIPT} --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml --input ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_host_image_copy --output-path ${CMAKE_CURRENT_LIST_DIR}/test/data/VP_LUNARG_test_host_image_copy.json VERBATIM SOURCES ${MERGE_SCRIPT} ${PROFILES_FILES} - DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES}) + DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES} python_venv) set_target_properties(VpTestHostImageCopy PROPERTIES FOLDER "Profiles generator/Tests") add_dependencies(VpTestHostImageCopy VpProfilesProcessor) diff --git a/scripts/tests/CMakeLists.txt b/scripts/tests/CMakeLists.txt index daff041c..f0e23b3b 100644 --- a/scripts/tests/CMakeLists.txt +++ b/scripts/tests/CMakeLists.txt @@ -22,7 +22,7 @@ function(add_vulkan_python_test TARGET_NAME SCRIPT_FILENAME) # Register the script as a CTest test case add_test(NAME ${TARGET_NAME} - COMMAND ${Python3_EXECUTABLE} "${FULL_SCRIPT_PATH}" --registry "${REGISTRY_PATH}/vk.xml" + COMMAND ${VENV_PYTHON_EXECUTABLE} "${FULL_SCRIPT_PATH}" --registry "${REGISTRY_PATH}/vk.xml" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) From 68cd0de1b3bd8368c9c29e09b3227676387e81f2 Mon Sep 17 00:00:00 2001 From: Bob Ellison Date: Mon, 20 Jul 2026 17:59:45 -0600 Subject: [PATCH 11/17] fix python_venv dependency --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8847c75e..b92171cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,7 +131,7 @@ function(add_pyinstaller_target TARGET_NAME) add_custom_command( OUTPUT "${FINAL_EXECUTABLE}" COMMAND "${VENV_PYTHON_EXECUTABLE}" -m PyInstaller --onefile --name "${ARG_OUTPUT_NAME}" --distpath "${ARG_OUTPUT_DIR}" "${ARG_SCRIPT}" - DEPENDS ${ARG_DEPENDS} "${VENV_STAMP}" + DEPENDS ${ARG_DEPENDS} python_venv WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMENT "Packaging ${TARGET_NAME} into a standalone executable..." VERBATIM From 2d92503302ceb0e711a17ee763e61614bb902f45 Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 20 Jul 2026 18:07:47 +0200 Subject: [PATCH 12/17] cmake: Fix registry python modules localisation --- profiles/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiles/CMakeLists.txt b/profiles/CMakeLists.txt index 8ff0b89c..97ea6190 100644 --- a/profiles/CMakeLists.txt +++ b/profiles/CMakeLists.txt @@ -46,7 +46,7 @@ set(PROFILES_FILES_FOR_VULKAN_HEADER_DOC ) set(PROFILES_FILES_FOR_ANDROID_DOC - "VP_ANDROID_15_requirements.json,VP_ANDROID_16_requirements.json,VP_ANDROID_17_requirements.json,VP_ANDROID_vulkan_profile_2021.json,VP_ANDROID_vulkan_profile_2022.json,VP_ANDROID_vulkan_profile_2025.json" + "VP_ANDROID_15_requirements.json,VP_ANDROID_16_requirements.json,VP_ANDROID_17_requirements.json,VP_ANDROID_vulkan_profile_2021.json,VP_ANDROID_vulkan_profile_2022.json" ) # Generate profiles schema From c2be7cd8e981d64d2cec7950f2d173e1a1906268 Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 21 Jul 2026 16:57:49 +0200 Subject: [PATCH 13/17] profiles: Add Vulkan Object utils tests --- profiles/CMakeLists.txt | 3 +-- scripts/source/vulkan_object_utils.py | 18 +++++++++--------- scripts/tests/CMakeLists.txt | 6 +++--- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/profiles/CMakeLists.txt b/profiles/CMakeLists.txt index 97ea6190..542bfa98 100644 --- a/profiles/CMakeLists.txt +++ b/profiles/CMakeLists.txt @@ -30,7 +30,6 @@ set(PROFILES_FILES_FOR_INSTALL LunarG/VP_LUNARG_desktop_baseline.json Android/VP_ANDROID_vulkan_profile_2021.json Android/VP_ANDROID_vulkan_profile_2022.json - Android/VP_ANDROID_vulkan_profile_2025.json ) set(PROFILES_FILES_FOR_API_LIBRARY @@ -46,7 +45,7 @@ set(PROFILES_FILES_FOR_VULKAN_HEADER_DOC ) set(PROFILES_FILES_FOR_ANDROID_DOC - "VP_ANDROID_15_requirements.json,VP_ANDROID_16_requirements.json,VP_ANDROID_17_requirements.json,VP_ANDROID_vulkan_profile_2021.json,VP_ANDROID_vulkan_profile_2022.json" + "VP_ANDROID_15_requirements.json,VP_ANDROID_16_requirements.json,VP_ANDROID_vulkan_profile_2021.json,VP_ANDROID_vulkan_profile_2022.json" ) # Generate profiles schema diff --git a/scripts/source/vulkan_object_utils.py b/scripts/source/vulkan_object_utils.py index 76394932..2d3527bc 100644 --- a/scripts/source/vulkan_object_utils.py +++ b/scripts/source/vulkan_object_utils.py @@ -156,16 +156,16 @@ def gatherCapabilityAliases(vk: VulkanObject, alias_id: CapabilityAlias) -> list # Follow the chain if the canonical member itself points to another structure feature if canonical_struct in vk.structs: for member in vk.structs[canonical_struct].members: - if member.name == alias_id.member and isinstance(member.alias, StructCapabilityAlias): - target_struct_obj = getStructByName(vk.structs, member.alias.struct) - canonical_key = (target_struct_obj.name if target_struct_obj else member.alias.struct, member.alias.member) + if member.name == alias_id.member and isinstance(member.capabilityAlias, StructCapabilityAlias): + target_struct_obj = getStructByName(vk.structs, member.capabilityAlias.struct) + canonical_key = (target_struct_obj.name if target_struct_obj else member.capabilityAlias.struct, member.capabilityAlias.member) break elif isinstance(alias_id, ExtensionCapabilityAlias): # Extensions lack structural layout, so locate their defining struct member for struct_name, struct_obj in vk.structs.items(): for member in struct_obj.members: - if isinstance(member.alias, ExtensionCapabilityAlias) and member.alias.name == alias_id.name: + if isinstance(member.capabilityAlias, ExtensionCapabilityAlias) and member.capabilityAlias.name == alias_id.name: canonical_key = (struct_name, member.name) break if canonical_key: @@ -179,9 +179,9 @@ def gatherCapabilityAliases(vk: VulkanObject, alias_id: CapabilityAlias) -> list for struct_name, struct_obj in vk.structs.items(): for member in struct_obj.members: # Determine where the current member resolves to - if isinstance(member.alias, StructCapabilityAlias): - target_struct_obj = getStructByName(vk.structs, member.alias.struct) - current_key = (target_struct_obj.name if target_struct_obj else member.alias.struct, member.alias.member) + if isinstance(member.capabilityAlias, StructCapabilityAlias): + target_struct_obj = getStructByName(vk.structs, member.capabilityAlias.struct) + current_key = (target_struct_obj.name if target_struct_obj else member.capabilityAlias.struct, member.capabilityAlias.member) else: current_key = (struct_name, member.name) @@ -191,8 +191,8 @@ def gatherCapabilityAliases(vk: VulkanObject, alias_id: CapabilityAlias) -> list for alias_struct in struct_obj.aliases: aliases.append(StructCapabilityAlias(alias_struct, member.name)) - if isinstance(member.alias, ExtensionCapabilityAlias) and member.alias.name in vk.extensions: - aliases.append(ExtensionCapabilityAlias(member.alias.name)) + if isinstance(member.capabilityAlias, ExtensionCapabilityAlias) and member.capabilityAlias.name in vk.extensions: + aliases.append(ExtensionCapabilityAlias(member.capabilityAlias.name)) # Step 3: Streamlined filtering to remove the original query item return [item for item in aliases if item != alias_id] diff --git a/scripts/tests/CMakeLists.txt b/scripts/tests/CMakeLists.txt index f0e23b3b..0c151bd6 100644 --- a/scripts/tests/CMakeLists.txt +++ b/scripts/tests/CMakeLists.txt @@ -33,9 +33,9 @@ function(add_vulkan_python_test TARGET_NAME SCRIPT_FILENAME) endfunction() if(NOT APPLE) # macOS doesn't handle dynamically loading Python jsonschema' - add_vulkan_python_test(VpProfilesProcessor_TestVulkanProfilesUtil test_util.py) - #add_vulkan_python_test(VpProfilesProcessor_TestVulkanObjectEmpty test_init.py) requires Header 357 - #add_vulkan_python_test(VpProfilesProcessor_TestVulkanObjectUtils test_vulkan_object_utils.py) requires Header 357 + add_vulkan_python_test(VpProfilesProcessor_TestVulkanProfilesUtil test_util.py) + add_vulkan_python_test(VpProfilesProcessor_TestVulkanObjectEmpty test_init.py) + add_vulkan_python_test(VpProfilesProcessor_TestVulkanObjectUtils test_vulkan_object_utils.py) add_vulkan_python_test(VpProfilesProcessor_TestVulkanObjectDepends test_vulkan_object_expression_parsing.py) add_vulkan_python_test(VpProfilesProcessor_TestVulkanProfilesJson test_vulkan_profiles.py) endif() From aca8132b4263087e492592425f6f82dd93b8a54e Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 22 Jul 2026 17:00:34 +0200 Subject: [PATCH 14/17] schema: Use vkprofiles.exe to generate the schema --- CMakeLists.txt | 21 ++++++++++-- profiles/CMakeLists.txt | 32 +++++++++++++----- scripts/gen_profiles_solution.py | 2 +- scripts/profiles.py | 39 ++++++++++++++++++---- scripts/source/generate_profiles_schema.py | 1 - scripts/source/profiles_parsing.py | 22 ++++++------ scripts/source/vulkan_object_utils.py | 4 +-- scripts/tests/test_init.py | 2 +- scripts/tests/test_vulkan_object_utils.py | 12 +++---- 9 files changed, 97 insertions(+), 38 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b92171cc..f0bea577 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,7 +104,7 @@ function(add_pyinstaller_target TARGET_NAME) # Define the arguments the function accepts set(options) set(oneValueArgs SCRIPT OUTPUT_DIR FOLDER OUTPUT_NAME) - set(multiValueArgs DEPENDS) + set(multiValueArgs DEPENDS PATHS) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT ARG_SCRIPT) @@ -127,10 +127,15 @@ function(add_pyinstaller_target TARGET_NAME) endif() set(FINAL_EXECUTABLE "${ARG_OUTPUT_DIR}/${EXE_NAME}") + set(PYINSTALLER_PATH_FLAGS) + foreach(p ${ARG_PATHS}) + list(APPEND PYINSTALLER_PATH_FLAGS "--paths" "${p}") + endforeach() + # Generate the executable via PyInstaller using the VENV python executable add_custom_command( OUTPUT "${FINAL_EXECUTABLE}" - COMMAND "${VENV_PYTHON_EXECUTABLE}" -m PyInstaller --onefile --name "${ARG_OUTPUT_NAME}" --distpath "${ARG_OUTPUT_DIR}" "${ARG_SCRIPT}" + COMMAND "${VENV_PYTHON_EXECUTABLE}" -m PyInstaller --onefile --clean ${PYINSTALLER_PATH_FLAGS} --name "${ARG_OUTPUT_NAME}" --distpath "${ARG_OUTPUT_DIR}" "${ARG_SCRIPT}" DEPENDS ${ARG_DEPENDS} python_venv WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMENT "Packaging ${TARGET_NAME} into a standalone executable..." @@ -161,10 +166,22 @@ set(PYTHON_DEPENDENCIES "${CMAKE_CURRENT_SOURCE_DIR}/scripts/source/vulkan_object_version.py" ) +if(VULKAN_HEADERS_INSTALL_DIR) + set(VULKAN_REGISTRY_DIR "${VULKAN_HEADERS_INSTALL_DIR}/registry") + set(VULKAN_REGISTRY_SHARE_DIR "${VULKAN_HEADERS_INSTALL_DIR}/share/vulkan/registry") +elseif(TARGET Vulkan::Registry) + get_target_property(VULKAN_REGISTRY_DIR Vulkan::Registry INTERFACE_INCLUDE_DIRECTORIES) + set(VULKAN_REGISTRY_SHARE_DIR "${VULKAN_REGISTRY_DIR}") +endif() + add_pyinstaller_target(VpProfilesProcessor OUTPUT_NAME "vkprofiles" SCRIPT "${PROFILES_SCRIPT}" OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts" + PATHS + "${VULKAN_REGISTRY_DIR}" + "${VULKAN_REGISTRY_SHARE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/source" DEPENDS ${PYTHON_DEPENDENCIES} FOLDER "Profiles generator" ) diff --git a/profiles/CMakeLists.txt b/profiles/CMakeLists.txt index 542bfa98..65e821b5 100644 --- a/profiles/CMakeLists.txt +++ b/profiles/CMakeLists.txt @@ -49,19 +49,33 @@ set(PROFILES_FILES_FOR_ANDROID_DOC ) # Generate profiles schema +if(WIN32) + set(VKPROFILES_EXE "${PROJECT_SOURCE_DIR}/scripts/vkprofiles.exe") +else() + set(VKPROFILES_EXE "${PROJECT_SOURCE_DIR}/scripts/vkprofiles") +endif() + add_custom_target(VpGenerate-ProfilesSchema COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/schema - COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" - ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} - --api ${API_TYPE} + COMMAND ${VKPROFILES_EXE} + schema --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml - --output-schema ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} - --validate - VERBATIM - SOURCES ${SOLUTION_SCRIPT} - DEPENDS ${SOLUTION_SCRIPT} python_venv) + --output ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} + --api ${API_TYPE} + VERBATIM) + +# COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" +# ${VENV_PYTHON_EXECUTABLE} ${SOLUTION_SCRIPT} +# --api ${API_TYPE} +# --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml +# --output-schema ${PROJECT_SOURCE_DIR}/schema/${PROFILES_SCHEMA_FILENAME} +# --validate +# VERBATIM +# SOURCES ${SOLUTION_SCRIPT} +# DEPENDS ${SOLUTION_SCRIPT} python_venv) + set_target_properties(VpGenerate-ProfilesSchema PROPERTIES FOLDER "Profiles schema") -add_dependencies(VpGenerate-ProfilesSchema VpProfilesProcessor) +add_dependencies(VpGenerate-ProfilesSchema VpProfilesProcessor python_venv) set(PROFILE_DESKTOP_MAX_2024_LABEL "LunarG Vulkan Desktop Max 2024 profile") set(PROFILE_DESKTOP_MAX_2024_DESC "A profile generated by the intersection of a collection of GPUInfo.org device reports to support the latest AMD, Intel and NVIDIA GPUs and drivers.") diff --git a/scripts/gen_profiles_solution.py b/scripts/gen_profiles_solution.py index 647f482c..08ec53dc 100644 --- a/scripts/gen_profiles_solution.py +++ b/scripts/gen_profiles_solution.py @@ -5797,7 +5797,7 @@ def gen_videoFormats(self, videoProfileName, videoCodec): if args.registry != None: registry = VulkanRegistry(args.registry, args.api) - vk: VulkanObject = initVulkanObject(args.registry, True) + vk: VulkanObject = initVulkanObject(args.api, args.registry, True) if args.output_schema != None or args.validate: generator = VulkanProfilesSchemaGenerator(registry) diff --git a/scripts/profiles.py b/scripts/profiles.py index 946b4919..e5ee2872 100644 --- a/scripts/profiles.py +++ b/scripts/profiles.py @@ -21,7 +21,7 @@ import logging from datetime import datetime -from enum import StrEnum +from enum import Enum from pathlib import Path import argparse import sys @@ -29,10 +29,12 @@ from source.vulkan_object_utils import initVulkanObject, VK_VERSION, gatherDependentExtensions from source.profiles_parsing import load_profiles_jsons from source.profiles_parsing import save_profiles_jsons -from source.profiles_parsing import validate_profiles_json +from source.profiles_parsing import validate_profiles_json, validate_profiles_jsons_data from source.profiles_parsing import OutputFormatType +from source.generate_profiles_schema import VulkanProfilesSchemaGenerator2 +from source.log import Log -class ConvertMode(StrEnum): +class ConvertMode(str, Enum): STRIP_DUPLICATION = 'strip-duplication' PULL_DEPENDENCES = 'pull-dependences' @@ -123,7 +125,7 @@ def strip_profiles_files_capabilities_duplication(json_files_dict): def main_convert(args): - vk = initVulkanObject(args.registry or None) + vk = initVulkanObject(args.api, args.registry or None) for version in vk.versions.values(): logging.debug(version.name) @@ -151,8 +153,25 @@ def main_convert(args): def main_validate(args): - validate_profiles_json(Path(args.input), Path(args.schema)) + if args.schema is None: + if args.registry is None: + Log.e("`--schema` or `--registry` are required to validate profile files") + else: + vk: VulkanObject = initVulkanObject(args.api, args.registry, True) + generator2 = VulkanProfilesSchemaGenerator2(vk) + validate_profiles_jsons_data(Path(args.input), generator2.schema) + else: + validate_profiles_json(Path(args.input), Path(args.schema)) + +def main_schema(args): + vk: VulkanObject + if args.registry is None: + vk = initVulkanObject(args.api) + else: + vk = initVulkanObject(args.api, args.registry, True) + generator = VulkanProfilesSchemaGenerator2(vk) + generator.generate(args.output) def main(argv): logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s') @@ -170,15 +189,23 @@ def main(argv): convert_parser.add_argument('--mode', nargs='*',action='store', choices=list(ConvertMode), default=list(ConvertMode), help='List of conversion capabilities') validate_parser = subparsers.add_parser('validate', help='Validate a profile file against a profile schema.') - validate_parser.add_argument('--schema', '-s', action='store', required=True, help='Use a specific Vulkan registry file (vk.xml).') + validate_parser.add_argument('--registry', '-r', action='store', help='Use a specific Vulkan registry file (vk.xml).') + validate_parser.add_argument('--schema', '-s', action='store', help='Use a profile schema (profiles-*.json). By default, generate a profile schema vk.xml.') validate_parser.add_argument('--input', '-i', action='store', required=True, help='Path to the input profiles files.') + schema_parser = subparsers.add_parser('schema', help='Generate a profile json schema file.') + schema_parser.add_argument('--registry', '-r', action='store', help='Use a specific Vulkan registry file (vk.xml).') + schema_parser.add_argument('--output', '-o', action='store', required=True, help='Path to the output profile schema file.') + schema_parser.add_argument('--api', action='store', default='vulkan', choices=['vulkan'], help="Target API") + args = parser.parse_args(argv) if args.command == 'convert': main_convert(args) elif args.command == 'validate': main_validate(args) + elif args.command == 'schema': + main_schema(args) else: parser.print_help() diff --git a/scripts/source/generate_profiles_schema.py b/scripts/source/generate_profiles_schema.py index d11b9618..1116eed8 100644 --- a/scripts/source/generate_profiles_schema.py +++ b/scripts/source/generate_profiles_schema.py @@ -40,7 +40,6 @@ def __init__(self, vk): # Call the global discovery helper passing the VulkanObject parameter self.valid_dynamic_structs = gatherDynamicStructs(vk) - self.schema = self.gen_schema() def validate(self): diff --git a/scripts/source/profiles_parsing.py b/scripts/source/profiles_parsing.py index 05770bab..ad0f3510 100644 --- a/scripts/source/profiles_parsing.py +++ b/scripts/source/profiles_parsing.py @@ -43,6 +43,7 @@ def _validate_profiles_json_data(json_data, schema_data) -> bool: logging.warning("`jsonschema` module is not installed, schema validation skip") return False + def validate_profiles_json(json_data_path: Path, json_schema_path: Path) -> bool: schema_data = load_schema_json(json_schema_path) if schema_data is None: @@ -59,12 +60,7 @@ def validate_profiles_json(json_data_path: Path, json_schema_path: Path) -> bool return _validate_profiles_json_data(json_data, schema_data) -def validate_profiles_jsons(json_data_dir: Path, json_schema_path: Path) -> int: - schema_data = load_schema_json(json_schema_path) - if schema_data is None: - logging.error(f"Invalid profile file: {json_schema_path}") - return 0 - +def validate_profiles_jsons_data(json_data_dir: Path, json_schema_data) -> int: profiles_files_paths = [] for pos_json in os.listdir(json_data_dir): if pos_json.endswith('.json'): @@ -78,12 +74,21 @@ def validate_profiles_jsons(json_data_dir: Path, json_schema_path: Path) -> int: logging.debug(f"Invalid profile file: {profiles_files_paths[i]}") continue - if _validate_profiles_json_data(json_data, schema_data): + if _validate_profiles_json_data(json_data, json_schema_data): result += 1 return result +def validate_profiles_jsons(json_data_dir: Path, json_schema_path: Path) -> int: + schema_data = load_schema_json(json_schema_path) + if schema_data is None: + logging.error(f"Invalid profile file: {json_schema_path}") + return 0 + + return validate_profiles_jsons_data(json_data_dir, schema_data) + + def load_schema_json(input_file): with open(input_file, "r", encoding="utf-8") as file: schema_file_data = json.load(file) @@ -135,7 +140,6 @@ def load_profiles_jsons(input_dir): return json_files_dict - class OutputFormatType(Enum): PRETTY = 'pretty' FLATTEN = 'flatten' @@ -169,5 +173,3 @@ def save_profiles_jsons(json_files_dict, output_dir, format: OutputFormatType): file.write(flat_json) else: json.dump(value, file, indent=4) - - diff --git a/scripts/source/vulkan_object_utils.py b/scripts/source/vulkan_object_utils.py index 2d3527bc..9fb455df 100644 --- a/scripts/source/vulkan_object_utils.py +++ b/scripts/source/vulkan_object_utils.py @@ -40,7 +40,7 @@ # Create the simplified, cached public function @functools.lru_cache(maxsize=1) -def initVulkanObject(alternative_xml: str = None, video: bool = False) -> VulkanObject: +def initVulkanObject(target_api: str = 'vulkan', alternative_xml: str = None, video: bool = False) -> VulkanObject: """ Parses the bundled Vulkan registry (vk.xml) and returns the populated VulkanObject. @@ -74,7 +74,7 @@ def generate(self): SetOutputDirectory(output_dir) SetOutputFileName("unused.txt") # TODO - Make a get_vulkan_sc_object() or pass this in as a parameter - SetTargetApiName('vulkan') + SetTargetApiName(target_api) SetMergedApiNames(None) xml_path = None diff --git a/scripts/tests/test_init.py b/scripts/tests/test_init.py index 6a547b4b..ff174878 100644 --- a/scripts/tests/test_init.py +++ b/scripts/tests/test_init.py @@ -35,7 +35,7 @@ class TestVulkanObjectInit(unittest.TestCase): registry_path = None def test_load_vulkan_object(self): - vk: VulkanObject = initVulkanObject(self.registry_path) + vk: VulkanObject = initVulkanObject('vulkan', self.registry_path) if __name__ == '__main__': parser = argparse.ArgumentParser() diff --git a/scripts/tests/test_vulkan_object_utils.py b/scripts/tests/test_vulkan_object_utils.py index 51b639be..447eff85 100644 --- a/scripts/tests/test_vulkan_object_utils.py +++ b/scripts/tests/test_vulkan_object_utils.py @@ -36,7 +36,7 @@ class TestVulkanObjectUtils(unittest.TestCase): registry_path = None # def testVulkanObjectVersion(self): - # vk: VulkanObject = initVulkanObject(self.registry_path) + # vk: VulkanObject = initVulkanObject('vulkan', self.registry_path) # VK_VERSION = buildVulkanVersionEnum(vk) @@ -48,7 +48,7 @@ class TestVulkanObjectUtils(unittest.TestCase): # Check we can get the list of feature aliases from any feature structure def testVulkanObjectUtilsStructFeatureAliasesAccess(self): - vk: VulkanObject = initVulkanObject(self.registry_path) + vk: VulkanObject = initVulkanObject('vulkan', self.registry_path) # Case 2: Building the list of aliases of an actual struct using the getAliases helper function that hide that not all structs are stored in vk.structs query_id2 = StructCapabilityAlias("VkPhysicalDeviceShaderSubgroupRotateFeatures", "shaderSubgroupRotate") @@ -122,7 +122,7 @@ def testVulkanObjectUtilsStructFeatureAliasesAccess(self): # Check we can get the list of property aliases from any property structure def testVulkanObjectUtilsStructPropertyAliasesAccess(self): - vk: VulkanObject = initVulkanObject(self.registry_path) + vk: VulkanObject = initVulkanObject('vulkan', self.registry_path) # Case 1: Building the list of aliases of an actual struct using the getCapabilityAliases helper function that hide that not all structs are stored in vk.structs query_id1 = StructCapabilityAlias("VkPhysicalDeviceLineRasterizationProperties", "lineSubPixelPrecisionBits") @@ -197,7 +197,7 @@ def testVulkanObjectUtilsStructPropertyAliasesAccess(self): assert member_C_aliases == [] def testFindExtensionVersion(self): - vk: VulkanObject = initVulkanObject(self.registry_path) + vk: VulkanObject = initVulkanObject('vulkan', self.registry_path) extension_version0 = findExtensionVersion(vk, "VK_KHR_dynamic_rendering") self.assertEqual(extension_version0, 1) @@ -211,7 +211,7 @@ def testFindExtensionVersion(self): def testGatherDependentExtensions(self): self.maxDiff = 1024 - vk: VulkanObject = initVulkanObject(self.registry_path) + vk: VulkanObject = initVulkanObject('vulkan', self.registry_path) extensions_data = { "VK_KHR_dynamic_rendering": 1, @@ -336,7 +336,7 @@ def testGatherDynamicStructs(self): Verifies that gatherDynamicStructs correctly builds an automated layout of valid dynamic array properties directly from the parsed VulkanObject. """ - vk: VulkanObject = initVulkanObject(self.registry_path) + vk: VulkanObject = initVulkanObject('vulkan', self.registry_path) # Programmatically discover all extensible dynamic array property containers dynamic_structs = gatherDynamicStructs(vk) From dfdb287d80d71374c9948c52d87bb08e950e4b66 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 22 Jul 2026 18:15:27 +0200 Subject: [PATCH 15/17] doc: Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 863e7ca5..a5a25996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ### Features: - Implement `VK_NO_PROTOTYPES` support #734 - Add `vkprofiles` executable which bundles the python scripts in a standalone executable + - Validate profiles JSON file with `validate` command + - Generate profiles schema file with `schema` command ### Improvements: - Improve profiles schema to support capabilities dynamic structures From 98dbeb829235d54f23fe5af2cc8fd302b8da745a Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 23 Jul 2026 17:10:17 +0200 Subject: [PATCH 16/17] build: Split main, add convert bits --- scripts/profiles.py | 159 +------------------------- scripts/source/main_convert.py | 196 ++++++++++++++++++++++++++++++++ scripts/source/main_schema.py | 31 +++++ scripts/source/main_validate.py | 38 +++++++ 4 files changed, 271 insertions(+), 153 deletions(-) create mode 100644 scripts/source/main_convert.py create mode 100644 scripts/source/main_schema.py create mode 100644 scripts/source/main_validate.py diff --git a/scripts/profiles.py b/scripts/profiles.py index e5ee2872..84ec8d6e 100644 --- a/scripts/profiles.py +++ b/scripts/profiles.py @@ -20,158 +20,13 @@ # - Christophe Riccio import logging -from datetime import datetime -from enum import Enum -from pathlib import Path + import argparse import sys -from vulkan_object import VulkanObject -from source.vulkan_object_utils import initVulkanObject, VK_VERSION, gatherDependentExtensions -from source.profiles_parsing import load_profiles_jsons -from source.profiles_parsing import save_profiles_jsons -from source.profiles_parsing import validate_profiles_json, validate_profiles_jsons_data -from source.profiles_parsing import OutputFormatType -from source.generate_profiles_schema import VulkanProfilesSchemaGenerator2 -from source.log import Log - -class ConvertMode(str, Enum): - STRIP_DUPLICATION = 'strip-duplication' - PULL_DEPENDENCES = 'pull-dependences' - -# A Profiles Json capabilities element containts block names. Collect all the names -# "capabilities": [ -# "MUST", -# ["multisampledToSingleSampled", "shaderStencilExport"], -# ["wideLinesEnabledConstrained", "wideLinesDisabledUnconstrained"] -# ] -def collect_block_names(json_capabilities): - block_names = [] - - for value in json_capabilities: - if isinstance(value, str): - block_names.append(value) - elif isinstance(value, list): - names = value - for value in names: - block_names.append(value) - - return block_names - -def pull_capabilities_block_dependencies(vk: VulkanObject, version: VK_VERSION, ignore_extension_versions: bool, json_profiles_capabilities_block): - if "extensions" not in json_profiles_capabilities_block: - return - - extensions = gatherDependentExtensions(vk, version, ignore_extension_versions, json_profiles_capabilities_block["extensions"]) - json_profiles_capabilities_block["extensions"] = extensions - - return - -# TODO: Add Vulkan version and other profiles? (Maybe not necessary) -def pull_profiles_file_dependencies(vk: VulkanObject, require_promoted_extensions: bool, ignore_extension_versions: bool, json_file_data): - profiles_data = json_file_data["profiles"] - json_profiles_capabilities = json_file_data["capabilities"] - - for key, value in profiles_data.items(): - version = VK_VERSION.NONE - if not require_promoted_extensions: - version = VK_VERSION.from_string(value["api-version"]) - - block_names = collect_block_names(value["capabilities"]) - - for block_name in block_names: - pull_capabilities_block_dependencies(vk, version, ignore_extension_versions, json_profiles_capabilities[block_name]) -def pull_profiles_files_dependencies(vk: VulkanObject, require_promoted_extensions: bool, ignore_extension_versions: bool, json_files_dict): - for key, value in json_files_dict.items(): - logging.debug(f"Fill capabilities dependencies for: {key}") - pull_profiles_file_dependencies(vk, require_promoted_extensions, ignore_extension_versions, value) - -def strip_capabilities_block_duplication(json_files_dict, json_profiles_capabilities_block, collected_extension_names: set[str]): - if "extensions" not in json_profiles_capabilities_block: - return - - stripped_extensions: dict[str, int] = {} - - for extension_name, extension_version in json_profiles_capabilities_block["extensions"].items(): - if extension_name in collected_extension_names: - continue # The extension was already listed, it's a duplicate. - collected_extension_names.add(extension_name) - stripped_extensions[extension_name] = extension_version - - json_profiles_capabilities_block["extensions"] = stripped_extensions - - -def strip_profiles_file_capabilities_duplication(json_files_dict, json_file_data): - profiles_data = json_file_data["profiles"] - json_profiles_capabilities = json_file_data["capabilities"] - - for key, value in profiles_data.items(): - collected_extension_names: set[str] = set() - - version = VK_VERSION.from_string(value["api-version"]) - - block_names = collect_block_names(value["capabilities"]) # Here, it collects all the block names but some blocks are OR - - for block_name in block_names: - strip_capabilities_block_duplication(json_files_dict, json_profiles_capabilities[block_name], collected_extension_names) - - return - - -def strip_profiles_files_capabilities_duplication(json_files_dict): - for key, value in json_files_dict.items(): - logging.debug(f"Strip duplicated capabilities for: {key}") - strip_profiles_file_capabilities_duplication(json_files_dict, value) - - -def main_convert(args): - vk = initVulkanObject(args.api, args.registry or None) - - for version in vk.versions.values(): - logging.debug(version.name) - - json_files_dict = load_profiles_jsons(Path(args.input)) - #save_profiles_jsons(json_files_dict, Path(args.format)) - - require_promoted_extensions = False - if args.require_promoted_extensions is not None: - require_promoted_extensions = True - - ignore_extension_versions = False - if args.ignore_extension_versions is not None: - ignore_extension_versions = True - - mode_enums = [ConvertMode(m) for m in args.mode] - - if ConvertMode.PULL_DEPENDENCES in mode_enums: - pull_profiles_files_dependencies(vk, require_promoted_extensions, ignore_extension_versions, json_files_dict) - - if ConvertMode.STRIP_DUPLICATION in mode_enums: - strip_profiles_files_capabilities_duplication(json_files_dict) - - save_profiles_jsons(json_files_dict, Path(args.output), OutputFormatType(args.format)) - - -def main_validate(args): - if args.schema is None: - if args.registry is None: - Log.e("`--schema` or `--registry` are required to validate profile files") - else: - vk: VulkanObject = initVulkanObject(args.api, args.registry, True) - generator2 = VulkanProfilesSchemaGenerator2(vk) - validate_profiles_jsons_data(Path(args.input), generator2.schema) - else: - validate_profiles_json(Path(args.input), Path(args.schema)) - - -def main_schema(args): - vk: VulkanObject - if args.registry is None: - vk = initVulkanObject(args.api) - else: - vk = initVulkanObject(args.api, args.registry, True) - generator = VulkanProfilesSchemaGenerator2(vk) - generator.generate(args.output) +from source.main_convert import main_convert, ConvertBits, OutputFormatType +from source.main_schema import main_schema +from source.main_validate import main_validate def main(argv): logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s') @@ -180,14 +35,12 @@ def main(argv): subparsers = parser.add_subparsers(dest='command', required=True) convert_parser = subparsers.add_parser('convert', help='Convert an implicit profile to an explicit profile by pulling Vulkan capabilities dependencies from vk.xml.') - convert_parser.add_argument('--require-promoted-extensions', action='store_true', help='Require all extensions promoted to a core version.') - convert_parser.add_argument('--ignore-extension-versions', action='store_true', help='Set all required extensions to version 1, ignoring extension versions.') convert_parser.add_argument('--registry', '-r', action='store', help='Use a specific Vulkan registry file (vk.xml).') convert_parser.add_argument('--input', '-i', action='store', required=True, help='Path to the input profiles files.') convert_parser.add_argument('--output', '-o', action='store', required=True, help='Path to the output profiles files.') convert_parser.add_argument('--format', action='store', choices=list(OutputFormatType), default=OutputFormatType.FLATTEN, help='Formatting style for the profiles files (default: flatten).') - convert_parser.add_argument('--mode', nargs='*',action='store', choices=list(ConvertMode), default=list(ConvertMode), help='List of conversion capabilities') - + convert_parser.add_argument('--mode', nargs='*',action='store', choices=list(ConvertBits), default=list(ConvertBits), help='List of conversion capabilities') + validate_parser = subparsers.add_parser('validate', help='Validate a profile file against a profile schema.') validate_parser.add_argument('--registry', '-r', action='store', help='Use a specific Vulkan registry file (vk.xml).') validate_parser.add_argument('--schema', '-s', action='store', help='Use a profile schema (profiles-*.json). By default, generate a profile schema vk.xml.') diff --git a/scripts/source/main_convert.py b/scripts/source/main_convert.py new file mode 100644 index 00000000..18ac7b69 --- /dev/null +++ b/scripts/source/main_convert.py @@ -0,0 +1,196 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2026-2026 Google, Inc. +# Copyright (C) 2026-2026 Valve Corporation +# Copyright (c) 2026-2026 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. +# +# Authors: +# - Christophe Riccio + +import logging + +from pathlib import Path +from enum import Enum + +from source.vulkan_object_utils import VulkanObject, initVulkanObject, VK_VERSION, gatherDependentExtensions, gatherDependentCapabilityAliases +from source.profiles_parsing import load_profiles_jsons +from source.profiles_parsing import save_profiles_jsons +from source.profiles_parsing import OutputFormatType + + +class ConvertBits(str, Enum): + STRIP_DUPLICATION = 'strip-duplication' + PULL_DEPENDENCES = 'pull-dependences' + PULL_ALIASES = 'pull-aliases' + PULL_PROMOTED_EXTENSIONS = 'pull-promoted-extensions' # Require all extensions promoted to a core version. + IGNORE_EXTENSION_VERSIONS = 'ignore-extension-versions' # Set all required extensions to version 1, ignoring extension versions. + + +# A Profiles Json capabilities element containts block names. Collect all the names +# "capabilities": [ +# "MUST", +# ["multisampledToSingleSampled", "shaderStencilExport"], +# ["wideLinesEnabledConstrained", "wideLinesDisabledUnconstrained"] +# ] +def collect_block_names(json_capabilities): + block_names = [] + + for value in json_capabilities: + if isinstance(value, str): + block_names.append(value) + elif isinstance(value, list): + names = value + for value in names: + block_names.append(value) + + return block_names + + +def pull_capabilities_block_dependencies(vk: VulkanObject, version: VK_VERSION, ignore_extension_versions: bool, json_profiles_capabilities_block): + if "extensions" not in json_profiles_capabilities_block: + return + + extensions = gatherDependentExtensions(vk, version, ignore_extension_versions, json_profiles_capabilities_block["extensions"]) + json_profiles_capabilities_block["extensions"] = extensions + + return + + +# TODO: Add Vulkan version and other profiles? (Maybe not necessary) +def pull_profiles_file_dependencies(vk: VulkanObject, require_promoted_extensions: bool, ignore_extension_versions: bool, json_file_data): + profiles_data = json_file_data["profiles"] + json_profiles_capabilities = json_file_data["capabilities"] + + for key, value in profiles_data.items(): + version = VK_VERSION.NONE + if not require_promoted_extensions: + version = VK_VERSION.from_string(value["api-version"]) + + block_names = collect_block_names(value["capabilities"]) + + for block_name in block_names: + pull_capabilities_block_dependencies(vk, version, ignore_extension_versions, json_profiles_capabilities[block_name]) + + +def pull_profiles_files_dependencies(vk: VulkanObject, require_promoted_extensions: bool, ignore_extension_versions: bool, json_files_dict): + for key, value in json_files_dict.items(): + logging.debug(f"Fill capabilities dependencies for: {key}") + pull_profiles_file_dependencies(vk, require_promoted_extensions, ignore_extension_versions, value) + + +def pull_aliases_capabilities_block(vk: VulkanObject, version: VK_VERSION, ignore_extension_versions: bool, json_profiles_capabilities_block): + if "features" in json_profiles_capabilities_block: + for struct_name, members in json_profiles_capabilities_block["features"].items(): + #aliases = gatherDependentCapabilityAliases(vk, version) + continue # TODO + + if "properties" in json_profiles_capabilities_block: + for struct_name, members in json_profiles_capabilities_block["properties"].items(): + #aliases = gatherDependentCapabilityAliases(vk, version) + continue # TODO + + return + + +def pull_aliases_profiles_file(vk: VulkanObject, require_promoted_extensions: bool, ignore_extension_versions: bool, json_file_data): + profiles_data = json_file_data["profiles"] + json_profiles_capabilities = json_file_data["capabilities"] + + for key, value in profiles_data.items(): + version = VK_VERSION.NONE + if not require_promoted_extensions: + version = VK_VERSION.from_string(value["api-version"]) + + block_names = collect_block_names(value["capabilities"]) + + for block_name in block_names: + pull_aliases_capabilities_block(vk, version, ignore_extension_versions, json_profiles_capabilities[block_name]) + + +def pull_aliases_profiles_files(vk: VulkanObject, require_promoted_extensions: bool, ignore_extension_versions: bool, json_files_dict): + for key, value in json_files_dict.items(): + logging.debug(f"Fill capabilities aliases for: {key}") + pull_aliases_profiles_file(vk, require_promoted_extensions, ignore_extension_versions, value) + + +def strip_capabilities_block_duplication(json_files_dict, json_profiles_capabilities_block, collected_extension_names: set[str]): + if "extensions" not in json_profiles_capabilities_block: + return + + stripped_extensions: dict[str, int] = {} + + for extension_name, extension_version in json_profiles_capabilities_block["extensions"].items(): + if extension_name in collected_extension_names: + continue # The extension was already listed, it's a duplicate. + collected_extension_names.add(extension_name) + stripped_extensions[extension_name] = extension_version + + json_profiles_capabilities_block["extensions"] = stripped_extensions + + +def strip_profiles_file_capabilities_duplication(json_files_dict, json_file_data): + profiles_data = json_file_data["profiles"] + json_profiles_capabilities = json_file_data["capabilities"] + + for key, value in profiles_data.items(): + collected_extension_names: set[str] = set() + + version = VK_VERSION.from_string(value["api-version"]) + + block_names = collect_block_names(value["capabilities"]) # Here, it collects all the block names but some blocks are OR + + for block_name in block_names: + strip_capabilities_block_duplication(json_files_dict, json_profiles_capabilities[block_name], collected_extension_names) + + return + + +def strip_profiles_files_capabilities_duplication(json_files_dict): + for key, value in json_files_dict.items(): + logging.debug(f"Strip duplicated capabilities for: {key}") + strip_profiles_file_capabilities_duplication(json_files_dict, value) + + +def main_convert(args): + vk = initVulkanObject('vulkan', args.registry or None) + + for version in vk.versions.values(): + logging.debug(version.name) + + json_files_dict = load_profiles_jsons(Path(args.input)) + #save_profiles_jsons(json_files_dict, Path(args.format)) + + mode_enums = [ConvertBits(m) for m in args.mode] + + require_promoted_extensions = False + if ConvertBits.PULL_PROMOTED_EXTENSIONS in mode_enums: + require_promoted_extensions = True + + ignore_extension_versions = False + if ConvertBits.IGNORE_EXTENSION_VERSIONS in mode_enums: + ignore_extension_versions = True + + if ConvertBits.PULL_DEPENDENCES in mode_enums: + pull_profiles_files_dependencies(vk, require_promoted_extensions, ignore_extension_versions, json_files_dict) + + if ConvertBits.PULL_ALIASES in mode_enums: + pull_aliases_profiles_files(vk, require_promoted_extensions, ignore_extension_versions, json_files_dict) + + if ConvertBits.STRIP_DUPLICATION in mode_enums: + strip_profiles_files_capabilities_duplication(json_files_dict) + + save_profiles_jsons(json_files_dict, Path(args.output), OutputFormatType(args.format)) + + diff --git a/scripts/source/main_schema.py b/scripts/source/main_schema.py new file mode 100644 index 00000000..28e59eba --- /dev/null +++ b/scripts/source/main_schema.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2026-2026 Google, Inc. +# Copyright (C) 2026-2026 Valve Corporation +# Copyright (c) 2026-2026 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. +# +# Authors: +# - Christophe Riccio + +from source.vulkan_object_utils import initVulkanObject +from source.generate_profiles_schema import VulkanProfilesSchemaGenerator2 + +def main_schema(args): + if args.registry is None: + vk = initVulkanObject(args.api) + else: + vk = initVulkanObject(args.api, args.registry, True) + generator = VulkanProfilesSchemaGenerator2(vk) + generator.generate(args.output) diff --git a/scripts/source/main_validate.py b/scripts/source/main_validate.py new file mode 100644 index 00000000..b2ed7650 --- /dev/null +++ b/scripts/source/main_validate.py @@ -0,0 +1,38 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2026-2026 Google, Inc. +# Copyright (C) 2026-2026 Valve Corporation +# Copyright (c) 2026-2026 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. +# +# Authors: +# - Christophe Riccio + +from pathlib import Path + +from source.vulkan_object_utils import initVulkanObject +from source.generate_profiles_schema import VulkanProfilesSchemaGenerator2 +from source.profiles_parsing import validate_profiles_json, validate_profiles_jsons_data +from source.log import Log + +def main_validate(args): + if args.schema is None: + if args.registry is None: + Log.e("`--schema` or `--registry` are required to validate profile files") + else: + vk = initVulkanObject(args.api, args.registry, True) + generator2 = VulkanProfilesSchemaGenerator2(vk) + validate_profiles_jsons_data(Path(args.input), generator2.schema) + else: + validate_profiles_json(Path(args.input), Path(args.schema)) From 609af09cdfb4788049c6155f946d374a2d494fe3 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 6 Aug 2026 14:03:48 +0200 Subject: [PATCH 17/17] library: Fix test build --- library/test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/test/CMakeLists.txt b/library/test/CMakeLists.txt index 884052e6..330a7cd5 100644 --- a/library/test/CMakeLists.txt +++ b/library/test/CMakeLists.txt @@ -248,7 +248,7 @@ function(add_unit_test_no_prototypes NAME) target_compile_definitions(${TEST_NAME} PUBLIC "VK_ENABLE_BETA_EXTENSIONS=1") target_include_directories(${TEST_NAME} PUBLIC "${vulkan-headers_SOURCE_DIR}/include") target_link_libraries(${TEST_NAME} PRIVATE GTest::gtest GTest::gtest_main Vulkan::Headers Vulkan::Profiles Vulkan::CompilerConfiguration Vulkan::CompilerConfigurationExtra) - add_dependencies(${TEST_NAME} VpGenerated VpLibrary_test_generated_library) + add_dependencies(${TEST_NAME} VpGenerate-Libraries VpLibrary_test_generated_library) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME} --gtest_catch_exceptions=0) set_target_properties(${TEST_NAME} PROPERTIES FOLDER "Profiles API library") endfunction()