The open source OpenXR runtime
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

a/bindings: Include requirements of parent profile

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

+139 -24
+139 -24
src/xrt/auxiliary/bindings/bindings.py
··· 262 262 self.components = component_list 263 263 self.dpad = dpad 264 264 return 265 + 266 + 267 + class FeatureSet: 268 + """An AND of requirements (versions and/or extensions) under which a binding becomes available""" 269 + 270 + def __init__(self, required_extensions=None, required_version=None): 271 + self.required_extensions = frozenset(required_extensions if required_extensions is not None else []) 272 + self.required_version = required_version if required_version is not None else {"major": "0", "minor": "0"} 273 + 274 + def as_tuple(self): 275 + return (self.required_version["major"], 276 + self.required_version["minor"], 277 + sorted(self.required_extensions)) 278 + 279 + def __str__(self) -> str: 280 + return f"{self.as_tuple()}" 281 + 282 + def is_more_restrictive_than(self, other): 283 + if other.required_extensions.issuperset(self.required_extensions): 284 + # other requires extensions we don't 285 + return False 286 + if check_promoted(other.required_version) and not check_promoted(self.required_version): 287 + # other bounds version, we don't 288 + return False 289 + if check_promoted(other.required_version) and check_promoted(self.required_version): 290 + if other.required_version["major"] != self.required_version["major"]: 291 + # different major versions - not fully implemented, but this seems right 292 + return False 293 + if int(other.required_version["minor"]) > int(self.required_version["minor"]): 294 + # other has a higher lower bound on minor version than us 295 + return False 296 + 297 + return True 298 + 299 + def and_also(self, other): 300 + result = copy.deepcopy(self) 301 + result.required_extensions = self.required_extensions | other.required_extensions 302 + if not check_promoted(result.required_version): 303 + result.required_version = other.required_version 304 + elif check_promoted(other.required_version): 305 + if result.required_version["major"] != other.required_version["major"]: 306 + raise NotImplementedError("Major version mismatch not handled") 307 + if int(result.required_version["minor"]) < int(other.required_version["minor"]): 308 + result.required_version["minor"] = other.required_version["minor"] 309 + 310 + return result 311 + 312 + 313 + class Availability: 314 + """An OR of FeatureSets, where any one of them being satisfied means a binding becomes available""" 315 + 316 + def __init__(self, feature_sets, optimize=True): 317 + if not optimize: 318 + self.feature_sets = set(feature_sets) 319 + return 320 + 321 + self.feature_sets = set() 322 + for feature_set in feature_sets: 323 + self.add_in_place(feature_set) 324 + 325 + def __str__(self) -> str: 326 + return f"{[str(fs) for fs in sorted(self.feature_sets, key=FeatureSet.as_tuple)]}" 327 + 328 + """Add an additional way for this availability to be satisfied""" 329 + def add_in_place(self, new_feature_set): 330 + for existing_feature in list(self.feature_sets): 331 + if existing_feature.is_more_restrictive_than(new_feature_set): 332 + self.feature_sets.remove(existing_feature) 333 + elif new_feature_set.is_more_restrictive_than(existing_feature): 334 + return 335 + self.feature_sets.add(new_feature_set) 336 + 337 + """Add an additional restriction to all feature sets""" 338 + def intersect_with_feature(self, feature_set): 339 + result = Availability(feature_sets=[]) 340 + for existing_feature in self.feature_sets: 341 + result.add_in_place(existing_feature.and_also(feature_set)) 342 + return result 343 + 344 + """Combine two availabilities into one that is satisfied if either is""" 345 + def union(self, other): 346 + result = copy.deepcopy(self) 347 + for feature_set in other.feature_sets: 348 + result.add_in_place(feature_set) 349 + 350 + """Combine two availabilities into one that is satisfied if both are. 351 + Note that this acts as a cartesian product followed by an at least n^2 op on that. 352 + """ 353 + def intersection(self, other): 354 + features = [] 355 + for feature_set in other.feature_sets: 356 + inter = self.intersect_with_feature(feature_set) 357 + features.extend(inter.feature_sets) 358 + 359 + return Availability(features) 265 360 266 361 267 362 class Profile: ··· 368 463 self.components += identifier.components 369 464 self.components = sorted(self.components, key=attrgetter("steamvr_path")) 370 465 466 + def availability(self): 467 + result = Availability(feature_sets=[]) 468 + has_requirements = False 469 + if check_promoted(self.openxr_version_promoted): 470 + result.add_in_place(FeatureSet(required_version=self.openxr_version_promoted)) 471 + has_requirements = True 472 + if self.extension_name is not None: 473 + result.add_in_place(FeatureSet(required_extensions=[self.extension_name])) 474 + has_requirements = True 475 + if not has_requirements: 476 + result.add_in_place(FeatureSet()) 477 + 478 + return result 479 + 371 480 372 481 oxr_verify_extension_status_struct_name = "oxr_verify_extension_status" 373 482 ··· 456 565 457 566 def check_promoted(openxr_version_promoted): 458 567 # If required version is 0.0, we can skip checking that the instance uses a more recent version 459 - return openxr_version_promoted is not None and openxr_version_promoted["major"] != '0' and openxr_version_promoted["minor"] != '0' 568 + return openxr_version_promoted is not None and not (openxr_version_promoted["major"] == '0' and openxr_version_promoted["minor"] == '0') 460 569 461 - def write_verify_switch_body(f, dict_of_lists, profile, profile_name, ext_name, tab_char): 570 + def write_verify_switch_body(f, dict_of_lists, profile, profile_name, tab_char): 462 571 """Generate function to check if a string is in a set of strings. 463 572 Input is a file to write the code into, a dict where keys are length and 464 573 the values are lists of strings of that length. And a suffix if any.""" ··· 470 579 f.write(f"{{\n{tab_char}\t\t\tbreak;\n{tab_char}\t\t}}\n") 471 580 f.write(f"{tab_char}\tdefault: break;\n{tab_char}\t}}\n") 472 581 473 - def write_verify_func_switch(f, dict_of_lists, profile, profile_name, ext_name): 582 + def write_verify_func_switch(f, dict_of_lists, profile, profile_name, availability): 474 583 """Generate function to check if a string is in a set of strings. 475 584 Input is a file to write the code into, a dict where keys are length and 476 585 the values are lists of strings of that length. And a suffix if any.""" 477 586 if len(dict_of_lists) == 0: 478 587 return 479 588 480 - is_ext = ext_name is not None and len(ext_name) > 0 481 - is_promoted = check_promoted(profile.openxr_version_promoted) 482 - 483 589 f.write(f"\t// generated from: {profile_name}\n") 484 590 485 591 # Example: pico neo 3 can be enabled by either enabling XR_BD_controller_interaction ext or using OpenXR 1.1+. 486 592 # Disabling OXR_HAVE_BD_controller_interaction should NOT remove pico neo from OpenXR 1.1+ (it makes "exts->BD_controller_interaction" invalid C code). 487 593 # Therefore separate code blocks for ext and version checks generated to avoid ifdef hell. 488 - if is_ext: 489 - f.write(f'#ifdef OXR_HAVE_{profile.extension_name}\n') 490 - f.write(f"\tif (exts->{ext_name}) {{\n") 491 - write_verify_switch_body(f, dict_of_lists, profile, profile_name, ext_name, '\t') 492 - f.write("\t}\n") 493 - f.write(f'#endif // OXR_HAVE_{profile.extension_name}\n') 594 + feature_sets = sorted(availability.feature_sets, key=FeatureSet.as_tuple) 595 + for feature_set in feature_sets: 596 + requires_version = check_promoted(feature_set.required_version) 597 + requires_extensions = bool(feature_set.required_extensions) 598 + 599 + tab_char = '' 600 + closing = [] 494 601 495 - # The split into "is_promoted" and "not is_ext and not is_promoted" cases is not strictly necessary as we could generate the version check for both cases. 496 - # For the "not is_ext and not is_promoted" case this would generate "if (openxr_version >= XR_MAKE_VERSION(0, 0, 0))", which we avoid doing here by this split. 497 - if is_promoted: 498 - f.write(f'\tif (openxr_version >= XR_MAKE_VERSION({profile.openxr_version_promoted["major"]}, {profile.openxr_version_promoted["minor"]}, 0)) {{\n') 499 - write_verify_switch_body(f, dict_of_lists, profile, profile_name, ext_name, '\t') 500 - f.write("\t}\n") 602 + if requires_version: 603 + tab_char += '\t' 604 + f.write(f'{tab_char}if (openxr_version >= XR_MAKE_VERSION({feature_set.required_version["major"]}, {feature_set.required_version["minor"]}, 0)) {{\n') 605 + closing.append(f'{tab_char}}}\n') 501 606 502 - if not is_ext and not is_promoted: 503 - write_verify_switch_body(f, dict_of_lists, profile, profile_name, ext_name, '') 607 + if requires_extensions: 608 + tab_char += '\t' 609 + exts = sorted(feature_set.required_extensions) 610 + ext_defines = ' && '.join(f'defined(OXR_HAVE_{ext})' for ext in exts) 611 + f.write(f'#if {ext_defines}\n') 612 + f.write(f'{tab_char}if ('+' && '.join(f'exts->{ext}' for ext in exts)+') {\n') 613 + closing.append(f'{tab_char}}}\n#endif // {ext_defines}\n') 504 614 505 - def write_verify_func_body(f, profile, dict_name): 615 + write_verify_switch_body(f, dict_of_lists, profile, profile_name, tab_char) 616 + 617 + for closer in reversed(closing): 618 + f.write(closer) 619 + 620 + def write_verify_func_body(f, profile, dict_name, availability): 506 621 if profile is None or dict_name is None or len(dict_name) == 0: 507 622 return 508 623 write_verify_func_switch(f, getattr( 509 - profile, dict_name), profile, profile.name, profile.extension_name) 624 + profile, dict_name), profile, profile.name, availability) 510 625 if profile.parent_profiles is None: 511 626 return 512 627 for pp in sorted(profile.parent_profiles, key=attrgetter("name")): 513 - write_verify_func_body(f, pp, dict_name) 628 + write_verify_func_body(f, pp, dict_name, availability.intersection(pp.availability())) 514 629 515 630 516 631 def write_verify_func(f, profile, dict_name, suffix): 517 632 write_verify_func_begin( 518 633 f, f"oxr_verify_{profile.validation_func_name}{suffix}") 519 - write_verify_func_body(f, profile, dict_name) 634 + write_verify_func_body(f, profile, dict_name, profile.availability()) 520 635 write_verify_func_end(f) 521 636 522 637