···262262 self.components = component_list
263263 self.dpad = dpad
264264 return
265265+266266+267267+class FeatureSet:
268268+ """An AND of requirements (versions and/or extensions) under which a binding becomes available"""
269269+270270+ def __init__(self, required_extensions=None, required_version=None):
271271+ self.required_extensions = frozenset(required_extensions if required_extensions is not None else [])
272272+ self.required_version = required_version if required_version is not None else {"major": "0", "minor": "0"}
273273+274274+ def as_tuple(self):
275275+ return (self.required_version["major"],
276276+ self.required_version["minor"],
277277+ sorted(self.required_extensions))
278278+279279+ def __str__(self) -> str:
280280+ return f"{self.as_tuple()}"
281281+282282+ def is_more_restrictive_than(self, other):
283283+ if other.required_extensions.issuperset(self.required_extensions):
284284+ # other requires extensions we don't
285285+ return False
286286+ if check_promoted(other.required_version) and not check_promoted(self.required_version):
287287+ # other bounds version, we don't
288288+ return False
289289+ if check_promoted(other.required_version) and check_promoted(self.required_version):
290290+ if other.required_version["major"] != self.required_version["major"]:
291291+ # different major versions - not fully implemented, but this seems right
292292+ return False
293293+ if int(other.required_version["minor"]) > int(self.required_version["minor"]):
294294+ # other has a higher lower bound on minor version than us
295295+ return False
296296+297297+ return True
298298+299299+ def and_also(self, other):
300300+ result = copy.deepcopy(self)
301301+ result.required_extensions = self.required_extensions | other.required_extensions
302302+ if not check_promoted(result.required_version):
303303+ result.required_version = other.required_version
304304+ elif check_promoted(other.required_version):
305305+ if result.required_version["major"] != other.required_version["major"]:
306306+ raise NotImplementedError("Major version mismatch not handled")
307307+ if int(result.required_version["minor"]) < int(other.required_version["minor"]):
308308+ result.required_version["minor"] = other.required_version["minor"]
309309+310310+ return result
311311+312312+313313+class Availability:
314314+ """An OR of FeatureSets, where any one of them being satisfied means a binding becomes available"""
315315+316316+ def __init__(self, feature_sets, optimize=True):
317317+ if not optimize:
318318+ self.feature_sets = set(feature_sets)
319319+ return
320320+321321+ self.feature_sets = set()
322322+ for feature_set in feature_sets:
323323+ self.add_in_place(feature_set)
324324+325325+ def __str__(self) -> str:
326326+ return f"{[str(fs) for fs in sorted(self.feature_sets, key=FeatureSet.as_tuple)]}"
327327+328328+ """Add an additional way for this availability to be satisfied"""
329329+ def add_in_place(self, new_feature_set):
330330+ for existing_feature in list(self.feature_sets):
331331+ if existing_feature.is_more_restrictive_than(new_feature_set):
332332+ self.feature_sets.remove(existing_feature)
333333+ elif new_feature_set.is_more_restrictive_than(existing_feature):
334334+ return
335335+ self.feature_sets.add(new_feature_set)
336336+337337+ """Add an additional restriction to all feature sets"""
338338+ def intersect_with_feature(self, feature_set):
339339+ result = Availability(feature_sets=[])
340340+ for existing_feature in self.feature_sets:
341341+ result.add_in_place(existing_feature.and_also(feature_set))
342342+ return result
343343+344344+ """Combine two availabilities into one that is satisfied if either is"""
345345+ def union(self, other):
346346+ result = copy.deepcopy(self)
347347+ for feature_set in other.feature_sets:
348348+ result.add_in_place(feature_set)
349349+350350+ """Combine two availabilities into one that is satisfied if both are.
351351+ Note that this acts as a cartesian product followed by an at least n^2 op on that.
352352+ """
353353+ def intersection(self, other):
354354+ features = []
355355+ for feature_set in other.feature_sets:
356356+ inter = self.intersect_with_feature(feature_set)
357357+ features.extend(inter.feature_sets)
358358+359359+ return Availability(features)
265360266361267362class Profile:
···368463 self.components += identifier.components
369464 self.components = sorted(self.components, key=attrgetter("steamvr_path"))
370465466466+ def availability(self):
467467+ result = Availability(feature_sets=[])
468468+ has_requirements = False
469469+ if check_promoted(self.openxr_version_promoted):
470470+ result.add_in_place(FeatureSet(required_version=self.openxr_version_promoted))
471471+ has_requirements = True
472472+ if self.extension_name is not None:
473473+ result.add_in_place(FeatureSet(required_extensions=[self.extension_name]))
474474+ has_requirements = True
475475+ if not has_requirements:
476476+ result.add_in_place(FeatureSet())
477477+478478+ return result
479479+371480372481oxr_verify_extension_status_struct_name = "oxr_verify_extension_status"
373482···456565457566def check_promoted(openxr_version_promoted):
458567 # If required version is 0.0, we can skip checking that the instance uses a more recent version
459459- return openxr_version_promoted is not None and openxr_version_promoted["major"] != '0' and openxr_version_promoted["minor"] != '0'
568568+ return openxr_version_promoted is not None and not (openxr_version_promoted["major"] == '0' and openxr_version_promoted["minor"] == '0')
460569461461-def write_verify_switch_body(f, dict_of_lists, profile, profile_name, ext_name, tab_char):
570570+def write_verify_switch_body(f, dict_of_lists, profile, profile_name, tab_char):
462571 """Generate function to check if a string is in a set of strings.
463572 Input is a file to write the code into, a dict where keys are length and
464573 the values are lists of strings of that length. And a suffix if any."""
···470579 f.write(f"{{\n{tab_char}\t\t\tbreak;\n{tab_char}\t\t}}\n")
471580 f.write(f"{tab_char}\tdefault: break;\n{tab_char}\t}}\n")
472581473473-def write_verify_func_switch(f, dict_of_lists, profile, profile_name, ext_name):
582582+def write_verify_func_switch(f, dict_of_lists, profile, profile_name, availability):
474583 """Generate function to check if a string is in a set of strings.
475584 Input is a file to write the code into, a dict where keys are length and
476585 the values are lists of strings of that length. And a suffix if any."""
477586 if len(dict_of_lists) == 0:
478587 return
479588480480- is_ext = ext_name is not None and len(ext_name) > 0
481481- is_promoted = check_promoted(profile.openxr_version_promoted)
482482-483589 f.write(f"\t// generated from: {profile_name}\n")
484590485591 # Example: pico neo 3 can be enabled by either enabling XR_BD_controller_interaction ext or using OpenXR 1.1+.
486592 # Disabling OXR_HAVE_BD_controller_interaction should NOT remove pico neo from OpenXR 1.1+ (it makes "exts->BD_controller_interaction" invalid C code).
487593 # Therefore separate code blocks for ext and version checks generated to avoid ifdef hell.
488488- if is_ext:
489489- f.write(f'#ifdef OXR_HAVE_{profile.extension_name}\n')
490490- f.write(f"\tif (exts->{ext_name}) {{\n")
491491- write_verify_switch_body(f, dict_of_lists, profile, profile_name, ext_name, '\t')
492492- f.write("\t}\n")
493493- f.write(f'#endif // OXR_HAVE_{profile.extension_name}\n')
594594+ feature_sets = sorted(availability.feature_sets, key=FeatureSet.as_tuple)
595595+ for feature_set in feature_sets:
596596+ requires_version = check_promoted(feature_set.required_version)
597597+ requires_extensions = bool(feature_set.required_extensions)
598598+599599+ tab_char = ''
600600+ closing = []
494601495495- # 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.
496496- # 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.
497497- if is_promoted:
498498- f.write(f'\tif (openxr_version >= XR_MAKE_VERSION({profile.openxr_version_promoted["major"]}, {profile.openxr_version_promoted["minor"]}, 0)) {{\n')
499499- write_verify_switch_body(f, dict_of_lists, profile, profile_name, ext_name, '\t')
500500- f.write("\t}\n")
602602+ if requires_version:
603603+ tab_char += '\t'
604604+ f.write(f'{tab_char}if (openxr_version >= XR_MAKE_VERSION({feature_set.required_version["major"]}, {feature_set.required_version["minor"]}, 0)) {{\n')
605605+ closing.append(f'{tab_char}}}\n')
501606502502- if not is_ext and not is_promoted:
503503- write_verify_switch_body(f, dict_of_lists, profile, profile_name, ext_name, '')
607607+ if requires_extensions:
608608+ tab_char += '\t'
609609+ exts = sorted(feature_set.required_extensions)
610610+ ext_defines = ' && '.join(f'defined(OXR_HAVE_{ext})' for ext in exts)
611611+ f.write(f'#if {ext_defines}\n')
612612+ f.write(f'{tab_char}if ('+' && '.join(f'exts->{ext}' for ext in exts)+') {\n')
613613+ closing.append(f'{tab_char}}}\n#endif // {ext_defines}\n')
504614505505-def write_verify_func_body(f, profile, dict_name):
615615+ write_verify_switch_body(f, dict_of_lists, profile, profile_name, tab_char)
616616+617617+ for closer in reversed(closing):
618618+ f.write(closer)
619619+620620+def write_verify_func_body(f, profile, dict_name, availability):
506621 if profile is None or dict_name is None or len(dict_name) == 0:
507622 return
508623 write_verify_func_switch(f, getattr(
509509- profile, dict_name), profile, profile.name, profile.extension_name)
624624+ profile, dict_name), profile, profile.name, availability)
510625 if profile.parent_profiles is None:
511626 return
512627 for pp in sorted(profile.parent_profiles, key=attrgetter("name")):
513513- write_verify_func_body(f, pp, dict_name)
628628+ write_verify_func_body(f, pp, dict_name, availability.intersection(pp.availability()))
514629515630516631def write_verify_func(f, profile, dict_name, suffix):
517632 write_verify_func_begin(
518633 f, f"oxr_verify_{profile.validation_func_name}{suffix}")
519519- write_verify_func_body(f, profile, dict_name)
634634+ write_verify_func_body(f, profile, dict_name, profile.availability())
520635 write_verify_func_end(f)
521636522637