2021-05-03 23:29:53 +00:00
2021-05-06 21:21:34 +00:00
#
# generate_dispatch.py
2022-02-06 01:26:21 +00:00
#
2021-05-06 21:21:34 +00:00
# Copyright © 2021 Cody Goodson (contact@vibimanx.com)
2022-02-06 01:26:21 +00:00
# Copyright © 2022 Charles Giessen (charles@lunarg.com)
2021-05-06 21:21:34 +00:00
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the “Software”), to deal in the Software without restriction, including without
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2021-05-03 23:29:53 +00:00
2021-05-06 21:21:34 +00:00
# This file is a part of VkBootstrap
# https://github.com/charles-lunarg/vk-bootstrap
2021-06-06 19:41:03 +00:00
# On run, vk.xml is pulled from the master of Khronos's Vulkan-Headers repo and a VkBoostrapDispatch header
2021-05-06 21:21:34 +00:00
# is generated and placed in VkBoostrap's source directory
# https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/master/registry/vk.xml
# This script makes use of xmltodict
# https://github.com/martinblech/xmltodict
2021-06-06 19:41:03 +00:00
# User will be prompted to install if not detected
2021-05-06 21:21:34 +00:00
2022-02-16 04:29:05 +00:00
# Command Line Arguments
# [--auto] Don't ask for input from the command line
2021-05-06 21:21:34 +00:00
# Exclusions
2021-05-06 06:24:19 +00:00
exclusions = [
' vkGetDeviceProcAddr ' ,
' vkCreateDevice ' ,
' vkDestroyDevice '
]
2021-05-05 22:55:02 +00:00
2022-02-06 03:36:34 +00:00
# Excluded extension authors - don't generate anything for these types of extensions
excluded_extension_authors = [
' NVX '
]
2021-05-06 21:21:34 +00:00
# Check for/install xmltodict
2021-05-06 06:24:19 +00:00
import sys
2022-02-16 04:29:05 +00:00
import os
2021-05-06 06:24:19 +00:00
import subprocess
2021-05-06 21:21:34 +00:00
import pkg_resources
2021-05-20 01:15:29 +00:00
import copy
2021-06-15 17:50:23 +00:00
import codecs
2022-02-16 04:29:05 +00:00
import re
2021-05-20 08:19:51 +00:00
from string import Template
2021-05-06 21:21:34 +00:00
installed = { pkg . key for pkg in pkg_resources . working_set }
xmltodict_missing = { ' xmltodict ' } - installed
2021-05-06 06:24:19 +00:00
2021-06-06 19:41:03 +00:00
# Install xmltodict
2021-05-06 21:21:34 +00:00
if xmltodict_missing :
2022-02-16 04:29:05 +00:00
if ' --auto ' not in sys . argv :
val = input ( " xmltodict is required to run this script. Would you like to install? (y/n): " )
else :
val = " y "
2021-06-03 19:27:34 +00:00
if ( val . lower ( ) == " y " ) :
try :
subprocess . check_call ( [ sys . executable , ' -m ' , ' pip ' , ' install ' , ' xmltodict ' ] )
except subprocess . CalledProcessError as error :
2022-02-06 03:36:34 +00:00
print ( " Failed to install xmltodict due to error: " )
print ( error )
2022-02-16 04:29:05 +00:00
if ' --auto ' not in sys . argv :
input ( " Press Enter to continue... " )
2022-02-06 03:36:34 +00:00
sys . exit ( )
2021-06-03 19:27:34 +00:00
else :
2022-02-06 03:36:34 +00:00
sys . exit ( )
2021-05-06 06:24:19 +00:00
2021-05-06 21:21:34 +00:00
# Fetch fresh vk.xml from Khronos repo
2021-05-06 06:24:19 +00:00
import urllib . request
import xmltodict
2022-02-06 01:26:21 +00:00
try :
response = urllib . request . urlopen ( ' https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/registry/vk.xml ' )
2021-06-03 23:15:10 +00:00
except urllib . error . URLError as error :
2022-02-06 03:36:34 +00:00
print ( " Failed to download vk.xml due to error: " )
2021-06-03 23:15:10 +00:00
print ( error . reason )
2022-02-16 04:29:05 +00:00
if ' -q ' not in sys . argv :
input ( " Press Enter to continue... " )
2022-02-06 03:36:34 +00:00
sys . exit ( )
2021-06-03 23:15:10 +00:00
vk_xml_raw = response . read ( )
2021-05-06 06:24:19 +00:00
vk_xml = xmltodict . parse ( vk_xml_raw , process_namespaces = True )
2021-05-20 08:19:51 +00:00
command_params = { ' return_type ' : ' ' , ' args ' : [ ] , ' requirements ' : [ ] , ' macro_template ' : Template ( ' ' ) }
2021-05-19 23:03:49 +00:00
2021-05-06 20:46:33 +00:00
device_commands = { }
2021-05-06 18:47:16 +00:00
2022-02-06 03:36:34 +00:00
aliased_types = { }
types_node = vk_xml [ ' registry ' ] [ ' types ' ] [ ' type ' ]
for type_node in types_node :
if ' @alias ' in type_node :
aliased_types [ type_node [ ' @alias ' ] ] = type_node [ ' @name ' ]
2021-05-06 21:21:34 +00:00
# Gather all device functions/aliases for filtering core/extension function fetching
2021-05-19 23:03:49 +00:00
commands_node = vk_xml [ ' registry ' ] [ ' commands ' ] [ ' command ' ]
2021-05-06 18:47:16 +00:00
aliases = { }
2021-05-19 23:03:49 +00:00
for command_node in commands_node :
if ' proto ' in command_node :
command_name = command_node [ ' proto ' ] [ ' name ' ]
2021-05-20 01:15:29 +00:00
new_command_params = copy . deepcopy ( command_params )
2021-05-19 23:03:49 +00:00
new_command_params [ ' return_type ' ] = command_node [ ' proto ' ] [ ' type ' ]
if type ( command_node [ ' param ' ] ) is list :
new_command_params [ ' args ' ] = command_node [ ' param ' ]
2021-05-06 18:47:16 +00:00
else :
2021-05-19 23:03:49 +00:00
new_command_params [ ' args ' ] = [ command_node [ ' param ' ] ]
if not command_name in exclusions :
2022-02-06 03:36:34 +00:00
if new_command_params [ ' args ' ] [ 0 ] [ ' type ' ] in [ ' VkDevice ' , ' VkCommandBuffer ' , ' VkQueue ' ] :
2021-05-19 23:03:49 +00:00
device_commands [ command_name ] = new_command_params
2022-02-06 03:36:34 +00:00
device_commands [ command_name ] [ ' is_alias ' ] = False
2021-05-19 23:03:49 +00:00
elif ' @alias ' in command_node :
2022-02-06 03:36:34 +00:00
aliases [ command_node [ ' @alias ' ] ] = command_node [ ' @name ' ]
2021-05-06 18:47:16 +00:00
2021-05-06 21:21:34 +00:00
# Push the alias name as a device function if the alias exists in device commands
2021-05-06 18:47:16 +00:00
for alias in aliases :
if alias in device_commands :
2022-02-06 03:36:34 +00:00
device_commands [ aliases [ alias ] ] = copy . deepcopy ( device_commands [ alias ] )
device_commands [ aliases [ alias ] ] [ ' is_alias ' ] = True
2021-05-06 18:47:16 +00:00
2021-05-20 01:15:29 +00:00
# Add requirements for core PFN's
2021-05-19 23:03:49 +00:00
features_node = vk_xml [ ' registry ' ] [ ' feature ' ]
for feature_node in features_node :
2021-05-20 08:19:51 +00:00
if feature_node [ ' @name ' ] != ' VK_VERSION_1_0 ' :
for require_node in feature_node [ ' require ' ] :
for param_node in require_node :
if param_node == ' command ' :
2021-06-03 18:46:27 +00:00
if type ( require_node [ param_node ] ) is not list :
require_node [ param_node ] = [ require_node [ param_node ] ]
for param in require_node [ param_node ] :
if param [ ' @name ' ] in device_commands :
device_commands [ param [ ' @name ' ] ] [ ' requirements ' ] + = [ [ feature_node [ ' @name ' ] ] ]
2021-05-06 18:47:16 +00:00
2021-05-20 01:15:29 +00:00
# Add requirements for extension PFN's
2021-05-19 23:03:49 +00:00
extensions_node = vk_xml [ ' registry ' ] [ ' extensions ' ] [ ' extension ' ]
for extension_node in extensions_node :
extension_name = extension_node [ ' @name ' ]
2021-05-20 01:15:29 +00:00
if ' require ' in extension_node . keys ( ) :
require_nodes = extension_node [ ' require ' ]
for require_node in require_nodes :
2022-02-06 03:36:34 +00:00
requirements = [ extension_name ]
2021-05-20 01:15:29 +00:00
if type ( require_node ) is not str :
if ' command ' in require_node . keys ( ) :
if ' @feature ' in require_node . keys ( ) :
requirements . append ( require_node [ ' @feature ' ] )
if ' @extension ' in require_node . keys ( ) :
requirements . append ( require_node [ ' @extension ' ] )
if type ( require_node [ ' command ' ] ) is not list :
2021-06-03 18:46:27 +00:00
require_node [ ' command ' ] = [ require_node [ ' command ' ] ]
for command_node in require_node [ ' command ' ] :
2021-05-20 01:15:29 +00:00
if command_node [ ' @name ' ] in device_commands :
2022-02-06 03:36:34 +00:00
if ' @author ' in extension_node and extension_node [ ' @author ' ] in excluded_extension_authors :
device_commands . pop ( command_node [ ' @name ' ] )
else :
device_commands [ command_node [ ' @name ' ] ] [ ' requirements ' ] + = [ requirements ]
2021-06-03 18:46:27 +00:00
elif require_node == ' command ' :
if type ( require_nodes [ ' command ' ] ) is not list :
require_nodes [ ' command ' ] = [ require_nodes [ ' command ' ] ]
for command_node in require_nodes [ ' command ' ] :
if command_node [ ' @name ' ] in device_commands :
2022-02-06 03:36:34 +00:00
if ' @author ' in extension_node and extension_node [ ' @author ' ] in excluded_extension_authors :
device_commands . pop ( command_node [ ' @name ' ] )
else :
device_commands [ command_node [ ' @name ' ] ] [ ' requirements ' ] + = [ requirements ]
2021-05-20 01:15:29 +00:00
2021-05-20 08:19:51 +00:00
# Generate macro templates
for command in device_commands :
macro = ' '
requirements_collection = device_commands [ command ] [ ' requirements ' ]
collection_count = len ( requirements_collection )
if collection_count > 0 :
macro = ' #if '
while ( collection_count > 0 ) :
for requirements in requirements_collection :
requirements_count = len ( requirements )
macro + = ' ( '
for requirement in requirements :
macro + = ' defined( {} ) ' . format ( requirement )
requirements_count - = 1
if requirements_count > 0 :
macro + = ' && '
macro + = ' ) '
if collection_count > 0 :
collection_count - = 1
if collection_count > 0 :
macro + = ' || '
macro + = ' \n $body#endif \n '
else :
macro = ' $body '
device_commands [ command ] [ ' macro_template ' ] = Template ( macro )
2021-05-06 21:21:34 +00:00
# License
2021-05-20 08:19:51 +00:00
license = ' /* \n '
license + = ' * Copyright © 2021 Cody Goodson (contact@vibimanx.com) \n '
2022-02-06 01:26:21 +00:00
license + = ' * Copyright © 2022 Charles Giessen (charles@lunarg.com) \n '
2021-05-20 08:19:51 +00:00
license + = ' * \n '
license + = ' * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated \n '
license + = ' * documentation files (the “Software”), to deal in the Software without restriction, including without \n '
license + = ' * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies \n '
license + = ' * of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: \n '
license + = ' * \n '
license + = ' * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. \n '
license + = ' * \n '
license + = ' * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \n '
license + = ' * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. \n '
license + = ' * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \n '
license + = ' * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \n '
license + = ' * \n '
license + = ' */ \n \n '
2021-05-06 21:21:34 +00:00
2021-05-20 08:19:51 +00:00
# Info
info = ' // This file is a part of VkBootstrap \n '
info + = ' // https://github.com/charles-lunarg/vk-bootstrap \n \n '
2021-05-06 21:21:34 +00:00
2021-05-19 23:03:49 +00:00
# # Content
2021-05-20 08:19:51 +00:00
body = ' \n #pragma once \n \n #include <vulkan/vulkan.h> \n \n '
body + = ' namespace vkb { \n \n '
body + = ' struct DispatchTable { \n '
body + = ' \t DispatchTable() = default; \n '
2021-06-04 00:34:39 +00:00
body + = ' \t DispatchTable(VkDevice device, PFN_vkGetDeviceProcAddr procAddr) : device(device), populated(true) { \n '
2021-05-06 18:47:16 +00:00
2021-05-20 08:19:51 +00:00
proxy_section = ' '
fp_decl_section = ' '
pfn_load_section = ' '
2021-05-06 18:47:16 +00:00
2021-06-06 19:41:03 +00:00
proxy_template = Template ( ' \t $return_type $proxy_name($args_full) const noexcept { \n \t \t $opt_return$fp_name($args_names); \n \t } \n ' )
2021-05-20 08:19:51 +00:00
fp_decl_template = Template ( ' \t $pfn_name $fp_name = nullptr; \n ' )
2022-02-06 03:39:07 +00:00
pfn_load_template = Template ( ' \t \t $fp_name = reinterpret_cast<$pfn_name>(procAddr(device, " $command_name " )); \n ' )
2021-05-06 20:46:33 +00:00
2021-05-20 08:19:51 +00:00
for command in device_commands :
params = device_commands [ command ]
# easy stuff out of the way
return_type = params [ ' return_type ' ]
if ( return_type != ' void ' ) :
opt_return = ' return '
else :
opt_return = ' '
proxy_name = command [ 2 ] . lower ( ) + command [ 3 : ]
fp_name = ' fp_ ' + command
pfn_name = ' PFN_ ' + command
2021-05-06 20:46:33 +00:00
2021-05-20 08:19:51 +00:00
# Now for args
arg_template = Template ( ' $front_mods$arg_type$back_mods$arg_name$array ' )
args_full = ' '
args_names = ' '
args_count = len ( params [ ' args ' ] )
i = args_count
for arg in params [ ' args ' ] :
2022-02-06 03:36:34 +00:00
front_mods = ' '
back_mods = ' '
array = ' '
2021-05-20 08:19:51 +00:00
arg_type = arg [ ' type ' ]
arg_name = arg [ ' name ' ]
if ' #text ' in arg :
text = arg [ ' #text ' ]
text = text . replace ( ' ' , ' ' )
array_index = text . find ( ' [ ' )
if array_index != - 1 :
array = text [ array_index : ]
text = text [ 0 : array_index ]
if text == ' * ' :
front_mods = ' '
back_mods = ' * '
elif text == ' ** ' :
front_mods = ' '
back_mods = ' ** '
elif text == ' const* ' :
front_mods = ' const '
back_mods = ' * '
elif text == ' const** ' :
front_mods = ' const '
back_mods = ' ** '
elif text == ' const*const* ' :
front_mods = ' const '
back_mods = ' * const* '
if i == args_count and arg_type == ' VkDevice ' :
args_names + = arg_name
if i > 0 :
i - = 1
if i > 0 :
args_names + = ' , '
else :
2022-02-06 03:36:34 +00:00
if arg_type in aliased_types :
arg_type = aliased_types [ arg_type ]
2021-05-20 08:19:51 +00:00
args_full + = arg_template . substitute ( front_mods = front_mods , arg_type = arg_type , back_mods = back_mods , arg_name = arg_name , array = array )
args_names + = arg_name
if i > 0 :
i - = 1
if i > 0 :
args_full + = ' , '
args_names + = ' , '
2021-05-06 20:46:33 +00:00
2021-05-20 08:19:51 +00:00
proxy_body = proxy_template . substitute ( return_type = return_type , proxy_name = proxy_name , args_full = args_full , opt_return = opt_return , fp_name = fp_name , args_names = args_names )
fp_decl_body = fp_decl_template . substitute ( pfn_name = pfn_name , fp_name = fp_name )
pfn_load_body = pfn_load_template . substitute ( fp_name = fp_name , pfn_name = pfn_name , command_name = command )
2021-05-06 18:47:16 +00:00
2021-05-20 08:19:51 +00:00
macro_template = params [ ' macro_template ' ]
proxy_section + = macro_template . substitute ( body = proxy_body )
fp_decl_section + = macro_template . substitute ( body = fp_decl_body )
pfn_load_section + = macro_template . substitute ( body = pfn_load_body )
2021-05-17 20:25:19 +00:00
2021-05-20 08:19:51 +00:00
body + = pfn_load_section
body + = ' \t } \n '
body + = proxy_section
body + = fp_decl_section
2021-06-03 23:15:10 +00:00
body + = ' \t bool is_populated() const { return populated; } \n '
2021-05-20 08:19:51 +00:00
body + = ' \t VkDevice device = VK_NULL_HANDLE; \n '
2021-06-03 23:15:10 +00:00
body + = ' private: \n '
body + = ' \t bool populated = false; \n '
2021-05-20 08:19:51 +00:00
body + = ' }; \n \n '
body + = ' } // namespace vkb '
2021-05-06 20:46:33 +00:00
2022-02-16 04:29:05 +00:00
# find the version used to generate the code
for type_node in types_node :
if ' name ' in type_node and type_node [ ' name ' ] == ' VK_HEADER_VERSION_COMPLETE ' :
complete_header_version = type_node [ " #text " ]
if ' name ' in type_node and type_node [ ' name ' ] == ' VK_HEADER_VERSION ' :
vk_header_version = type_node [ ' #text ' ]
find_number_fields = re . compile ( ' [0-9]+ ' )
version_fields = find_number_fields . findall ( complete_header_version )
header_version_field = find_number_fields . findall ( vk_header_version ) [ 0 ]
version_tag = f ' { version_fields [ 1 ] } . { version_fields [ 2 ] } . { header_version_field } '
2021-05-20 08:19:51 +00:00
header = license + info + body
2021-05-06 18:47:16 +00:00
2022-02-16 04:29:05 +00:00
path_to_src = os . path . join ( ' src ' )
if not os . path . exists ( path_to_src ) :
path_to_src = os . path . join ( ' .. ' , ' src ' )
if not os . path . exists ( path_to_src ) :
print ( " Couldn ' t find source folder. Is the current directory wrong? " )
sys . exit ( )
header_file = codecs . open ( os . path . join ( path_to_src , " VkBootstrapDispatch.h " ) , " w " , " utf-8 " )
2021-05-20 08:19:51 +00:00
header_file . write ( header )
2022-02-06 01:26:21 +00:00
header_file . close ( )
2021-06-03 19:27:34 +00:00
2022-02-16 04:29:05 +00:00
path_to_gen = os . path . join ( ' gen ' )
if not os . path . exists ( path_to_gen ) :
path_to_gen = os . path . join ( ' .. ' , ' gen ' )
if not os . path . exists ( path_to_gen ) :
print ( " Couldn ' t find gen folder. Is the current directory wrong? " )
sys . exit ( )
# Generate a CMake file that contains the header version used.
cmake_version_file = codecs . open ( os . path . join ( path_to_gen , " CurrentBuildVulkanVersion.cmake " ) , " w " , " utf-8 " )
cmake_version_file . write ( f ' set(VK_BOOTSTRAP_SOURCE_HEADER_VERSION { version_tag } ) \n ' )
cmake_version_file . write ( f ' set(VK_BOOTSTRAP_SOURCE_HEADER_VERSION_GIT_TAG v { version_tag } ) \n ' )
cmake_version_file . close ( )
2022-02-06 01:26:21 +00:00
print ( " Generation finished. " )