COPYRIGHT=u""" /* Copyright © 2023 Collabora, Ltd. * * 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 (including the next * paragraph) 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. */ """ import argparse import os import textwrap import xml.etree.ElementTree as et from mako.template import Template from vk_extensions import get_api_list TEMPLATE_C = Template(COPYRIGHT + """\ #include "vk_synchronization.h" VkPipelineStageFlags2 vk_expand_pipeline_stage_flags2(VkPipelineStageFlags2 stages) { % for (group_stage, stages) in group_stages.items(): if (stages & ${group_stage}) stages |= ${' |\\n '.join(stages)}; % endfor if (stages & VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT) { % for (guard, stage) in all_commands_stages: % if guard is not None: #ifdef ${guard} % endif stages |= ${stage}; % if guard is not None: #endif % endif % endfor } return stages; } VkAccessFlags2 vk_read_access2_for_pipeline_stage_flags2(VkPipelineStageFlags2 stages) { VkAccessFlags2 access = 0; % for ((guard, stages), access) in stages_read_access.items(): % if guard is not None: #ifdef ${guard} % endif if (stages & (${' |\\n '.join(stages)})) access |= ${' |\\n '.join(access)}; % if guard is not None: #endif % endif % endfor return access; } VkAccessFlags2 vk_write_access2_for_pipeline_stage_flags2(VkPipelineStageFlags2 stages) { VkAccessFlags2 access = 0; % for ((guard, stages), access) in stages_write_access.items(): % if guard is not None: #ifdef ${guard} % endif if (stages & (${' |\\n '.join(stages)})) access |= ${' |\\n '.join(access)}; % if guard is not None: #endif % endif % endfor return access; } """) def get_guards(xml, api): guards = {} for ext_elem in xml.findall('./extensions/extension'): supported = get_api_list(ext_elem.attrib['supported']) if api not in supported: continue for enum in ext_elem.findall('./require/enum[@extends]'): if enum.attrib['extends'] not in ('VkPipelineStageFlagBits2', 'VkAccessFlagBits2'): continue if 'protect' not in enum.attrib: continue name = enum.attrib['name'] guard = enum.attrib['protect'] guards[name] = guard return guards def get_all_commands_stages(xml, guards): stages = [] for stage in xml.findall('./sync/syncstage'): stage_name = stage.attrib['name'] exclude = [ # This isn't a real stage 'VK_PIPELINE_STAGE_2_NONE', # These are real stages but they're a bit weird to include in # ALL_COMMANDS because they're context-dependent, depending on # whether they're part of srcStagesMask or dstStagesMask. # # We could avoid all grouped stages but then if someone adds # another group later, the behavior of this function may change in # a backwards-compatible way. Also, the other ones aren't really # hurting anything if we add them in. 'VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT', 'VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT', # This is all COMMANDS, not host. 'VK_PIPELINE_STAGE_2_HOST_BIT', ] if stage_name in exclude: continue guard = guards.get(stage_name, None) stages.append((guard, stage_name)) return stages def get_group_stages(xml): group_stages = {} for stage in xml.findall('./sync/syncstage'): name = stage.attrib['name'] equiv = stage.find('./syncequivalent') if equiv is not None: stages = equiv.attrib['stage'].split(',') group_stages[name] = stages return group_stages def access_is_read(name): if 'READ' in name: assert 'WRITE' not in name return True elif 'WRITE' in name: return False else: print(name) assert False, "Invalid access bit name" def get_stages_access(xml, read, guards, all_commands_stages, group_stages): stages_access = {} for access in xml.findall('./sync/syncaccess'): access_name = access.attrib['name'] if access_name == 'VK_ACCESS_2_NONE': continue if access_is_read(access_name) != read: continue guard = guards.get(access_name, None) support = access.find('./syncsupport') if support is not None: stages = support.attrib['stage'].split(',') for stage in stages: if (guard, stage) in all_commands_stages: stages.append('VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT') stages.append('VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT' if read else 'VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT') break for (group, equiv) in group_stages.items(): for stage in stages: if stage in equiv: stages.append(group) break stages.sort() key = (guard, tuple(stages)) if key in stages_access: stages_access[key].append(access_name) else: stages_access[key] = [access_name] return stages_access def main(): parser = argparse.ArgumentParser() parser.add_argument('--beta', required=True, help='Enable beta extensions.') parser.add_argument('--xml', required=True, help='Vulkan API XML file') parser.add_argument('--out-c', required=True, help='Output C file.') args = parser.parse_args() xml = et.parse(args.xml); guards = get_guards(xml, 'vulkan') all_commands_stages = get_all_commands_stages(xml, guards) group_stages = get_group_stages(xml) environment = { 'all_commands_stages': all_commands_stages, 'group_stages': group_stages, 'stages_read_access': get_stages_access(xml, True, guards, all_commands_stages, group_stages), 'stages_write_access': get_stages_access(xml, False, guards, all_commands_stages, group_stages), } try: with open(args.out_c, 'w', encoding='utf-8') as f: f.write(TEMPLATE_C.render(**environment)) except Exception: # In the event there's an error, this imports some helpers from mako # to print a useful stack trace and prints it, then exits with # status 1, if python is run with debug; otherwise it just raises # the exception import sys from mako import exceptions print(exceptions.text_error_template().render(), file=sys.stderr) sys.exit(1) if __name__ == '__main__': main()