The open source OpenXR runtime

xrt: Separate bindings generation into state trackers

Part-of: <https://gitlab.freedesktop.org/monado/monado/-/merge_requests/2564>

authored by

Christoph Haag and committed by
Marge Bot
620f3c7a 62013b74

+938 -499
+13 -7
src/xrt/auxiliary/bindings/CMakeLists.txt
··· 1 - # Copyright 2019-2021, Collabora, Ltd. 1 + # Copyright 2019-2025, Collabora, Ltd. 2 2 # SPDX-License-Identifier: BSL-1.0 3 3 4 4 # Binding generation: pass filename to generate 5 - function(bindings_gen output) 5 + function(bindings_gen template output) 6 6 add_custom_command( 7 7 OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${output}" 8 8 COMMAND 9 9 ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings.py 10 10 ${CMAKE_CURRENT_SOURCE_DIR}/bindings.json 11 + "${CMAKE_CURRENT_SOURCE_DIR}/${template}" 11 12 "${CMAKE_CURRENT_BINARY_DIR}/${output}" 12 13 VERBATIM 13 14 DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.py ··· 16 17 ) 17 18 endfunction() 18 19 19 - bindings_gen(b_generated_bindings.h) 20 - bindings_gen(b_generated_bindings.c) 20 + bindings_gen(b_generated_bindings_helpers.c.template b_generated_bindings_helpers.c) 21 21 22 22 # Bindings library. 23 23 add_library( 24 - aux_generated_bindings STATIC ${CMAKE_CURRENT_BINARY_DIR}/b_generated_bindings.c 25 - ${CMAKE_CURRENT_BINARY_DIR}/b_generated_bindings.h 24 + aux_generated_bindings_helpers STATIC 25 + ${CMAKE_CURRENT_BINARY_DIR}/b_generated_bindings_helpers.c 26 26 ) 27 27 28 28 # needed globally for steamvr input profile generation in steamvr target 29 29 set_property(GLOBAL PROPERTY AUX_BINDINGS_DIR_PROP "${CMAKE_CURRENT_SOURCE_DIR}") 30 30 31 - target_link_libraries(aux_generated_bindings PRIVATE xrt-interfaces oxr-interfaces) 31 + target_link_libraries(aux_generated_bindings_helpers PRIVATE xrt-interfaces) 32 + target_include_directories(aux_generated_bindings_helpers PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") 33 + 34 + set(AUX_BINDINGS_DIR 35 + "${CMAKE_CURRENT_SOURCE_DIR}" 36 + CACHE PATH "Path to the shared bindings generation code" 37 + )
+44
src/xrt/auxiliary/bindings/b_generated_bindings_helpers.c.template
··· 1 + // Copyright 2020-2022, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Generated bindings data. 6 + * @author Jakob Bornecrantz <jakob@collabora.com> 7 + * @author Christoph Haag <christoph.haag@collabora.com> 8 + * @author Korcan Hussein <korcan.hussein@collabora.com> 9 + * @ingroup xrt_main 10 + */ 11 + 12 + #include "b_generated_bindings_helpers.h" 13 + #include <string.h> 14 + 15 + // clang-format off 16 + 17 + const char * 18 + xrt_input_name_string(enum xrt_input_name input) { 19 + switch(input) 20 + { 21 + $xrt_input_name_string_switch 22 + } 23 + } 24 + 25 + enum xrt_input_name 26 + xrt_input_name_enum(const char *input) 27 + { 28 + $xrt_input_name_enum_content 29 + } 30 + 31 + const char * 32 + xrt_output_name_string(enum xrt_output_name output) 33 + { 34 + switch(output) 35 + { 36 + $xrt_output_name_string_switch 37 + } 38 + } 39 + 40 + enum xrt_output_name 41 + xrt_output_name_enum(const char *output) 42 + { 43 + $xrt_output_name_enum_content 44 + }
+35
src/xrt/auxiliary/bindings/b_generated_bindings_helpers.h
··· 1 + // Copyright 2020-2022, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Generated bindings helpers header. 6 + * @author Jakob Bornecrantz <jakob@collabora.com> 7 + * @author Christoph Haag <christoph.haag@collabora.com> 8 + * @author Korcan Hussein <korcan.hussein@collabora.com> 9 + * @ingroup xrt_api 10 + */ 11 + 12 + #pragma once 13 + #include <stddef.h> 14 + #include "xrt/xrt_defines.h" 15 + 16 + #ifdef __cplusplus 17 + extern "C" { 18 + #endif 19 + 20 + const char * 21 + xrt_input_name_string(enum xrt_input_name input); 22 + 23 + enum xrt_input_name 24 + xrt_input_name_enum(const char *input); 25 + 26 + const char * 27 + xrt_output_name_string(enum xrt_output_name output); 28 + 29 + enum xrt_output_name 30 + xrt_output_name_enum(const char *output); 31 + 32 + // clang-format on" 33 + #ifdef __cplusplus 34 + } 35 + #endif
+63 -467
src/xrt/auxiliary/bindings/bindings.py
··· 1 1 #!/usr/bin/env python3 2 - # Copyright 2020-2023, Collabora, Ltd. 2 + # Copyright 2020-2025, Collabora, Ltd. 3 3 # Copyright 2024-2025, NVIDIA CORPORATION. 4 4 # SPDX-License-Identifier: BSL-1.0 5 - """Generate code from a JSON file describing interaction profiles and 6 - bindings.""" 5 + """Parse a JSON file describing interaction profiles and 6 + bindings and write misc utility functions.""" 7 7 8 8 import argparse 9 9 import json 10 10 import copy 11 11 from operator import attrgetter 12 + 13 + from string import Template 14 + 15 + def wl(f, *args, endNewLine=True): 16 + """Write lines""" 17 + s = '\n'.join(args) 18 + if endNewLine: 19 + s += '\n' 20 + f.write(s) 12 21 13 22 def find_component_in_list_by_name(name, component_list, subaction_path=None, identifier_json_path=None): 14 23 """Find a component with the given name in a list of components.""" ··· 265 274 return 266 275 267 276 277 + def is_valid_version(version): 278 + """Returns whether the version is a valid version that is > 0.0""" 279 + if version is None: 280 + return False 281 + # 0.0 is a placeholder for "no particular version" 282 + if version["major"] == '0' and version["minor"] == '0': 283 + return False 284 + return True 285 + 286 + 268 287 class FeatureSet: 269 288 """An AND of requirements (versions and/or extensions) under which a binding becomes available""" 270 289 ··· 284 303 if other.required_extensions.issuperset(self.required_extensions): 285 304 # other requires extensions we don't 286 305 return False 287 - if check_promoted(other.required_version) and not check_promoted(self.required_version): 306 + if is_valid_version(other.required_version) and not is_valid_version(self.required_version): 288 307 # other bounds version, we don't 289 308 return False 290 - if check_promoted(other.required_version) and check_promoted(self.required_version): 309 + if is_valid_version(other.required_version) and is_valid_version(self.required_version): 291 310 if other.required_version["major"] != self.required_version["major"]: 292 311 # different major versions - not fully implemented, but this seems right 293 312 return False ··· 300 319 def and_also(self, other): 301 320 result = copy.deepcopy(self) 302 321 result.required_extensions = self.required_extensions | other.required_extensions 303 - if not check_promoted(result.required_version): 322 + if not is_valid_version(result.required_version): 304 323 result.required_version = other.required_version 305 - elif check_promoted(other.required_version): 324 + elif is_valid_version(other.required_version): 306 325 if result.required_version["major"] != other.required_version["major"]: 307 326 raise NotImplementedError("Major version mismatch not handled") 308 327 if int(result.required_version["minor"]) < int(other.required_version["minor"]): ··· 467 486 def availability(self): 468 487 result = Availability(feature_sets=[]) 469 488 has_requirements = False 470 - if check_promoted(self.openxr_version_promoted): 489 + if is_valid_version(self.openxr_version_promoted): 471 490 result.add_in_place(FeatureSet(required_version=self.openxr_version_promoted)) 472 491 has_requirements = True 473 492 if self.extension_name is not None: ··· 477 496 result.add_in_place(FeatureSet()) 478 497 479 498 return result 480 - 481 - 482 - oxr_verify_extension_status_struct_name = "oxr_verify_extension_status" 483 499 484 500 485 501 class Bindings: ··· 532 548 return True 533 549 return False 534 550 535 - header = '''// Copyright 2020-2022, Collabora, Ltd. 551 + 552 + header = '''// Copyright 2020-2025, Collabora, Ltd. 536 553 // SPDX-License-Identifier: BSL-1.0 537 554 /*! 538 555 * @file ··· 544 561 */ 545 562 ''' 546 563 547 - func_start = ''' 548 - bool 549 - {name}(const struct oxr_extension_status *exts, XrVersion openxr_version, const char *str, size_t length) 550 - {{ 551 - ''' 552 - 553 - 554 - def write_verify_func_begin(f, name): 555 - f.write(func_start.format( 556 - name=name, ext_status_struct_name=oxr_verify_extension_status_struct_name)) 557 - 558 - 559 - def write_verify_func_end(f): 560 - f.write("\treturn false;\n}\n") 561 - 562 - 563 - if_strcmp = '''if (strcmp(str, "{check}") == 0) {{ 564 - {exttab}\t\t\treturn true; 565 - {exttab}\t\t}} else ''' 566 - 567 - def check_promoted(openxr_version_promoted): 568 - # If required version is 0.0, we can skip checking that the instance uses a more recent version 569 - return openxr_version_promoted is not None and not (openxr_version_promoted["major"] == '0' and openxr_version_promoted["minor"] == '0') 570 - 571 - def write_verify_switch_body(f, dict_of_lists, profile, profile_name, tab_char): 572 - """Generate function to check if a string is in a set of strings. 573 - Input is a file to write the code into, a dict where keys are length and 574 - the values are lists of strings of that length. And a suffix if any.""" 575 - f.write(f"{tab_char}\tswitch (length) {{\n") 576 - for length in sorted(dict_of_lists.keys()): 577 - f.write(f"{tab_char}\tcase {str(length)}:\n\t\t{tab_char}") 578 - for path in sorted(dict_of_lists[length]): 579 - f.write(if_strcmp.format(exttab=tab_char, check=path)) 580 - f.write(f"{{\n{tab_char}\t\t\tbreak;\n{tab_char}\t\t}}\n") 581 - f.write(f"{tab_char}\tdefault: break;\n{tab_char}\t}}\n") 582 - 583 - def write_verify_func_switch(f, dict_of_lists, profile, profile_name, availability): 584 - """Generate function to check if a string is in a set of strings. 585 - Input is a file to write the code into, a dict where keys are length and 586 - the values are lists of strings of that length. And a suffix if any.""" 587 - if len(dict_of_lists) == 0: 588 - return 589 - 590 - f.write(f"\t// generated from: {profile_name}\n") 591 - 592 - # Example: pico neo 3 can be enabled by either enabling XR_BD_controller_interaction ext or using OpenXR 1.1+. 593 - # Disabling OXR_HAVE_BD_controller_interaction should NOT remove pico neo from OpenXR 1.1+ (it makes "exts->BD_controller_interaction" invalid C code). 594 - # Therefore separate code blocks for ext and version checks generated to avoid ifdef hell. 595 - feature_sets = sorted(availability.feature_sets, key=FeatureSet.as_tuple) 596 - for feature_set in feature_sets: 597 - requires_version = check_promoted(feature_set.required_version) 598 - requires_extensions = bool(feature_set.required_extensions) 599 - 600 - tab_char = '' 601 - closing = [] 602 - 603 - if requires_version: 604 - tab_char += '\t' 605 - f.write(f'{tab_char}if (openxr_version >= XR_MAKE_VERSION({feature_set.required_version["major"]}, {feature_set.required_version["minor"]}, 0)) {{\n') 606 - closing.append(f'{tab_char}}}\n') 607 - 608 - if requires_extensions: 609 - tab_char += '\t' 610 - exts = sorted(feature_set.required_extensions) 611 - ext_defines = ' && '.join(f'defined(OXR_HAVE_{ext})' for ext in exts) 612 - f.write(f'#if {ext_defines}\n') 613 - f.write(f'{tab_char}if ('+' && '.join(f'exts->{ext}' for ext in exts)+') {\n') 614 - closing.append(f'{tab_char}}}\n#endif // {ext_defines}\n') 615 - 616 - write_verify_switch_body(f, dict_of_lists, profile, profile_name, tab_char) 617 - 618 - for closer in reversed(closing): 619 - f.write(closer) 620 - 621 - def write_verify_func_body(f, profile, dict_name, availability): 622 - if profile is None or dict_name is None or len(dict_name) == 0: 623 - return 624 - write_verify_func_switch(f, getattr( 625 - profile, dict_name), profile, profile.name, availability) 626 - if profile.parent_profiles is None: 627 - return 628 - for pp in sorted(profile.parent_profiles, key=attrgetter("name")): 629 - write_verify_func_body(f, pp, dict_name, availability.intersection(pp.availability())) 630 - 631 - 632 - def write_verify_func(f, profile, dict_name, suffix): 633 - write_verify_func_begin( 634 - f, f"oxr_verify_{profile.validation_func_name}{suffix}") 635 - write_verify_func_body(f, profile, dict_name, profile.availability()) 636 - write_verify_func_end(f) 637 - 638 - 639 - def generate_verify_functions(f, profile): 640 - write_verify_func(f, profile, "subpaths_by_length", "_subpath") 641 - write_verify_func(f, profile, "dpad_paths_by_length", "_dpad_path") 642 - write_verify_func(f, profile, "dpad_emulators_by_length", "_dpad_emulator") 643 - 644 - f.write(f''' 645 - void 646 - oxr_verify_{profile.validation_func_name}_ext(const struct oxr_extension_status *extensions, XrVersion openxr_version, bool *out_supported, bool *out_enabled) 647 - {{ 648 - ''') 649 - is_promoted = check_promoted(profile.openxr_version_promoted) 650 - if is_promoted: 651 - f.write(f'\tif (openxr_version >= XR_MAKE_VERSION({profile.openxr_version_promoted["major"]}, {profile.openxr_version_promoted["minor"]}, 0)) {{\n') 652 - f.write(f'\t\t*out_supported = true;\n') 653 - f.write(f'\t\t*out_enabled = true;\n') 654 - f.write(f'\t\treturn;\n') 655 - f.write(f'\t}}') 656 - 657 - 658 - if profile.extension_name is not None: 659 - f.write(f''' 660 - #ifdef OXR_HAVE_{profile.extension_name} 661 - \t*out_supported = true; 662 - \t*out_enabled = extensions->{profile.extension_name}; 663 - #else 664 - \t*out_supported = false; 665 - \t*out_enabled = false; 666 - #endif // OXR_HAVE_{profile.extension_name} 667 - ''') 668 - 669 - else: 670 - f.write(f''' 671 - \t*out_supported = true; 672 - \t*out_enabled = true; 673 - ''') 674 - 675 - f.write(f'''}} 676 - ''') 677 - 678 - 679 - def generate_bindings_c(file, b): 680 - """Generate the file to verify subpaths on a interaction profile.""" 681 - f = open(file, "w") 682 - f.write(header.format(brief='Generated bindings data', group='oxr_main')) 683 - f.write(''' 684 - #include "b_generated_bindings.h" 685 - #include <string.h> 686 - #include <oxr_objects.h> 687 - 688 - // clang-format off 689 - ''') 690 - 691 - for profile in b.profiles: 692 - generate_verify_functions(f, profile) 693 - 694 - f.write( 695 - f'\n\nstruct profile_template profile_templates[{len(b.profiles)}] = {{ // array of profile_template\n') 696 - for profile in b.profiles: 697 - hw_name = str(profile.name.split("/")[-1]) 698 - vendor_name = str(profile.name.split("/")[-2]) 699 - fname = vendor_name + "_" + hw_name + "_profile.json" 700 - controller_type = "monado_" + vendor_name + "_" + hw_name 701 - 702 - binding_count = len(profile.components) 703 - f.write(f'\t{{ // profile_template\n') 704 - f.write(f'\t\t.name = {profile.monado_device_enum},\n') 705 - f.write(f'\t\t.path = "{profile.name}",\n') 706 - f.write(f'\t\t.localized_name = "{profile.localized_name}",\n') 707 - f.write(f'\t\t.steamvr_input_profile_path = "{fname}",\n') 708 - f.write(f'\t\t.steamvr_controller_type = "{controller_type}",\n') 709 - f.write(f'\t\t.binding_count = {binding_count},\n') 710 - f.write( 711 - f'\t\t.bindings = (struct binding_template[]){{ // array of binding_template\n') 712 - 713 - component: Component 714 - for idx, component in enumerate(profile.components): 715 - 716 - # @todo Doesn't handle pose yet. 717 - steamvr_path = component.steamvr_path 718 - if component.component_name in ["click", "touch", "force", "value", "proximity"]: 719 - steamvr_path += "/" + component.component_name 720 - 721 - f.write(f'\t\t\t{{ // binding_template {idx}\n') 722 - f.write(f'\t\t\t\t.subaction_path = "{component.subaction_path}",\n') 723 - f.write(f'\t\t\t\t.steamvr_path = "{steamvr_path}",\n') 724 - f.write( 725 - f'\t\t\t\t.localized_name = "{component.subpath_localized_name}",\n') 726 - 727 - f.write('\t\t\t\t.paths = { // array of paths\n') 728 - for path in component.get_full_openxr_paths(): 729 - f.write(f'\t\t\t\t\t"{path}",\n') 730 - f.write('\t\t\t\t\tNULL\n') 731 - f.write('\t\t\t\t}, // /array of paths\n') 732 - 733 - # print("component", component.__dict__) 734 - 735 - component_str = component.component_name 736 - 737 - # controllers can have input that we don't have bindings for 738 - if component.monado_binding: 739 - monado_binding = component.monado_binding 740 - 741 - if component.is_input() and monado_binding is not None: 742 - f.write(f'\t\t\t\t.input = {monado_binding},\n') 743 - else: 744 - f.write(f'\t\t\t\t.input = 0,\n') 745 - 746 - if component.has_dpad_emulation() and "activate" in component.dpad_emulation: 747 - activate_component = find_component_in_list_by_name( 748 - component.dpad_emulation["activate"], profile.components, 749 - subaction_path=component.subaction_path, 750 - identifier_json_path=component.identifier_json_path) 751 - f.write( 752 - f'\t\t\t\t.dpad_activate = {activate_component.monado_binding},\n') 753 - else: 754 - f.write(f'\t\t\t\t.dpad_activate = 0,\n') 755 - 756 - if component.is_output() and monado_binding is not None: 757 - f.write(f'\t\t\t\t.output = {monado_binding},\n') 758 - else: 759 - f.write(f'\t\t\t\t.output = 0,\n') 760 - f.write(f'\t\t\t}}, // /binding_template {idx}\n') 761 - 762 - f.write('\t\t}, // /array of binding_template\n') 763 - 764 - dpads = [] 765 - for idx, identifier in enumerate(profile.identifiers): 766 - if identifier.dpad: 767 - dpads.append(identifier) 768 - 769 - # for identifier in dpads: 770 - # print(identifier.path, identifier.dpad_position_component) 771 564 772 - dpad_count = len(dpads) 773 - f.write(f'\t\t.dpad_count = {dpad_count},\n') 774 - if len(dpads) == 0: 775 - f.write(f'\t\t.dpads = NULL,\n') 776 - else: 777 - f.write( 778 - f'\t\t.dpads = (struct dpad_emulation[]){{ // array of dpad_emulation\n') 779 - for idx, identifier in enumerate(dpads): 780 - f.write('\t\t\t{\n') 781 - f.write(f'\t\t\t\t.subaction_path = "{identifier.subaction_path}",\n') 782 - f.write('\t\t\t\t.paths = {\n') 783 - for path in identifier.dpad.paths: 784 - f.write(f'\t\t\t\t\t"{path}",\n') 785 - f.write('\t\t\t\t},\n') 786 - f.write(f'\t\t\t\t.position = {identifier.dpad.position_component.monado_binding},\n') 787 - if identifier.dpad.activate_component: 788 - f.write(f'\t\t\t\t.activate = {identifier.dpad.activate_component.monado_binding},\n') 789 - else: 790 - f.write(f'\t\t\t\t.activate = 0') 791 - 792 - f.write('\t\t\t},\n') 793 - f.write('\t\t}, // /array of dpad_emulation\n') 794 - 795 - f.write(f'\t\t.openxr_version.promoted.major = {profile.openxr_version_promoted["major"]},\n') 796 - f.write(f'\t\t.openxr_version.promoted.minor = {profile.openxr_version_promoted["minor"]},\n') 797 - 798 - fn_prefixes = ["subpath", "dpad_path", "dpad_emulator"] 799 - for prefix in fn_prefixes: 800 - f.write(f'\t\t.{prefix}_fn = oxr_verify_{profile.validation_func_name}_{prefix},\n') 801 - f.write(f'\t\t.ext_verify_fn = oxr_verify_{profile.validation_func_name}_ext,\n') 802 - if profile.extension_name is None: 803 - f.write(f'\t\t.extension_name = NULL,\n') 804 - else: 805 - f.write(f'\t\t.extension_name = "{profile.extension_name}",\n') 806 - f.write('\t}, // /profile_template\n') 807 - 808 - f.write('}; // /array of profile_template\n\n') 565 + def generate_bindings_helpers_c(template, file, b): 566 + """Generate the bindings helpers.""" 809 567 810 568 inputs = set() 811 569 outputs = set() ··· 830 588 inputs.add("XRT_INPUT_HT_CONFORMING_RIGHT") 831 589 inputs.add("XRT_INPUT_GENERIC_TRACKER_POSE") 832 590 833 - f.write('const char *\n') 834 - f.write('xrt_input_name_string(enum xrt_input_name input)\n') 835 - f.write('{\n') 836 - f.write('\tswitch(input)\n') 837 - f.write('\t{\n') 838 - for input in sorted(inputs): 839 - f.write(f'\tcase {input}: return "{input}";\n') 840 - f.write(f'\tdefault: return "UNKNOWN";\n') 841 - f.write('\t}\n') 842 - f.write('}\n') 591 + xrt_input_name_string_switch = '\n'.join( 592 + [(f'\tcase {input}: return "{input}";') for input in sorted(inputs)] 593 + ) 594 + xrt_input_name_string_switch += (f'\n\tdefault: return "UNKNOWN";') 843 595 844 - f.write('enum xrt_input_name\n') 845 - f.write('xrt_input_name_enum(const char *input)\n') 846 - f.write('{\n') 847 - for input in sorted(inputs): 848 - f.write(f'\tif(strcmp("{input}", input) == 0) return {input};\n') 849 - f.write(f'\treturn XRT_INPUT_GENERIC_TRACKER_POSE;\n') 850 - f.write('}\n') 596 + xrt_input_name_enum_content = '\n'.join( 597 + [f'\tif(strcmp("{input}", input) == 0) return {input};' for input in sorted(inputs)] 598 + ) 599 + xrt_input_name_enum_content += f'\n\treturn XRT_INPUT_GENERIC_TRACKER_POSE;' 851 600 852 - f.write('const char *\n') 853 - f.write('xrt_output_name_string(enum xrt_output_name output)\n') 854 - f.write('{\n') 855 - f.write('\tswitch(output)\n') 856 - f.write('\t{\n') 857 - for output in sorted(outputs): 858 - f.write(f'\tcase {output}: return "{output}";\n') 859 - f.write(f'\tdefault: return "UNKNOWN";\n') 860 - f.write('\t}\n') 861 - f.write('}\n') 862 - 863 - f.write('enum xrt_output_name\n') 864 - f.write('xrt_output_name_enum(const char *output)\n') 865 - f.write('{\n') 866 - for output in sorted(outputs): 867 - f.write(f'\tif(strcmp("{output}", output) == 0) return {output};\n') 868 - f.write(f'\treturn XRT_OUTPUT_NAME_SIMPLE_VIBRATION;\n') 869 - f.write('}\n') 870 - 871 - f.write(f''' 872 - 873 - static const struct oxr_bindings_path_cache internal_path_cache = {{ 874 - \t.path_cache = {{''') 875 - for profile_index, _ in enumerate(b.profiles): 876 - f.write(f''' 877 - \t\t{{ 878 - \t\t\t.path_cache = &profile_templates[{profile_index}].path_cache, 879 - \t\t\t.path_cache_name = &profile_templates[{profile_index}].path, 880 - \t\t}},\n''') 881 - profile_index += 1 882 - f.write(f'''\t}} 883 - }}; 884 - 885 - void oxr_get_interaction_profile_path_cache(const struct oxr_bindings_path_cache **out_path_cache) 886 - {{ 887 - *out_path_cache = &internal_path_cache; 888 - }} 889 - ''') 890 - f.write("\n// clang-format on\n") 601 + xrt_output_name_string_switch = '\n'.join( 602 + [(f'\tcase {output}: return "{output}";') for output in sorted(outputs)] 603 + ) 604 + xrt_output_name_string_switch+= f'\n\tdefault: return "UNKNOWN";' 891 605 892 - f.close() 606 + xrt_output_name_enum_content = '\n'.join( 607 + [f'\tif(strcmp("{output}", output) == 0) return {output};' for output in sorted(outputs)] 608 + ) 609 + xrt_output_name_enum_content += f'\n\treturn XRT_OUTPUT_NAME_SIMPLE_VIBRATION;' 893 610 611 + with open(template, "r") as f: 612 + src = Template(f.read()) 894 613 895 - def generate_bindings_h(file, b): 896 - """Generate header for the verify subpaths functions.""" 897 - f = open(file, "w") 898 - f.write(header.format(brief='Generated bindings data header', 899 - group='oxr_api')) 900 - f.write(f''' 901 - #pragma once 902 614 903 - #include <stddef.h> 904 - 905 - #include "xrt/xrt_defines.h" 906 - 907 - 908 - #ifdef __cplusplus 909 - extern "C" {{ 910 - #endif 911 - 912 - typedef uint64_t XrPath; // OpenXR typedef 913 - typedef uint64_t XrVersion; // OpenXR typedef 914 - 915 - struct oxr_extension_status; 916 - 917 - #define OXR_BINDINGS_PROFILE_TEMPLATE_COUNT {len(b.profiles)} 918 - 919 - struct oxr_bindings_path_cache_element {{ 920 - //! Pointer to XrPath 921 - XrPath *path_cache; 922 - //! Pointer to char* 923 - const char **path_cache_name; 924 - }}; 925 - 926 - struct oxr_bindings_path_cache {{ 927 - // wrapped in a struct solely to reduce the C pointer soup 928 - struct oxr_bindings_path_cache_element path_cache[OXR_BINDINGS_PROFILE_TEMPLATE_COUNT]; 929 - }}; 930 - 931 - void oxr_get_interaction_profile_path_cache(const struct oxr_bindings_path_cache **out_path_cache); 932 - 933 - // clang-format off 934 - ''') 935 - 936 - fn_prefixes = ["_subpath", "_dpad_path", "_dpad_emulator"] 937 - for profile in b.profiles: 938 - for fn_suffix in fn_prefixes: 939 - f.write( 940 - f"\nbool\noxr_verify_{profile.validation_func_name}{fn_suffix}(const struct oxr_extension_status *extensions, XrVersion openxr_major_minor, const char *str, size_t length);\n") 941 - f.write(f'''\nvoid\noxr_verify_{profile.validation_func_name}_ext(const struct oxr_extension_status *extensions, XrVersion openxr_version, bool *out_supported, bool *out_enabled);\n''') 942 - 943 - f.write(f''' 944 - #define PATHS_PER_BINDING_TEMPLATE 16 945 - 946 - enum oxr_dpad_binding_point 947 - {{ 948 - \tOXR_DPAD_BINDING_POINT_NONE, 949 - \tOXR_DPAD_BINDING_POINT_UP, 950 - \tOXR_DPAD_BINDING_POINT_DOWN, 951 - \tOXR_DPAD_BINDING_POINT_LEFT, 952 - \tOXR_DPAD_BINDING_POINT_RIGHT, 953 - }}; 954 - 955 - struct dpad_emulation 956 - {{ 957 - \tconst char *subaction_path; 958 - \tconst char *paths[PATHS_PER_BINDING_TEMPLATE]; 959 - \tenum xrt_input_name position; 960 - \tenum xrt_input_name activate; // Can be zero 961 - }}; 962 - 963 - struct binding_template 964 - {{ 965 - \tconst char *subaction_path; 966 - \tconst char *steamvr_path; 967 - \tconst char *localized_name; 968 - \tconst char *paths[PATHS_PER_BINDING_TEMPLATE]; 969 - \tenum xrt_input_name input; 970 - \tenum xrt_input_name dpad_activate; 971 - \tenum xrt_output_name output; 972 - }}; 973 - 974 - typedef bool (*path_verify_fn_t)(const struct oxr_extension_status *extensions, XrVersion openxr_version, const char *, size_t); 975 - typedef void (*ext_verify_fn_t)(const struct oxr_extension_status *extensions, XrVersion openxr_version, bool *out_supported, bool *out_enabled); 976 - 977 - struct profile_template 978 - {{ 979 - \tenum xrt_device_name name; 980 - \tconst char *path; 981 - \tconst char *localized_name; 982 - \tconst char *steamvr_input_profile_path; 983 - \tconst char *steamvr_controller_type; 984 - \tstruct binding_template *bindings; 985 - \tsize_t binding_count; 986 - \tstruct dpad_emulation *dpads; 987 - \tsize_t dpad_count; 988 - \tstruct {{ 989 - \t\tstruct {{ 990 - \t\t\tuint32_t major; 991 - \t\t\tuint32_t minor; 992 - \t\t}} promoted; 993 - \t}} openxr_version; 994 - \t// Only valid after path cache entries are initialized via oxr_get_interaction_profile_path_cache. 995 - \tXrPath path_cache; 996 - 997 - \tpath_verify_fn_t subpath_fn; 998 - \tpath_verify_fn_t dpad_path_fn; 999 - \tpath_verify_fn_t dpad_emulator_fn; 1000 - \text_verify_fn_t ext_verify_fn; 1001 - \tconst char *extension_name; 1002 - }}; 1003 - 1004 - extern struct profile_template profile_templates[OXR_BINDINGS_PROFILE_TEMPLATE_COUNT]; 1005 - 1006 - ''') 1007 - 1008 - f.write('const char *\n') 1009 - f.write('xrt_input_name_string(enum xrt_input_name input);\n\n') 1010 - 1011 - f.write('enum xrt_input_name\n') 1012 - f.write('xrt_input_name_enum(const char *input);\n\n') 1013 - 1014 - f.write('const char *\n') 1015 - f.write('xrt_output_name_string(enum xrt_output_name output);\n\n') 1016 - 1017 - f.write('enum xrt_output_name\n') 1018 - f.write('xrt_output_name_enum(const char *output);\n\n') 1019 - 1020 - f.write("\n// clang-format on\n") 1021 - f.write(f''' 1022 - #ifdef __cplusplus 1023 - }} 1024 - #endif 1025 - ''') 1026 - 1027 - f.close() 615 + with open(file, "w") as f: 616 + filled = src.substitute( 617 + xrt_input_name_string_switch=xrt_input_name_string_switch, 618 + xrt_input_name_enum_content=xrt_input_name_enum_content, 619 + xrt_output_name_string_switch=xrt_output_name_string_switch, 620 + xrt_output_name_enum_content=xrt_output_name_enum_content 621 + ) 622 + f.write(filled) 1028 623 1029 624 1030 625 def main(): 1031 626 """Handle command line and generate a file.""" 1032 - parser = argparse.ArgumentParser(description='Bindings generator.') 627 + parser = argparse.ArgumentParser(description='Bindings helper generator.') 1033 628 parser.add_argument( 1034 629 'bindings', help='Bindings file to use') 1035 630 parser.add_argument( 631 + 'template', type=str, nargs='+', 632 + help='Template File') 633 + parser.add_argument( 1036 634 'output', type=str, nargs='+', 1037 635 help='Output file, uses the name to choose output type') 1038 636 args = parser.parse_args() ··· 1040 638 bindings = Bindings.load_and_parse(args.bindings) 1041 639 1042 640 for output in args.output: 1043 - if output.endswith("generated_bindings.c"): 1044 - generate_bindings_c(output, bindings) 1045 - if output.endswith("generated_bindings.h"): 1046 - generate_bindings_h(output, bindings) 641 + if output.endswith("generated_bindings_helpers.c"): 642 + generate_bindings_helpers_c(args.template[0], output, bindings) 1047 643 1048 644 1049 645 if __name__ == "__main__":
+1 -1
src/xrt/auxiliary/util/CMakeLists.txt
··· 122 122 xrt-external-nanopb 123 123 xrt-external-bcdec 124 124 aux-includes 125 - aux_generated_bindings 125 + aux_generated_bindings_helpers 126 126 aux_os 127 127 aux_math 128 128 )
+1 -1
src/xrt/auxiliary/util/u_config_json.c
··· 22 22 #include <string.h> 23 23 #include <sys/stat.h> 24 24 25 - #include "bindings/b_generated_bindings.h" 25 + #include "bindings/b_generated_bindings_helpers.h" 26 26 #include <assert.h> 27 27 28 28 DEBUG_GET_ONCE_OPTION(active_config, "P_OVERRIDE_ACTIVE_CONFIG", NULL)
+1 -1
src/xrt/drivers/CMakeLists.txt
··· 18 18 endif() 19 19 20 20 add_library(drv_cemu STATIC ht_ctrl_emu/ht_ctrl_emu.cpp ht_ctrl_emu/ht_ctrl_emu_interface.h) 21 - target_link_libraries(drv_cemu PRIVATE xrt-interfaces aux_generated_bindings aux_util) 21 + target_link_libraries(drv_cemu PRIVATE xrt-interfaces aux_generated_bindings_helpers aux_util) 22 22 list(APPEND ENABLED_HEADSET_DRIVERS drv_cemu) 23 23 24 24 if(XRT_BUILD_DRIVER_DAYDREAM)
+1 -1
src/xrt/state_trackers/gui/gui_scene_tracking_overrides.c
··· 23 23 #include "gui_common.h" 24 24 #include "gui_imgui.h" 25 25 26 - #include "bindings/b_generated_bindings.h" 26 + #include "bindings/b_generated_bindings_helpers.h" 27 27 28 28 struct gui_tracking_overrides 29 29 {
+8 -10
src/xrt/state_trackers/oxr/CMakeLists.txt
··· 5 5 # Main code 6 6 # 7 7 8 - add_library(oxr-interfaces INTERFACE) 9 - target_include_directories( 10 - oxr-interfaces INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} 11 - ) 12 - 13 - # aux_os, because oxr_objects.h includes os_threading.h 14 - target_link_libraries( 15 - oxr-interfaces INTERFACE xrt-external-openxr aux_os Vulkan::Vulkan xrt-external-renderdoc 16 - ) 8 + add_subdirectory(oxr_bindings) 17 9 18 10 add_library( 19 11 st_oxr STATIC ··· 53 45 oxr_two_call.h 54 46 oxr_verify.c 55 47 oxr_xdev.c 48 + ${CMAKE_CURRENT_BINARY_DIR}/oxr_bindings/b_oxr_generated_bindings.c 49 + ${CMAKE_CURRENT_BINARY_DIR}/oxr_bindings/b_oxr_generated_bindings.h 56 50 ) 51 + 52 + add_dependencies(st_oxr generate_oxr_bindings) 53 + # To enable finding oxr_objects.h in b_oxr_generated_bindings 54 + target_include_directories(st_oxr PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") 57 55 58 56 if(XRT_HAVE_VULKAN) 59 57 target_sources(st_oxr PRIVATE oxr_session_gfx_vk.c oxr_swapchain_vk.c oxr_vulkan.c) ··· 123 121 xrt-external-openxr 124 122 aux_util 125 123 aux_math 126 - aux_generated_bindings 124 + aux_generated_bindings_helpers 127 125 comp_client 128 126 aux-includes 129 127 PUBLIC aux_os
+1 -1
src/xrt/state_trackers/oxr/oxr_api_action.c
··· 23 23 24 24 #include <stdio.h> 25 25 26 - #include "bindings/b_generated_bindings.h" 26 + #include "oxr_bindings/b_oxr_generated_bindings.h" 27 27 28 28 29 29 /*
+1 -1
src/xrt/state_trackers/oxr/oxr_binding.c
··· 11 11 #include "util/u_misc.h" 12 12 13 13 #include "xrt/xrt_compiler.h" 14 - #include "bindings/b_generated_bindings.h" 14 + #include "oxr_bindings/b_oxr_generated_bindings.h" 15 15 16 16 #include "oxr_objects.h" 17 17 #include "oxr_logger.h"
+28
src/xrt/state_trackers/oxr/oxr_bindings/CMakeLists.txt
··· 1 + # Copyright 2019-2025, Collabora, Ltd. 2 + # SPDX-License-Identifier: BSL-1.0 3 + 4 + # Binding generation: pass filename to generate 5 + # Need to use ${CMAKE_COMMAND} to be able to set environment with -e 6 + # Need to use $<TARGET_FILE:${PYTHON_EXECUTABLE}> to unwrap the cmake target to be able to run with ${CMAKE_COMMAND} 7 + function(oxr_bindings_gen output) 8 + add_custom_command( 9 + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${output}" 10 + COMMAND 11 + ${CMAKE_COMMAND} -E env PYTHONPATH=${AUX_BINDINGS_DIR};$ENV{PYTHONPATH} 12 + $<TARGET_FILE:${PYTHON_EXECUTABLE}> 13 + ${CMAKE_CURRENT_SOURCE_DIR}/oxr_bindings.py 14 + ${AUX_BINDINGS_DIR}/bindings.json "${CMAKE_CURRENT_BINARY_DIR}/${output}" 15 + VERBATIM 16 + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/oxr_bindings.py 17 + ${AUX_BINDINGS_DIR}/bindings.json 18 + COMMENT "Generating ${output}" 19 + ) 20 + endfunction() 21 + 22 + oxr_bindings_gen(b_oxr_generated_bindings.h) 23 + oxr_bindings_gen(b_oxr_generated_bindings.c) 24 + 25 + add_custom_target( 26 + generate_oxr_bindings DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/b_oxr_generated_bindings.h" 27 + "${CMAKE_CURRENT_BINARY_DIR}/b_oxr_generated_bindings.c" 28 + )
+455
src/xrt/state_trackers/oxr/oxr_bindings/oxr_bindings.py
··· 1 + #!/usr/bin/env python3 2 + # Copyright 2020-2025, Collabora, Ltd. 3 + # Copyright 2024-2025, NVIDIA CORPORATION. 4 + # SPDX-License-Identifier: BSL-1.0 5 + """Generate OpenXR binding code using the auxiliary bindings scripts.""" 6 + 7 + from bindings import * 8 + 9 + import argparse 10 + from operator import attrgetter 11 + from string import Template 12 + 13 + def get_verify_switch_body(dict_of_lists, profile, profile_name, tab_char): 14 + """Generate function to check if a string is in a set of strings. 15 + Input is a dict where keys are length and 16 + the values are lists of strings of that length. And a suffix if any. 17 + Returns a list of lines.""" 18 + ret = [ f"{tab_char}\tswitch (length) {{" ] 19 + for length in sorted(dict_of_lists.keys()): 20 + ret += [ 21 + f'{tab_char}\tcase {str(length)}:', 22 + ] 23 + 24 + for i, path in enumerate(sorted(dict_of_lists[length])): 25 + if_or_else_if = 'if' if i == 0 else '} else if' 26 + ret += [ 27 + f'{tab_char}\t\t{if_or_else_if} (strcmp(str, "{path}") == 0) {{', 28 + f'{tab_char}\t\t\treturn true;', 29 + ] 30 + ret += [ 31 + f'{tab_char}\t\t}}', 32 + f'{tab_char}\t\tbreak;', 33 + ] 34 + ret += [ 35 + f'{tab_char}\tdefault: break;', 36 + f'{tab_char}\t}}', 37 + ] 38 + return ret 39 + 40 + def get_verify_func_switch(dict_of_lists, profile, profile_name, availability): 41 + """Generate function to check if a string is in a set of strings. 42 + Input is a dict where keys are length and 43 + the values are lists of strings of that length. And a suffix if any. 44 + Returns a list of lines.""" 45 + if len(dict_of_lists) == 0: 46 + return '' 47 + 48 + ret = [ 49 + f"\t// generated from: {profile_name}" 50 + ] 51 + 52 + # Example: pico neo 3 can be enabled by either enabling XR_BD_controller_interaction ext or using OpenXR 1.1+. 53 + # Disabling OXR_HAVE_BD_controller_interaction should NOT remove pico neo from OpenXR 1.1+ (it makes "exts->BD_controller_interaction" invalid C code). 54 + # Therefore separate code blocks for ext and version checks generated to avoid ifdef hell. 55 + feature_sets = sorted(availability.feature_sets, key=FeatureSet.as_tuple) 56 + for feature_set in feature_sets: 57 + requires_version = is_valid_version(feature_set.required_version) 58 + requires_extensions = bool(feature_set.required_extensions) 59 + 60 + tab_char = '' 61 + closing = [] 62 + 63 + if requires_version: 64 + tab_char += '\t' 65 + ret += [ 66 + f'{tab_char}if (openxr_version >= XR_MAKE_VERSION({feature_set.required_version["major"]}, {feature_set.required_version["minor"]}, 0)) {{', 67 + ] 68 + closing.append(f'{tab_char}}}\n') 69 + 70 + if requires_extensions: 71 + tab_char += '\t' 72 + exts = sorted(feature_set.required_extensions) 73 + ext_defines = ' && '.join(f'defined(OXR_HAVE_{ext})' for ext in exts) 74 + ret += [ 75 + f'#if {ext_defines}', 76 + f'{tab_char}if ('+' && '.join(f'exts->{ext}' for ext in exts)+') {', 77 + ] 78 + closing.append(f'{tab_char}}}\n#endif // {ext_defines}\n\n') 79 + 80 + ret += get_verify_switch_body(dict_of_lists, profile, profile_name, tab_char) 81 + 82 + ret += reversed(closing) 83 + 84 + return ret 85 + 86 + def get_verify_func_body(profile, dict_name, availability): 87 + """returns a list of lines""" 88 + ret = [] 89 + if profile is None or dict_name is None or len(dict_name) == 0: 90 + return ret 91 + ret += get_verify_func_switch(getattr( 92 + profile, dict_name), profile, profile.name, availability) 93 + if profile.parent_profiles is None: 94 + return ret 95 + for pp in sorted(profile.parent_profiles, key=attrgetter("name")): 96 + ret += get_verify_func_body(pp, dict_name, availability.intersection(pp.availability())) 97 + 98 + return ret 99 + 100 + 101 + def get_verify_func(profile, dict_name, suffix): 102 + """returns a list of lines""" 103 + name = f"oxr_verify_{profile.validation_func_name}{suffix}" 104 + 105 + ret = [ 106 + 'bool', 107 + '{name}(const struct oxr_extension_status *exts, XrVersion openxr_version, const char *str, size_t length)'.format(name=name), 108 + '{', 109 + 110 + *get_verify_func_body(profile, dict_name, profile.availability()), 111 + 112 + '\treturn false;', 113 + '}', 114 + '' 115 + ] 116 + 117 + return ret 118 + 119 + def get_verify_functions(profile): 120 + """returns a list of lines""" 121 + ret = [ 122 + *get_verify_func(profile, "subpaths_by_length", "_subpath"), 123 + *get_verify_func(profile, "dpad_paths_by_length", "_dpad_path"), 124 + *get_verify_func(profile, "dpad_emulators_by_length", "_dpad_emulator"), 125 + 126 + 'void', 127 + f'oxr_verify_{profile.validation_func_name}_ext(const struct oxr_extension_status *extensions, XrVersion openxr_version, bool *out_supported, bool *out_enabled)', 128 + '{', 129 + '', 130 + ] 131 + 132 + is_promoted = is_valid_version(profile.openxr_version_promoted) 133 + if is_promoted: 134 + ret += [ 135 + f'\tif (openxr_version >= XR_MAKE_VERSION({profile.openxr_version_promoted["major"]}, {profile.openxr_version_promoted["minor"]}, 0)) {{', 136 + '\t\t*out_supported = true;', 137 + '\t\t*out_enabled = true;', 138 + '\t\treturn;', 139 + '\t}', 140 + '', 141 + ] 142 + 143 + 144 + if profile.extension_name is not None: 145 + ret += [ 146 + f'#ifdef OXR_HAVE_{profile.extension_name}', 147 + '\t*out_supported = true;', 148 + f'\t*out_enabled = extensions->{profile.extension_name};', 149 + '#else', 150 + '\t*out_supported = false;', 151 + '\t*out_enabled = false;', 152 + f'#endif // OXR_HAVE_{profile.extension_name}', 153 + '}', 154 + '', 155 + ] 156 + 157 + else: 158 + ret += [ 159 + '\t*out_supported = true;', 160 + '\t*out_enabled = true;', 161 + '}', 162 + '', 163 + ] 164 + 165 + return ret 166 + 167 + 168 + def generate_bindings_c(file, b): 169 + """Generate the file to verify subpaths on a interaction profile.""" 170 + f = open(file, "w") 171 + 172 + wl(f, 173 + header.format(brief='Generated bindings data', group='oxr_main'), 174 + '#include "oxr_bindings/b_oxr_generated_bindings.h"', 175 + '#include <string.h>', 176 + '#include "oxr_objects.h"', 177 + '', 178 + '// clang-format off', 179 + '', 180 + 181 + # unpack list comprehension that put the list of returned lines from the inner loop into a new list 182 + *[verify_function_lines 183 + for profile in b.profiles 184 + for verify_function_lines in get_verify_functions(profile) 185 + ], 186 + 187 + f'\n\nstruct profile_template profile_templates[{len(b.profiles)}] = {{ // array of profile_template', 188 + ) 189 + 190 + 191 + for profile in b.profiles: 192 + hw_name = str(profile.name.split("/")[-1]) 193 + vendor_name = str(profile.name.split("/")[-2]) 194 + fname = vendor_name + "_" + hw_name + "_profile.json" 195 + controller_type = "monado_" + vendor_name + "_" + hw_name 196 + 197 + binding_count = len(profile.components) 198 + wl(f, 199 + f'\t{{ // profile_template', 200 + f'\t\t.name = {profile.monado_device_enum},', 201 + f'\t\t.path = "{profile.name}",', 202 + f'\t\t.localized_name = "{profile.localized_name}",', 203 + f'\t\t.steamvr_input_profile_path = "{fname}",', 204 + f'\t\t.steamvr_controller_type = "{controller_type}",', 205 + f'\t\t.binding_count = {binding_count},', 206 + f'\t\t.bindings = (struct binding_template[]){{ // array of binding_template', 207 + ) 208 + 209 + component: Component 210 + for idx, component in enumerate(profile.components): 211 + 212 + # @todo Doesn't handle pose yet. 213 + steamvr_path = component.steamvr_path 214 + if component.component_name in ["click", "touch", "force", "value", "proximity"]: 215 + steamvr_path += "/" + component.component_name 216 + 217 + wl(f, 218 + f'\t\t\t{{ // binding_template {idx}', 219 + f'\t\t\t\t.subaction_path = "{component.subaction_path}",', 220 + f'\t\t\t\t.steamvr_path = "{steamvr_path}",', 221 + f'\t\t\t\t.localized_name = "{component.subpath_localized_name}",', 222 + '', 223 + '\t\t\t\t.paths = { // array of paths', 224 + 225 + '\n'.join([f'\t\t\t\t\t"{path}",' for path in component.get_full_openxr_paths() ]), 226 + 227 + '\t\t\t\t\tNULL,', 228 + '\t\t\t\t}, // /array of paths', 229 + ) 230 + 231 + # print("component", component.__dict__) 232 + 233 + component_str = component.component_name 234 + 235 + # controllers can have input that we don't have bindings for 236 + if component.monado_binding: 237 + monado_binding = component.monado_binding 238 + 239 + # Input, dpad_activate and output default to 0. 240 + # If a binding specifies an actual binding value for them in json, those get overridden. 241 + input_value = '0' 242 + dpad_activate_value = '0' 243 + output_value = '0' 244 + 245 + if component.is_input() and monado_binding is not None: 246 + input_value = monado_binding 247 + 248 + if component.has_dpad_emulation() and "activate" in component.dpad_emulation: 249 + activate_component = find_component_in_list_by_name( 250 + component.dpad_emulation["activate"], profile.components, 251 + subaction_path=component.subaction_path, 252 + identifier_json_path=component.identifier_json_path) 253 + dpad_activate_value = activate_component.monado_binding 254 + 255 + if component.is_output() and monado_binding is not None: 256 + output_value = monado_binding 257 + 258 + wl(f, 259 + f'\t\t\t\t.input = {input_value},', 260 + f'\t\t\t\t.dpad_activate = {dpad_activate_value},', 261 + f'\t\t\t\t.output = {output_value},', 262 + ) 263 + 264 + wl(f, f'\t\t\t}}, // /binding_template {idx}') 265 + 266 + wl(f, '\t\t}, // /array of binding_template') 267 + 268 + dpads = [] 269 + for idx, identifier in enumerate(profile.identifiers): 270 + if identifier.dpad: 271 + dpads.append(identifier) 272 + 273 + # for identifier in dpads: 274 + # print(identifier.path, identifier.dpad_position_component) 275 + 276 + dpad_count = len(dpads) 277 + f.write(f'\t\t.dpad_count = {dpad_count},\n') 278 + if len(dpads) == 0: 279 + f.write(f'\t\t.dpads = NULL,\n') 280 + else: 281 + f.write( 282 + f'\t\t.dpads = (struct dpad_emulation[]){{ // array of dpad_emulation\n') 283 + for idx, identifier in enumerate(dpads): 284 + f.write('\t\t\t{\n') 285 + f.write(f'\t\t\t\t.subaction_path = "{identifier.subaction_path}",\n') 286 + f.write('\t\t\t\t.paths = {\n') 287 + for path in identifier.dpad.paths: 288 + f.write(f'\t\t\t\t\t"{path}",\n') 289 + f.write('\t\t\t\t},\n') 290 + f.write(f'\t\t\t\t.position = {identifier.dpad.position_component.monado_binding},\n') 291 + if identifier.dpad.activate_component: 292 + f.write(f'\t\t\t\t.activate = {identifier.dpad.activate_component.monado_binding},\n') 293 + else: 294 + f.write(f'\t\t\t\t.activate = 0') 295 + 296 + f.write('\t\t\t},\n') 297 + f.write('\t\t}, // /array of dpad_emulation\n') 298 + 299 + f.write(f'\t\t.openxr_version.promoted.major = {profile.openxr_version_promoted["major"]},\n') 300 + f.write(f'\t\t.openxr_version.promoted.minor = {profile.openxr_version_promoted["minor"]},\n') 301 + 302 + fn_prefixes = ["subpath", "dpad_path", "dpad_emulator"] 303 + for prefix in fn_prefixes: 304 + f.write(f'\t\t.{prefix}_fn = oxr_verify_{profile.validation_func_name}_{prefix},\n') 305 + f.write(f'\t\t.ext_verify_fn = oxr_verify_{profile.validation_func_name}_ext,\n') 306 + if profile.extension_name is None: 307 + f.write(f'\t\t.extension_name = NULL,\n') 308 + else: 309 + f.write(f'\t\t.extension_name = "{profile.extension_name}",\n') 310 + f.write('\t}, // /profile_template\n') 311 + 312 + f.write('}; // /array of profile_template\n\n') 313 + 314 + f.write("\n// clang-format on\n") 315 + 316 + f.close() 317 + 318 + 319 + H_TEMPLATE = Template("""$header 320 + 321 + #pragma once 322 + 323 + #include <stddef.h> 324 + #include "xrt/xrt_defines.h" 325 + 326 + #ifdef __cplusplus 327 + extern "C" { 328 + #endif 329 + 330 + typedef uint64_t XrPath; // OpenXR typedef' 331 + typedef uint64_t XrVersion; // OpenXR typedef' 332 + 333 + struct oxr_extension_status; 334 + 335 + #define OXR_BINDINGS_PROFILE_TEMPLATE_COUNT $template_count 336 + 337 + // clang-format off 338 + 339 + $verify_protos 340 + 341 + #define PATHS_PER_BINDING_TEMPLATE 16 342 + 343 + enum oxr_dpad_binding_point 344 + { 345 + \tOXR_DPAD_BINDING_POINT_NONE, 346 + \tOXR_DPAD_BINDING_POINT_UP, 347 + \tOXR_DPAD_BINDING_POINT_DOWN, 348 + \tOXR_DPAD_BINDING_POINT_LEFT, 349 + \tOXR_DPAD_BINDING_POINT_RIGHT, 350 + }; 351 + 352 + struct dpad_emulation 353 + { 354 + \tconst char *subaction_path; 355 + \tconst char *paths[PATHS_PER_BINDING_TEMPLATE]; 356 + \tenum xrt_input_name position; 357 + \tenum xrt_input_name activate; // Can be zero 358 + }; 359 + 360 + struct binding_template 361 + { 362 + \tconst char *subaction_path; 363 + \tconst char *steamvr_path; 364 + \tconst char *localized_name; 365 + \tconst char *paths[PATHS_PER_BINDING_TEMPLATE]; 366 + \tenum xrt_input_name input; 367 + \tenum xrt_input_name dpad_activate; 368 + \tenum xrt_output_name output; 369 + }; 370 + 371 + typedef bool (*path_verify_fn_t)(const struct oxr_extension_status *extensions, XrVersion openxr_version, const char *, size_t); 372 + typedef void (*ext_verify_fn_t)(const struct oxr_extension_status *extensions, XrVersion openxr_version, bool *out_supported, bool *out_enabled); 373 + 374 + struct profile_template 375 + { 376 + \tenum xrt_device_name name; 377 + \tconst char *path; 378 + \tconst char *localized_name; 379 + \tconst char *steamvr_input_profile_path; 380 + \tconst char *steamvr_controller_type; 381 + \tstruct binding_template *bindings; 382 + \tsize_t binding_count; 383 + \tstruct dpad_emulation *dpads; 384 + \tsize_t dpad_count; 385 + \tstruct { 386 + \t\tstruct { 387 + \t\t\tuint32_t major; 388 + \t\t\tuint32_t minor; 389 + \t\t} promoted; 390 + \t} openxr_version; 391 + \t//! path_cache is an INVALID value until oxr_instance_create initializes it. 392 + \tXrPath path_cache; 393 + 394 + \tpath_verify_fn_t subpath_fn; 395 + \tpath_verify_fn_t dpad_path_fn; 396 + \tpath_verify_fn_t dpad_emulator_fn; 397 + \text_verify_fn_t ext_verify_fn; 398 + \tconst char *extension_name; 399 + }; 400 + 401 + extern struct profile_template profile_templates[OXR_BINDINGS_PROFILE_TEMPLATE_COUNT]; 402 + // clang-format on") 403 + #ifdef __cplusplus 404 + } 405 + #endif 406 + """) 407 + 408 + def generate_bindings_h(file, b): 409 + """Generate header for the verify subpaths functions.""" 410 + 411 + verify_protos = [] 412 + fn_prefixes = ["_subpath", "_dpad_path", "_dpad_emulator"] 413 + for profile in b.profiles: 414 + for fn_suffix in fn_prefixes: 415 + verify_protos += [ 416 + 'bool', 417 + f'oxr_verify_{profile.validation_func_name}{fn_suffix}(const struct oxr_extension_status *extensions, XrVersion openxr_major_minor, const char *str, size_t length);', 418 + '', 419 + ] 420 + verify_protos += [ 421 + 'void', 422 + f'oxr_verify_{profile.validation_func_name}_ext(const struct oxr_extension_status *extensions, XrVersion openxr_version, bool *out_supported, bool *out_enabled);', 423 + '', 424 + ] 425 + 426 + with open(file, "w") as f: 427 + filled = H_TEMPLATE.substitute( 428 + header = header.format(brief='Generated bindings data', group='oxr_main'), 429 + template_count=len(b.profiles), 430 + verify_protos='\n'.join(verify_protos) 431 + ) 432 + f.write(filled) 433 + 434 + 435 + def main(): 436 + """Handle command line and generate a file.""" 437 + parser = argparse.ArgumentParser(description='OpenXR Bindings generator.') 438 + parser.add_argument( 439 + 'bindings', help='Bindings file to use') 440 + parser.add_argument( 441 + 'output', type=str, nargs='+', 442 + help='Output file, uses the name to choose output type') 443 + args = parser.parse_args() 444 + 445 + bindings = Bindings.load_and_parse(args.bindings) 446 + 447 + for output in args.output: 448 + if output.endswith("oxr_generated_bindings.c"): 449 + generate_bindings_c(output, bindings) 450 + if output.endswith("oxr_generated_bindings.h"): 451 + generate_bindings_h(output, bindings) 452 + 453 + 454 + if __name__ == "__main__": 455 + main()
+2 -1
src/xrt/state_trackers/oxr/oxr_input.c
··· 9 9 * @ingroup oxr_main 10 10 */ 11 11 12 - #include "bindings/b_generated_bindings.h" 12 + #include "b_generated_bindings_helpers.h" 13 + #include "oxr_bindings/b_oxr_generated_bindings.h" 13 14 #include "util/u_debug.h" 14 15 #include "util/u_time.h" 15 16 #include "util/u_misc.h"
+3 -6
src/xrt/state_trackers/oxr/oxr_instance.c
··· 9 9 * @ingroup oxr_main 10 10 */ 11 11 12 - #include "bindings/b_generated_bindings.h" 12 + #include "oxr_bindings/b_oxr_generated_bindings.h" 13 13 #include "xrt/xrt_config_os.h" 14 14 #include "xrt/xrt_config_build.h" 15 15 #include "xrt/xrt_instance.h" ··· 289 289 290 290 #undef CACHE_SUBACTION_PATHS 291 291 292 - const struct oxr_bindings_path_cache *path_cache; 293 - oxr_get_interaction_profile_path_cache(&path_cache); 294 - 295 - for (uint32_t i = 0; i < ARRAY_SIZE(path_cache->path_cache); i++) { 296 - cache_path(log, inst, *path_cache->path_cache[i].path_cache_name, path_cache->path_cache[i].path_cache); 292 + for (uint32_t i = 0; i < OXR_BINDINGS_PROFILE_TEMPLATE_COUNT; i++) { 293 + cache_path(log, inst, profile_templates[i].path, &profile_templates[i].path_cache); 297 294 } 298 295 299 296 // fill in our application info - @todo - replicate all createInfo
+9 -1
src/xrt/state_trackers/steamvr_drv/CMakeLists.txt
··· 1 1 # Copyright 2020-2021, Collabora, Ltd. 2 2 # SPDX-License-Identifier: BSL-1.0 3 3 4 + add_subdirectory(steamvr_bindings) 5 + 4 6 add_library(st_ovrd STATIC ovrd_driver.cpp ovrd_interface.h) 5 7 6 8 target_include_directories(st_ovrd INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 7 9 target_link_libraries( 8 - st_ovrd PRIVATE xrt-interfaces xrt-external-openvr aux_math aux_generated_bindings 10 + st_ovrd 11 + PRIVATE 12 + xrt-interfaces 13 + xrt-external-openvr 14 + aux_math 15 + aux_generated_bindings_helpers 16 + ovrd_generated_bindings 9 17 )
+1 -1
src/xrt/state_trackers/steamvr_drv/ovrd_driver.cpp
··· 38 38 #include "xrt/xrt_device.h" 39 39 #include "xrt/xrt_instance.h" 40 40 41 - #include "bindings/b_generated_bindings.h" 41 + #include "b_ovrd_generated_bindings.h" 42 42 } 43 43 #include "math/m_vec3.h" 44 44
+34
src/xrt/state_trackers/steamvr_drv/steamvr_bindings/CMakeLists.txt
··· 1 + # Copyright 2019-2025, Collabora, Ltd. 2 + # SPDX-License-Identifier: BSL-1.0 3 + 4 + # Binding generation: pass filename to generate 5 + # Need to use ${CMAKE_COMMAND} to be able to set environment with -e 6 + # Need to use $<TARGET_FILE:${PYTHON_EXECUTABLE}> to unwrap the cmake target to be able to run with ${CMAKE_COMMAND} 7 + function(ovrd_bindings_gen output) 8 + add_custom_command( 9 + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${output}" 10 + COMMAND 11 + ${CMAKE_COMMAND} -E env PYTHONPATH=${AUX_BINDINGS_DIR};$ENV{PYTHONPATH} 12 + $<TARGET_FILE:${PYTHON_EXECUTABLE}> 13 + ${CMAKE_CURRENT_SOURCE_DIR}/ovrd_bindings.py 14 + ${AUX_BINDINGS_DIR}/bindings.json "${CMAKE_CURRENT_BINARY_DIR}/${output}" 15 + VERBATIM 16 + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ovrd_bindings.py 17 + ${AUX_BINDINGS_DIR}/bindings.json 18 + COMMENT "Generating ${output}" 19 + ) 20 + endfunction() 21 + 22 + ovrd_bindings_gen(b_ovrd_generated_bindings.h) 23 + ovrd_bindings_gen(b_ovrd_generated_bindings.c) 24 + 25 + # Bindings library. 26 + add_library( 27 + ovrd_generated_bindings STATIC ${CMAKE_CURRENT_BINARY_DIR}/b_ovrd_generated_bindings.c 28 + ${CMAKE_CURRENT_BINARY_DIR}/b_ovrd_generated_bindings.h 29 + ) 30 + 31 + target_link_libraries(ovrd_generated_bindings PRIVATE xrt-interfaces) 32 + 33 + # So that linking ovrd_generated_bindings makes it possible to include the generated header 34 + target_include_directories(ovrd_generated_bindings PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
+237
src/xrt/state_trackers/steamvr_drv/steamvr_bindings/ovrd_bindings.py
··· 1 + #!/usr/bin/env python3 2 + # Copyright 2020-2025, Collabora, Ltd. 3 + # Copyright 2024-2025, NVIDIA CORPORATION. 4 + # SPDX-License-Identifier: BSL-1.0 5 + """Generate SteamVR binding code using the auxiliary bindings scripts.""" 6 + 7 + from bindings import * 8 + 9 + import argparse 10 + from string import Template 11 + 12 + def ovrd_generate_bindings_c(file, b): 13 + """Generate the file with templates for interaction profiles.""" 14 + f = open(file, "w") 15 + 16 + wl(f, 17 + header.format(brief='Generated bindings data', group='oxr_main'), 18 + '#include "b_ovrd_generated_bindings.h"', 19 + '#include <string.h>', 20 + '', 21 + '// clang-format off', 22 + '', 23 + f'\n\nstruct profile_template profile_templates[{len(b.profiles)}] = {{ // array of profile_template', 24 + ) 25 + 26 + 27 + for profile in b.profiles: 28 + hw_name = str(profile.name.split("/")[-1]) 29 + vendor_name = str(profile.name.split("/")[-2]) 30 + fname = vendor_name + "_" + hw_name + "_profile.json" 31 + controller_type = "monado_" + vendor_name + "_" + hw_name 32 + 33 + binding_count = len(profile.components) 34 + wl(f, 35 + f'\t{{ // profile_template', 36 + f'\t\t.name = {profile.monado_device_enum},', 37 + f'\t\t.path = "{profile.name}",', 38 + f'\t\t.localized_name = "{profile.localized_name}",', 39 + f'\t\t.steamvr_input_profile_path = "{fname}",', 40 + f'\t\t.steamvr_controller_type = "{controller_type}",', 41 + f'\t\t.binding_count = {binding_count},', 42 + f'\t\t.bindings = (struct binding_template[]){{ // array of binding_template', 43 + ) 44 + 45 + component: Component 46 + for idx, component in enumerate(profile.components): 47 + 48 + # @todo Doesn't handle pose yet. 49 + steamvr_path = component.steamvr_path 50 + if component.component_name in ["click", "touch", "force", "value", "proximity"]: 51 + steamvr_path += "/" + component.component_name 52 + 53 + wl(f, 54 + f'\t\t\t{{ // binding_template {idx}', 55 + f'\t\t\t\t.subaction_path = "{component.subaction_path}",', 56 + f'\t\t\t\t.steamvr_path = "{steamvr_path}",', 57 + f'\t\t\t\t.localized_name = "{component.subpath_localized_name}",', 58 + '', 59 + '\t\t\t\t.paths = { // array of paths', 60 + 61 + '\n'.join([f'\t\t\t\t\t"{path}",' for path in component.get_full_openxr_paths() ]), 62 + 63 + '\t\t\t\t\tNULL,', 64 + '\t\t\t\t}, // /array of paths', 65 + ) 66 + 67 + # print("component", component.__dict__) 68 + 69 + component_str = component.component_name 70 + 71 + # controllers can have input that we don't have bindings for 72 + if component.monado_binding: 73 + monado_binding = component.monado_binding 74 + 75 + if component.is_input() and monado_binding is not None: 76 + f.write(f'\t\t\t\t.input = {monado_binding},\n') 77 + else: 78 + f.write(f'\t\t\t\t.input = 0,\n') 79 + 80 + if component.has_dpad_emulation() and "activate" in component.dpad_emulation: 81 + activate_component = find_component_in_list_by_name( 82 + component.dpad_emulation["activate"], profile.components, 83 + subaction_path=component.subaction_path, 84 + identifier_json_path=component.identifier_json_path) 85 + f.write( 86 + f'\t\t\t\t.dpad_activate = {activate_component.monado_binding},\n') 87 + else: 88 + f.write(f'\t\t\t\t.dpad_activate = 0,\n') 89 + 90 + if component.is_output() and monado_binding is not None: 91 + f.write(f'\t\t\t\t.output = {monado_binding},\n') 92 + else: 93 + f.write(f'\t\t\t\t.output = 0,\n') 94 + f.write(f'\t\t\t}}, // /binding_template {idx}\n') 95 + 96 + f.write('\t\t}, // /array of binding_template\n') 97 + 98 + dpads = [] 99 + for idx, identifier in enumerate(profile.identifiers): 100 + if identifier.dpad: 101 + dpads.append(identifier) 102 + 103 + # for identifier in dpads: 104 + # print(identifier.path, identifier.dpad_position_component) 105 + 106 + dpad_count = len(dpads) 107 + f.write(f'\t\t.dpad_count = {dpad_count},\n') 108 + if len(dpads) == 0: 109 + f.write(f'\t\t.dpads = NULL,\n') 110 + else: 111 + f.write( 112 + f'\t\t.dpads = (struct dpad_emulation[]){{ // array of dpad_emulation\n') 113 + for idx, identifier in enumerate(dpads): 114 + f.write('\t\t\t{\n') 115 + f.write(f'\t\t\t\t.subaction_path = "{identifier.subaction_path}",\n') 116 + f.write('\t\t\t\t.paths = {\n') 117 + for path in identifier.dpad.paths: 118 + f.write(f'\t\t\t\t\t"{path}",\n') 119 + f.write('\t\t\t\t},\n') 120 + f.write(f'\t\t\t\t.position = {identifier.dpad.position_component.monado_binding},\n') 121 + if identifier.dpad.activate_component: 122 + f.write(f'\t\t\t\t.activate = {identifier.dpad.activate_component.monado_binding},\n') 123 + else: 124 + f.write(f'\t\t\t\t.activate = 0') 125 + 126 + f.write('\t\t\t},\n') 127 + f.write('\t\t}, // /array of dpad_emulation\n') 128 + 129 + f.write('\t}, // /profile_template\n') 130 + 131 + f.write('}; // /array of profile_template\n\n') 132 + 133 + f.write("\n// clang-format on\n") 134 + 135 + f.close() 136 + 137 + # Reduced version of OpenXR templates, without the OpenXR bits the SteamVR plugin does not need 138 + H_TEMPLATE = Template("""$header 139 + 140 + #pragma once 141 + 142 + #include <stddef.h> 143 + #include "xrt/xrt_defines.h" 144 + 145 + #ifdef __cplusplus 146 + extern "C" { 147 + #endif 148 + 149 + #define OXR_BINDINGS_PROFILE_TEMPLATE_COUNT $template_count 150 + 151 + // clang-format off 152 + 153 + #define PATHS_PER_BINDING_TEMPLATE 16 154 + 155 + enum oxr_dpad_binding_point 156 + { 157 + \tOXR_DPAD_BINDING_POINT_NONE, 158 + \tOXR_DPAD_BINDING_POINT_UP, 159 + \tOXR_DPAD_BINDING_POINT_DOWN, 160 + \tOXR_DPAD_BINDING_POINT_LEFT, 161 + \tOXR_DPAD_BINDING_POINT_RIGHT, 162 + }; 163 + 164 + struct dpad_emulation 165 + { 166 + \tconst char *subaction_path; 167 + \tconst char *paths[PATHS_PER_BINDING_TEMPLATE]; 168 + \tenum xrt_input_name position; 169 + \tenum xrt_input_name activate; // Can be zero 170 + }; 171 + 172 + struct binding_template 173 + { 174 + \tconst char *subaction_path; 175 + \tconst char *steamvr_path; 176 + \tconst char *localized_name; 177 + \tconst char *paths[PATHS_PER_BINDING_TEMPLATE]; 178 + \tenum xrt_input_name input; 179 + \tenum xrt_input_name dpad_activate; 180 + \tenum xrt_output_name output; 181 + }; 182 + 183 + struct profile_template 184 + { 185 + \tenum xrt_device_name name; 186 + \tconst char *path; 187 + \tconst char *localized_name; 188 + \tconst char *steamvr_input_profile_path; 189 + \tconst char *steamvr_controller_type; 190 + \tstruct binding_template *bindings; 191 + \tsize_t binding_count; 192 + \tstruct dpad_emulation *dpads; 193 + \tsize_t dpad_count; 194 + 195 + \tconst char *extension_name; 196 + }; 197 + 198 + extern struct profile_template profile_templates[OXR_BINDINGS_PROFILE_TEMPLATE_COUNT]; 199 + // clang-format on") 200 + #ifdef __cplusplus 201 + } 202 + #endif 203 + """) 204 + 205 + 206 + def ovrd_generate_bindings_h(file, b): 207 + """Generate the header with templates for interaction profiles.""" 208 + 209 + with open(file, "w") as f: 210 + filled = H_TEMPLATE.substitute( 211 + header = header.format(brief='Generated bindings data', group='oxr_main'), 212 + template_count=len(b.profiles), 213 + ) 214 + f.write(filled) 215 + 216 + 217 + def main(): 218 + """Handle command line and generate a file.""" 219 + parser = argparse.ArgumentParser(description='SteamVR Bindings generator.') 220 + parser.add_argument( 221 + 'bindings', help='Bindings file to use') 222 + parser.add_argument( 223 + 'output', type=str, nargs='+', 224 + help='Output file, uses the name to choose output type') 225 + args = parser.parse_args() 226 + 227 + bindings = Bindings.load_and_parse(args.bindings) 228 + 229 + for output in args.output: 230 + if output.endswith("ovrd_generated_bindings.c"): 231 + ovrd_generate_bindings_c(output, bindings) 232 + if output.endswith("ovrd_generated_bindings.h"): 233 + ovrd_generate_bindings_h(output, bindings) 234 + 235 + 236 + if __name__ == "__main__": 237 + main()