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/.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/CHANGELOG.md b/CHANGELOG.md index 4498abcd..a5a25996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,19 @@ ### 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 +- 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/CMakeLists.txt b/CMakeLists.txt index 2a3d9684..f0bea577 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,38 @@ 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}/python-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 +) + +# This target works in subdirectories as a dependency. +add_custom_target(python_venv DEPENDS "${VENV_STAMP}") + +# ============================================================================== + find_package(VulkanHeaders REQUIRED CONFIG QUIET) find_package(VulkanUtilityLibraries REQUIRED CONFIG QUIET) find_package(valijson REQUIRED CONFIG) @@ -69,7 +100,59 @@ 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 PATHS) + 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}") + + 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 --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..." + 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,30 +166,27 @@ 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") +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() -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" + PATHS + "${VULKAN_REGISTRY_DIR}" + "${VULKAN_REGISTRY_SHARE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/source" DEPENDS ${PYTHON_DEPENDENCIES} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Packaging profiles.py into a standalone executable..." - VERBATIM + FOLDER "Profiles generator" ) -add_custom_target( - profiles - DEPENDS "${FINAL_EXECUTABLE}" -) -set_target_properties(profiles PROPERTIES FOLDER "Profiles generator") - # The profiles directory regenerates the Profiles source and headers. add_subdirectory(profiles) add_subdirectory(library) add_subdirectory(layer) - diff --git a/layer/CMakeLists.txt b/layer/CMakeLists.txt index eab5c7d9..11b162b8 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" + ${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 @@ -176,13 +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 Python3::Interpreter ${TESTS_PYTHON_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${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 @@ -192,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/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}) diff --git a/library/test/CMakeLists.txt b/library/test/CMakeLists.txt index fa8e455c..330a7cd5 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" + ${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 @@ -35,11 +36,10 @@ 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 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 +57,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 +69,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 +87,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 +100,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 +174,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 +193,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 +211,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 +222,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) @@ -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() diff --git a/profiles/CMakeLists.txt b/profiles/CMakeLists.txt index 292e86f2..65e821b5 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,9 +45,38 @@ 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_vulkan_profile_2021.json,VP_ANDROID_vulkan_profile_2022.json" ) +# 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 ${VKPROFILES_EXE} + schema + --registry ${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry/vk.xml + --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 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.") set(PROFILE_DESKTOP_MAX_2024_API_VERSION "1.3.244") @@ -57,8 +85,10 @@ 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 - COMMAND Python3::Interpreter ${MERGE_SCRIPT} +# Generate profiles files +add_custom_target(VpGenerate-ProfilesDesktopMax ALL + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${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 @@ -68,7 +98,8 @@ add_custom_target(VpCreateDesktopMax 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" + ${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 @@ -80,42 +111,45 @@ add_custom_target(VpCreateDesktopMax 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) -set_target_properties(VpCreateDesktopMax PROPERTIES FOLDER "Profiles generator") - -add_custom_target(VpCreateDesktopBaseline ALL - COMMAND Python3::Interpreter ${MERGE_SCRIPT} +add_custom_target(VpGenerate-ProfilesDesktopBaseline ALL + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${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 --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(VpCreateDesktopBaseline PROPERTIES FOLDER "Profiles generator") + DEPENDS ${MERGE_SCRIPT} ${PROFILES_FILES} python_venv) +set_target_properties(VpGenerate-ProfilesDesktopBaseline PROPERTIES FOLDER "Profiles generator") +add_dependencies(VpGenerate-ProfilesDesktopBaseline VpGenerate-ProfilesSchema) -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} - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + ${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 ${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 --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" + ${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} @@ -124,7 +158,8 @@ add_custom_target(VpGenerated --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" + ${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} @@ -133,16 +168,18 @@ add_custom_target(VpGenerated --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" + ${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 - --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" --config release - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + 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 --input ${CMAKE_CURRENT_LIST_DIR} @@ -151,7 +188,8 @@ add_custom_target(VpGenerated --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" + ${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} @@ -160,19 +198,29 @@ add_custom_target(VpGenerated --output-library-src ${PROJECT_SOURCE_DIR}/library/test --output-library-filename "generated_vulkan_profiles_debug" --config debug - COMMAND Python3::Interpreter ${SOLUTION_SCRIPT} + VERBATIM + SOURCES ${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" + ${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 Python3::Interpreter ${SOLUTION_SCRIPT} + 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 --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" + ${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 @@ -180,14 +228,13 @@ add_custom_target(VpGenerated --output-doc ${PROJECT_SOURCE_DIR}/PROFILES_ANDROID.md VERBATIM SOURCES ${SOLUTION_SCRIPT} ${PROFILES_FILES} - DEPENDS ${SOLUTION_SCRIPT} ${PROFILES_FILES}) - -set_target_properties(VpGenerated PROPERTIES FOLDER "Profiles generator") - -add_dependencies(VpGenerated VpCreateDesktopBaseline VpCreateDesktopMax) + 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 Python3::Interpreter ${MERGE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${VULKAN_HEADERS_INSTALL_DIR}/${CMAKE_INSTALL_DATADIR}/vulkan/registry" + ${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 @@ -195,14 +242,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 VpGenerated) +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" + ${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 @@ -210,37 +256,33 @@ 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 VpGenerated) +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" + ${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 VpGenerated) +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" + ${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 VpGenerated) +add_dependencies(VpTestHostImageCopy VpProfilesProcessor) set(json_install_dir "${CMAKE_INSTALL_DATADIR}/vulkan/config/VK_LAYER_KHRONOS_profiles") if (WIN32) @@ -256,6 +298,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..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}) @@ -34,12 +36,9 @@ 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) +set_tests_properties(${TEST_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${REGISTRY_PATH}") +add_dependencies(${TEST_NAME} VpGenerate-ProfilesDesktopMax VpGenerate-ProfilesDesktopBaseline) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..c9e4f3ad --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +# Python modules needed for project builds +jsonschema == 4.* +pyinstaller == 6.* +pyparsing == 3.* diff --git a/scripts/gen_profiles_solution.py b/scripts/gen_profiles_solution.py index b72504e6..08ec53dc 100644 --- a/scripts/gen_profiles_solution.py +++ b/scripts/gen_profiles_solution.py @@ -15,60 +15,26 @@ # 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.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 = ''' /* @@ -3108,40 +3074,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 +3104,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 +4647,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 +4660,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 @@ -7843,7 +5739,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.') @@ -7901,11 +5797,14 @@ def gen_videoFormats(self, videoProfileName, videoCodec): if args.registry != None: registry = VulkanRegistry(args.registry, args.api) + vk: VulkanObject = initVulkanObject(args.api, 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/profiles.py b/scripts/profiles.py index 946b4919..84ec8d6e 100644 --- a/scripts/profiles.py +++ b/scripts/profiles.py @@ -20,139 +20,13 @@ # - Christophe Riccio import logging -from datetime import datetime -from enum import StrEnum -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 -from source.profiles_parsing import OutputFormatType - -class ConvertMode(StrEnum): - 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.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): - validate_profiles_json(Path(args.input), Path(args.schema)) +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') @@ -161,24 +35,30 @@ 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('--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 new file mode 100644 index 00000000..1116eed8 --- /dev/null +++ b/scripts/source/generate_profiles_schema.py @@ -0,0 +1,589 @@ +#!/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 new file mode 100644 index 00000000..5561da33 --- /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/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)) 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 33f9f208..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 @@ -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] @@ -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/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 + + diff --git a/scripts/tests/CMakeLists.txt b/scripts/tests/CMakeLists.txt index ec75dddf..0c151bd6 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}" ) @@ -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) + 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() 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 24ca21d0..447eff85 100644 --- a/scripts/tests/test_vulkan_object_utils.py +++ b/scripts/tests/test_vulkan_object_utils.py @@ -29,14 +29,14 @@ 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): 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, @@ -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('vulkan', 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()