ailloli_ui_openxr/openxr_runtime/
instance.rs1use openxr as xr;
4
5use super::error::OpenXrRuntimeError;
6
7pub(crate) struct OpenXrInstanceOptions<'a> {
18 pub application_name: &'a str,
20 pub engine_name: &'a str,
22}
23
24pub struct OpenXrInstance {
39 pub entry: xr::Entry,
41 pub instance: xr::Instance,
43 pub system: xr::SystemId,
45 pub blend_mode: xr::EnvironmentBlendMode,
47 pub runtime_name: String,
49 pub runtime_version: xr::Version,
51 pub hand_tracking_supported: bool,
53 pub hand_aim_supported: bool,
55 pub composition_layer_cylinder_supported: bool,
57}
58
59impl OpenXrInstance {
60 pub(crate) fn new(options: OpenXrInstanceOptions<'_>) -> Result<Self, OpenXrRuntimeError> {
80 validate_openxr_name(
81 "application_name",
82 options.application_name,
83 xr::sys::MAX_APPLICATION_NAME_SIZE,
84 )?;
85 validate_openxr_name(
86 "engine_name",
87 options.engine_name,
88 xr::sys::MAX_ENGINE_NAME_SIZE,
89 )?;
90
91 let entry = unsafe { xr::Entry::load() }.map_err(|err| OpenXrRuntimeError::LoadEntry {
92 message: err.to_string(),
93 })?;
94
95 #[cfg(target_os = "android")]
96 entry
97 .initialize_android_loader()
98 .map_err(|result| OpenXrRuntimeError::InitializeAndroidLoader { result })?;
99
100 let available = entry
101 .enumerate_extensions()
102 .map_err(|result| OpenXrRuntimeError::EnumerateExtensions { result })?;
103 if !available.khr_vulkan_enable2 {
104 return Err(OpenXrRuntimeError::MissingExtension(
105 "XR_KHR_vulkan_enable2",
106 ));
107 }
108 #[cfg(target_os = "android")]
109 if !available.khr_android_create_instance {
110 return Err(OpenXrRuntimeError::MissingExtension(
111 "XR_KHR_android_create_instance",
112 ));
113 }
114
115 let hand_tracking_ext_available = available.ext_hand_tracking;
116 let hand_aim_ext_available = hand_tracking_ext_available && available.fb_hand_tracking_aim;
117 let composition_layer_cylinder_supported = available.khr_composition_layer_cylinder;
118 if composition_layer_cylinder_supported {
119 log::info!("OpenXR extension XR_KHR_composition_layer_cylinder available/enabled");
120 } else {
121 log::warn!("OpenXR extension XR_KHR_composition_layer_cylinder unavailable");
122 }
123
124 let mut enabled = xr::ExtensionSet::default();
125 enabled.khr_vulkan_enable2 = true;
126 #[cfg(target_os = "android")]
127 {
128 enabled.khr_android_create_instance = true;
129 }
130 if hand_tracking_ext_available {
131 enabled.ext_hand_tracking = true;
132 if hand_aim_ext_available {
133 enabled.fb_hand_tracking_aim = true;
134 }
135 }
136 if composition_layer_cylinder_supported {
137 enabled.khr_composition_layer_cylinder = true;
138 }
139
140 let instance = entry
141 .create_instance(
142 &xr::ApplicationInfo {
143 application_name: options.application_name,
144 application_version: 0,
145 engine_name: options.engine_name,
146 engine_version: 0,
147 api_version: xr::Version::new(1, 0, 0),
148 },
149 &enabled,
150 &[],
151 )
152 .map_err(|result| OpenXrRuntimeError::CreateInstance { result })?;
153 let properties = instance
154 .properties()
155 .map_err(|result| OpenXrRuntimeError::InstanceProperties { result })?;
156
157 let system = instance
158 .system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)
159 .map_err(|result| OpenXrRuntimeError::System { result })?;
160
161 let blend_modes = instance
162 .enumerate_environment_blend_modes(system, xr::ViewConfigurationType::PRIMARY_STEREO)
163 .map_err(|result| OpenXrRuntimeError::BlendModes { result })?;
164 let blend_mode = blend_modes
165 .first()
166 .copied()
167 .unwrap_or(xr::EnvironmentBlendMode::OPAQUE);
168
169 let hand_tracking_supported = if hand_tracking_ext_available {
170 instance.supports_hand_tracking(system).unwrap_or(false)
171 } else {
172 false
173 };
174 let hand_aim_supported = hand_tracking_supported && hand_aim_ext_available;
175
176 Ok(Self {
177 entry,
178 instance,
179 system,
180 blend_mode,
181 runtime_name: properties.runtime_name,
182 runtime_version: properties.runtime_version,
183 hand_tracking_supported,
184 hand_aim_supported,
185 composition_layer_cylinder_supported,
186 })
187 }
188}
189
190fn validate_openxr_name(
200 field: &'static str,
201 value: &str,
202 max_bytes_with_nul: usize,
203) -> Result<(), OpenXrRuntimeError> {
204 if value.as_bytes().contains(&0) {
205 return Err(OpenXrRuntimeError::InvalidOpenXrName {
206 field,
207 reason: "must not contain NUL bytes".to_string(),
208 });
209 }
210 if value.len() >= max_bytes_with_nul {
211 return Err(OpenXrRuntimeError::InvalidOpenXrName {
212 field,
213 reason: format!(
214 "must be shorter than {max_bytes_with_nul} bytes including trailing NUL, got {}",
215 value.len() + 1
216 ),
217 });
218 }
219 Ok(())
220}