diff --git a/client_schema.json b/client_schema.json
new file mode 100644
index 0000000..3c8f13b
--- /dev/null
+++ b/client_schema.json
@@ -0,0 +1,189 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "definitions": {
+ "extension_name": {
+ "type": "string",
+ "description": "Name of an OpenXR extension",
+ "pattern": "^XR_([A-Z]+)(_([a-zA-Z0-9])+)+$"
+ },
+ "extension_with_details": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "allOf": [
+ {
+ "title": "Extension name"
+ },
+ {
+ "$ref": "#/definitions/extension_name"
+ }
+ ]
+ },
+ "notes": {
+ "type": "string",
+ "title": "Notes",
+ "description": "Free-form text about support of this extension"
+ }
+ },
+ "required": [
+ "name",
+ "notes"
+ ]
+ },
+ "extension": {
+ "allOf": [
+ {
+ "title": "Supported Extension"
+ },
+ {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/extension_name"
+ },
+ {
+ "$ref": "#/definitions/extension_with_details"
+ }
+ ]
+ }
+ ],
+ "examples": [
+ "XR_KHR_composition_layer_cylinder",
+ {
+ "name": "XR_EXT_hand_tracking",
+ "notes": "Currently in beta"
+ }
+ ]
+ },
+ "platform": {
+ "type": "string",
+ "enum": [
+ "Windows (Desktop)",
+ "Windows (HoloLens 2)",
+ "Linux (Desktop/Embedded)",
+ "Android (All-in-one)",
+ "Android (Phone/Installable)",
+ "MacOS (Desktop)"
+ ]
+ },
+ "form_factor": {
+ "type": "string",
+ "title": "XrFormFactor",
+ "enum": [
+ "XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY",
+ "XR_FORM_FACTOR_HANDHELD_DISPLAY"
+ ]
+ },
+ "view_configuration": {
+ "type": "string",
+ "title": "XrViewConfigurationType",
+ "enum": [
+ "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO",
+ "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO",
+ "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO",
+ "XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT"
+ ]
+ },
+ "environment_blend_mode": {
+ "type": "string",
+ "enum": [
+ "OPAQUE",
+ "ADDITIVE",
+ "ALPHA_BLEND"
+ ]
+ },
+ "view_configuration_data": {
+ "type": "object",
+ "title": "View Configuration Data",
+ "properties": {
+ "view_configuration": {
+ "$ref": "#/definitions/view_configuration"
+ },
+ "secondary_msft": {
+ "title": "Secondary (MSFT) view configuration?",
+ "type": "boolean",
+ "default": false
+ },
+ "environment_blend_modes": {
+ "title": "Supported environment blend modes in this view configuration",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/environment_blend_mode"
+ },
+ "minItems": 1
+ }
+ },
+ "required": [
+ "view_configuration",
+ "environment_blend_modes"
+ ]
+ },
+ "form_factor_data": {
+ "type": "object",
+ "title": "Form Factor Data",
+ "properties": {
+ "form_factor": {
+ "$ref": "#/definitions/form_factor"
+ },
+ "view_configurations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/view_configuration_data"
+ },
+ "minItems": 1
+ }
+ },
+ "required": [
+ "form_factor",
+ "view_configurations"
+ ]
+ }
+ },
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Client Name"
+ },
+ "vendor": {
+ "type": "string",
+ "title": "Vendor"
+ },
+ "platform": {
+ "$description": "Platforms this client supports.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/platform"
+ }
+ },
+ "updated": {
+ "type": "string",
+ "title": "Last updated",
+ "description": "Date this document was last reflective of the reality"
+ },
+ "notes": {
+ "type": "string",
+ "title": "Notes",
+ "description": "Freeform notes about this data file"
+ },
+ "extensions": {
+ "title": "Supported extensions",
+ "description": "A list of all supported extensions in this runtime, on this platform. Details or caveats may be included in 'notes'.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/extension"
+ }
+ },
+ "form_factors": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/form_factor_data"
+ }
+ }
+ },
+ "required": [
+ "name",
+ "vendor",
+ "extensions"
+ ]
+}
diff --git a/schema.json.license b/client_schema.json.license
similarity index 100%
rename from schema.json.license
rename to client_schema.json.license
diff --git a/clients/godot.json b/clients/godot.json
new file mode 100644
index 0000000..74c8a53
--- /dev/null
+++ b/clients/godot.json
@@ -0,0 +1,99 @@
+{
+ "$schema": "../client_schema.json",
+ "name": "Godot Game Engine",
+ "vendor": "Godot Community",
+ "notes": "Some extensions require the use of the Godot OpenXR Vendors Plugin.",
+ "platforms": [
+ "Windows (Desktop)",
+ "Linux (Desktop/Embedded)",
+ "Android (All-in-one)",
+ "Android (Phone/Installable)",
+ "MacOS (Desktop)"
+ ],
+ "extensions": [
+ "XR_KHR_android_create_instance",
+ "XR_KHR_composition_layer_cylinder",
+ "XR_KHR_composition_layer_depth",
+ "XR_KHR_composition_layer_equirect2",
+ "XR_KHR_loader_init_android",
+ "XR_KHR_opengl_es_enable",
+ "XR_KHR_opengl_enable",
+ "XR_KHR_visibility_mask",
+ "XR_KHR_vulkan_enable2",
+ "XR_EXT_debug_utils",
+ "XR_EXT_eye_gaze_interaction",
+ "XR_EXT_hand_interaction",
+ "XR_EXT_hand_tracking",
+ "XR_EXT_hand_joints_motion_range",
+ "XR_EXT_hand_tracking_data_source",
+ "XR_EXT_local_floor",
+ "XR_EXT_view_configuration_depth_range",
+ "XR_EXT_palm_pose",
+ "XR_EXT_hp_mixed_reality_controller",
+ "XR_EXT_samsung_odyssey_controller",
+ "XR_FB_body_tracking",
+ "XR_FB_composition_layer_alpha_blend",
+ "XR_FB_composition_layer_secure_content",
+ "XR_FB_composition_layer_settings",
+ "XR_FB_display_refresh_rate",
+ "XR_FB_face_tracking2",
+ "XR_FB_foveation",
+ "XR_FB_foveation_configuration",
+ "XR_FB_hand_tracking_aim",
+ "XR_FB_hand_tracking_capsules",
+ "XR_FB_hand_tracking_mesh",
+ "XR_FB_passthrough",
+ "XR_META_passthrough_preferences",
+ "XR_META_passthrough_color_lut",
+ "XR_META_spatial_entity_mesh",
+ "XR_FB_render_model",
+ "XR_FB_scene",
+ "XR_FB_scene_capture",
+ "XR_FB_spatial_entity_container",
+ "XR_FB_spatial_entity",
+ "XR_FB_spatial_entity_query",
+ "XR_FB_spatial_entity_sharing",
+ "XR_FB_spatial_entity_storage_batch",
+ "XR_FB_spatial_entity_storage",
+ "XR_FB_spatial_entity_user",
+ "XR_FB_swapchain_update_state",
+ "XR_FB_swapchain_update_state_vulkan",
+ "XR_FB_swapchain_update_state_opengl_es",
+ "XR_FB_triangle_mesh",
+ "XR_FB_touch_controller_proximity",
+ "XR_FB_touch_controller_pro",
+ "XR_META_touch_controller_plus",
+ "XR_META_automatic_layer_filter",
+ "XR_HTC_facial_tracking",
+ "XR_HTC_passthrough",
+ "XR_HTC_vive_cosmos_controller_interaction",
+ "XR_HTC_vive_focus3_controller_interaction",
+ "XR_HTC_hand_interaction",
+ "XR_HTCX_vive_tracker_interaction",
+ "XR_ML_ml2_controller_interaction",
+ "XR_MSFT_hand_interaction"
+ ],
+ "form_factors": [
+ {
+ "form_factor": "XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY",
+ "view_configurations": [
+ {
+ "view_configuration": "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO",
+ "environment_blend_modes": [
+ "OPAQUE",
+ "ADDITIVE",
+ "ALPHA_BLEND"
+ ]
+ },
+ {
+ "view_configuration": "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO",
+ "environment_blend_modes": [
+ "OPAQUE",
+ "ADDITIVE",
+ "ALPHA_BLEND"
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/clients/godot.json.license b/clients/godot.json.license
new file mode 100644
index 0000000..6a0d854
--- /dev/null
+++ b/clients/godot.json.license
@@ -0,0 +1,2 @@
+Copyright 2024, The Khronos Group Inc.
+SPDX-License-Identifier: CC-BY-4.0
diff --git a/extension_support_report.py b/extension_support_report.py
index 8796195..c20176f 100755
--- a/extension_support_report.py
+++ b/extension_support_report.py
@@ -5,7 +5,9 @@
from openxr_inventory.extensions import generate_report
from openxr_inventory.runtime_inventory import load_all_runtimes
+from openxr_inventory.client_inventory import load_all_clients
if __name__ == "__main__":
runtimes = load_all_runtimes()
- generate_report(runtimes)
+ clients = load_all_clients()
+ generate_report(runtimes, clients)
diff --git a/openxr_inventory/client_inventory.py b/openxr_inventory/client_inventory.py
new file mode 100644
index 0000000..16aba93
--- /dev/null
+++ b/openxr_inventory/client_inventory.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3 -i
+# Copyright 2022, The Khronos Group Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import json
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Dict, List, Optional, Union
+
+from .inventory_data import ExtensionEntry, EnvironmentBlendModeEntry, ViewConfigurationEntry, FormFactorEntry
+
+
+@dataclass(order=True)
+class ClientData:
+ """Data about a single runtime on a single platform, corresponds to a single JSON file in the inventory"""
+
+ stub: str
+ """A short identifier suitable for use as an HTML anchor, file name stem, etc."""
+
+ name: str
+ """The name of the runtime (on this platform)"""
+
+ notes: Optional[str]
+ """Free-form text with extra information"""
+
+ vendor: str
+ """The vendor's name"""
+
+ extensions: List[ExtensionEntry]
+ """The supported extensions"""
+
+ form_factors: List[FormFactorEntry]
+ """The supported form factors"""
+
+ def get_extension_entry(self, ext_name: str) -> Optional[ExtensionEntry]:
+ """
+ Get the entry for the named extension, if it exists.
+
+ This can tell you if the runtime supports that extension, as well as any notes from the inventory.
+ """
+ extension_entries = [
+ entry for entry in self.extensions if entry.name == ext_name
+ ]
+
+ if not extension_entries:
+ return
+ assert len(extension_entries) == 1
+ return extension_entries[0]
+
+ @property
+ def conformance_submission_url(self) -> Optional[str]:
+ if self.conformance_submission:
+ return "https://www.khronos.org/conformance/adopters/conformant-products/openxr#submission_{}".format(
+ self.conformance_submission
+ )
+
+ @classmethod
+ def from_json(cls, stub: str, d: Dict) -> "RuntimeData":
+ """
+ Create an object from the data loaded from a json file.
+
+ 'stub' should be the stem of the filename, typically.
+ """
+ exts = [ExtensionEntry.from_json(entry) for entry in d["extensions"]]
+ form_factors = [FormFactorEntry.from_json(entry) for entry in (d.get("form_factors", []))]
+ return ClientData(
+ stub=stub,
+ name=d["name"],
+ notes=d.get("notes"),
+ vendor=d["vendor"],
+ extensions=exts,
+ form_factors=form_factors,
+ )
+
+
+def load_all_clients(directory=None) -> List[ClientData]:
+ """Load all client inventory files."""
+ if not directory:
+ directory = Path(__file__).parent.parent / "clients"
+
+ failures = []
+ results = []
+ for f in directory.glob("*.json"):
+ with open(f, "r", encoding="utf-8") as fp:
+ data = json.load(fp)
+ try:
+ parsed = ClientData.from_json(f.stem, data)
+ results.append(parsed)
+ print("Loaded %s" % parsed.stub)
+ except KeyError as e:
+ print(
+ "Error loading %s (probably missing required property), skipping..."
+ % str(f)
+ )
+ print(e)
+ failures.append(str(f))
+ if failures:
+ print(failures)
+ raise RuntimeError(
+ "Could not parse some files, probably missing required properties"
+ )
+ return results
diff --git a/openxr_inventory/extensions.py b/openxr_inventory/extensions.py
index 43c0196..17c0c6a 100644
--- a/openxr_inventory/extensions.py
+++ b/openxr_inventory/extensions.py
@@ -7,7 +7,9 @@
import re
from typing import Dict, List, Tuple
-from .runtime_inventory import ExtensionEntry, RuntimeData
+from .inventory_data import ExtensionEntry
+from .runtime_inventory import RuntimeData
+from .client_inventory import ClientData
_RE_IS_KHR = re.compile(r"^XR_KHR_.*")
_RE_IS_EXT = re.compile(r"^XR_EXT_.*")
@@ -83,11 +85,16 @@ def ext_name_key(ext_name: str):
return (categorize_ext_name(ext_name), ext_name)
-def compute_known_extensions(runtimes: List[RuntimeData]) -> List[str]:
+def compute_known_extensions(runtimes: List[RuntimeData], clients: List[ClientData]) -> List[str]:
"""Compute a list of all known extensions, sorted as in the spec itself."""
known_extensions = set()
+
for runtime in runtimes:
known_extensions.update(ext.name for ext in runtime.extensions)
+
+ for client in clients:
+ known_extensions.update(ext.name for ext in client.extensions)
+
return list(sorted(known_extensions, key=ext_name_key))
@@ -100,8 +107,18 @@ def compute_runtime_support(runtimes: List[RuntimeData]) -> Dict[str, List[str]]
return runtime_support
-def compute_known_form_factors(runtimes: List[RuntimeData]) -> List[str]:
+def compute_client_support(clients: List[ClientData]) -> Dict[str, List[str]]:
+ """Compute a dictionary from client names to a list of supported extension names."""
+ client_support = {}
+ for client in clients:
+ support = [ext.name for ext in client.extensions]
+ client_support[client.name] = support
+ return client_support
+
+
+def compute_known_form_factors(runtimes: List[RuntimeData], clients: List[ClientData]) -> List[str]:
known_form_factors = {}
+
for runtime in runtimes:
for ff in runtime.form_factors:
for vc in ff.view_configurations:
@@ -109,46 +126,85 @@ def compute_known_form_factors(runtimes: List[RuntimeData]) -> List[str]:
for ebm in vc.environment_blend_modes:
known_form_factors[ff.name][vc.name] = known_form_factors[ff.name].get(vc.name, set())
known_form_factors[ff.name][vc.name].add(ebm.name)
+
+ for client in clients:
+ for ff in client.form_factors:
+ for vc in ff.view_configurations:
+ known_form_factors[ff.name] = known_form_factors.get(ff.name, {})
+ for ebm in vc.environment_blend_modes:
+ known_form_factors[ff.name][vc.name] = known_form_factors[ff.name].get(vc.name, set())
+ known_form_factors[ff.name][vc.name].add(ebm.name)
+
return known_form_factors
-def compute_form_factor_support(runtimes: List[RuntimeData]) -> Dict[str, List[str]]:
- runtime_form_factor_support = {}
+def compute_form_factor_support(runtimes: List[RuntimeData], clients: List[ClientData]) -> Dict[str, List[str]]:
+ form_factor_support = {}
+
for runtime in runtimes:
if runtime.form_factors:
- runtime_form_factor_support[runtime.name] = {}
+ form_factor_support[runtime.name] = {}
for ff in runtime.form_factors:
- runtime_form_factor_support[runtime.name][ff.name] = {}
+ form_factor_support[runtime.name][ff.name] = {}
+
+ for vc in ff.view_configurations:
+ form_factor_support[runtime.name][ff.name][vc.name] = set()
+
+ for ebm in vc.environment_blend_modes:
+ form_factor_support[runtime.name][ff.name][vc.name].add(ebm.name)
+
+ for client in clients:
+ if client.form_factors:
+ form_factor_support[client.name] = {}
+
+ for ff in client.form_factors:
+ form_factor_support[client.name][ff.name] = {}
for vc in ff.view_configurations:
- runtime_form_factor_support[runtime.name][ff.name][vc.name] = set()
+ form_factor_support[client.name][ff.name][vc.name] = set()
for ebm in vc.environment_blend_modes:
- runtime_form_factor_support[runtime.name][ff.name][vc.name].add(ebm.name)
- return runtime_form_factor_support
+ form_factor_support[client.name][ff.name][vc.name].add(ebm.name)
+ return form_factor_support
+
+class ExtensionSupport:
+ runtime_count: int
+ client_count: int
+
+ def __init__(self, runtime_count, client_count):
+ self.runtime_count = runtime_count
+ self.client_count = client_count
def compute_extension_support(
runtimes: List[RuntimeData],
-) -> Dict[str, List[Tuple[RuntimeData, ExtensionEntry]]]:
+ clients: List[ClientData],
+) -> Dict[str, ExtensionSupport]:
"""
- For each extension, find all the runtimes that support it.
+ For each extension, counts the runtimes and clients that support it.
- Returns a dict with extension names as the keys, and a list
- of (RuntimeData, ExtensionEntry) tuples as the values.
+ Returns a dict with counts
"""
- known_extensions = compute_known_extensions(runtimes)
+ known_extensions = compute_known_extensions(runtimes, clients)
extension_support = {}
for extension_name in known_extensions:
- # Get all the support
- support = [
- (runtime, runtime.get_extension_entry(extension_name))
- for runtime in runtimes
- ]
+ runtime_count = 0
+ client_count = 0
+
+ # Count our runtime support
+ for runtime in runtimes:
+ if runtime.get_extension_entry(extension_name):
+ runtime_count += 1
+
+ # Get all the client support
+ for client in clients:
+ if client.get_extension_entry(extension_name):
+ client_count += 1
+
# Filter out the empty ones
- support = [(runtime, entry) for runtime, entry in support if entry]
- extension_support[extension_name] = support
+ extension_support[extension_name] = ExtensionSupport(runtime_count, client_count)
+
return extension_support
@@ -157,6 +213,7 @@ def compute_extension_support(
def generate_report(
runtimes: List[RuntimeData],
+ clients: List[ClientData],
template_filename: str = _FILENAME_STEM + ".jinja2.html",
out_filename: str = "public/" + _FILENAME_STEM + ".html",
):
@@ -172,12 +229,14 @@ def generate_report(
env.globals["categorize_ext"] = categorize_ext_name
template = env.get_template(template_filename)
contents = template.render(
- extensions=compute_known_extensions(runtimes),
- extension_support=compute_extension_support(runtimes),
+ extensions=compute_known_extensions(runtimes, clients),
+ extension_support=compute_extension_support(runtimes, clients),
runtime_support=compute_runtime_support(runtimes),
- known_form_factors=compute_known_form_factors(runtimes),
- form_factor_support=compute_form_factor_support(runtimes),
+ client_support=compute_client_support(clients),
+ known_form_factors=compute_known_form_factors(runtimes, clients),
+ form_factor_support=compute_form_factor_support(runtimes, clients),
runtimes=runtimes,
+ clients=clients
)
if contents:
@@ -189,6 +248,8 @@ def generate_report(
if __name__ == "__main__":
from .runtime_inventory import load_all_runtimes
+ from .client_inventory import load_all_clients
runtimes = load_all_runtimes()
- generate_report(runtimes)
+ clients = load_all_clients()
+ generate_report(runtimes, clients)
diff --git a/openxr_inventory/inventory_data.py b/openxr_inventory/inventory_data.py
new file mode 100644
index 0000000..83ed878
--- /dev/null
+++ b/openxr_inventory/inventory_data.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3 -i
+# Copyright 2022, The Khronos Group Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import json
+from dataclasses import dataclass
+from typing import Dict, List, Optional, Union
+
+@dataclass
+class ExtensionEntry:
+ """
+ An entry in the "extensions" array for a runtime or layer.
+
+ Corresponds to the schema reference `#/definitions/extension`.
+ """
+
+ name: str
+ """Extension name"""
+
+ notes: Optional[str] = None
+ """Optional notes about the support/use of this extension"""
+
+ @classmethod
+ def from_json(cls, d: Union[Dict, str]) -> "ExtensionEntry":
+ """Create an ExtensionEntry from either a str or dict as you'd get from parsing the JSON."""
+ if isinstance(d, str):
+ return ExtensionEntry(name=d)
+ return ExtensionEntry(name=d["name"], notes=d.get("notes"))
+
+@dataclass
+class EnvironmentBlendModeEntry:
+ """
+ An entry in the "environment_blend_modes" array for a runtime or layer.
+
+ Corresponds to the schema reference `#/definitions/environment_blend_mode`.
+ """
+
+ name: str
+ """Environment blend mode name"""
+
+ @classmethod
+ def from_json(cls, d: Union[Dict, str]) -> "EnvironmentBlendModeEntry":
+ return EnvironmentBlendModeEntry(name=d)
+
+@dataclass
+class ViewConfigurationEntry:
+ """
+ An entry in the "view_configurations" array for a runtime or layer.
+
+ Corresponds to the schema reference `#/definitions/view_configuration`.
+ """
+
+ name: str
+ """View configuration name"""
+
+ environment_blend_modes: List[EnvironmentBlendModeEntry]
+ """Environment blend modes supported for the view configuration"""
+
+ @classmethod
+ def from_json(cls, d: Union[Dict, str]) -> "ViewConfigurationEntry":
+ return ViewConfigurationEntry(name=d["view_configuration"], environment_blend_modes=[EnvironmentBlendModeEntry.from_json(b) for b in d["environment_blend_modes"]])
+
+@dataclass
+class FormFactorEntry:
+ """
+ An entry in the "form_factors" array for a runtime or layer.
+
+ Corresponds to the schema reference `#/definitions/form_factor`.
+ """
+
+ name: str
+ """Form Factor name"""
+
+ view_configurations: List[ViewConfigurationEntry]
+ """View configurations supported for the form factor"""
+
+ @classmethod
+ def from_json(cls, d: Union[Dict, str]) -> "FormFactorEntry":
+ return FormFactorEntry(name=d["form_factor"], view_configurations=[ViewConfigurationEntry.from_json(v) for v in d["view_configurations"]])
diff --git a/openxr_inventory/runtime_inventory.py b/openxr_inventory/runtime_inventory.py
index edb6d58..100f0f3 100644
--- a/openxr_inventory/runtime_inventory.py
+++ b/openxr_inventory/runtime_inventory.py
@@ -8,81 +8,7 @@
from pathlib import Path
from typing import Dict, List, Optional, Union
-
-@dataclass
-class ExtensionEntry:
- """
- An entry in the "extensions" array for a runtime or layer.
-
- Corresponds to the schema reference `#/definitions/extension`.
- """
-
- name: str
- """Extension name"""
-
- notes: Optional[str] = None
- """Optional notes about the support/use of this extension"""
-
- @classmethod
- def from_json(cls, d: Union[Dict, str]) -> "ExtensionEntry":
- """Create an ExtensionEntry from either a str or dict as you'd get from parsing the JSON."""
- if isinstance(d, str):
- return ExtensionEntry(name=d)
- return ExtensionEntry(name=d["name"], notes=d.get("notes"))
-
-
-@dataclass
-class EnvironmentBlendModeEntry:
- """
- An entry in the "environment_blend_modes" array for a runtime or layer.
-
- Corresponds to the schema reference `#/definitions/environment_blend_mode`.
- """
-
- name: str
- """Environment blend mode name"""
-
- @classmethod
- def from_json(cls, d: Union[Dict, str]) -> "EnvironmentBlendModeEntry":
- return EnvironmentBlendModeEntry(name=d)
-
-
-@dataclass
-class ViewConfigurationEntry:
- """
- An entry in the "view_configurations" array for a runtime or layer.
-
- Corresponds to the schema reference `#/definitions/view_configuration`.
- """
-
- name: str
- """View configuration name"""
-
- environment_blend_modes: List[EnvironmentBlendModeEntry]
- """Environment blend modes supported for the view configuration"""
-
- @classmethod
- def from_json(cls, d: Union[Dict, str]) -> "ViewConfigurationEntry":
- return ViewConfigurationEntry(name=d["view_configuration"], environment_blend_modes=[EnvironmentBlendModeEntry.from_json(b) for b in d["environment_blend_modes"]])
-
-
-@dataclass
-class FormFactorEntry:
- """
- An entry in the "form_factors" array for a runtime or layer.
-
- Corresponds to the schema reference `#/definitions/form_factor`.
- """
-
- name: str
- """Form Factor name"""
-
- view_configurations: List[ViewConfigurationEntry]
- """View configurations supported for the form factor"""
-
- @classmethod
- def from_json(cls, d: Union[Dict, str]) -> "FormFactorEntry":
- return FormFactorEntry(name=d["form_factor"], view_configurations=[ViewConfigurationEntry.from_json(v) for v in d["view_configurations"]])
+from .inventory_data import ExtensionEntry, EnvironmentBlendModeEntry, ViewConfigurationEntry, FormFactorEntry
@dataclass(order=True)
@@ -104,6 +30,9 @@ class RuntimeData:
devices_notes: Optional[str]
"""Free-form text about devices support"""
+ notes: Optional[str]
+ """Free-form text with extra information"""
+
vendor: str
"""The vendor's name"""
@@ -150,6 +79,7 @@ def from_json(cls, stub: str, d: Dict) -> "RuntimeData":
conformance_submission=d.get("conformance_submission"),
conformance_notes=d.get("conformance_notes"),
devices_notes=d.get("devices_notes"),
+ notes=d.get("notes"),
vendor=d["vendor"],
extensions=exts,
form_factors=form_factors,
diff --git a/openxr_inventory/templates/base.jinja2.html b/openxr_inventory/templates/base.jinja2.html
index edfe9d4..d3c33ca 100644
--- a/openxr_inventory/templates/base.jinja2.html
+++ b/openxr_inventory/templates/base.jinja2.html
@@ -53,6 +53,11 @@
+