ailloli_ui_openxr/openxr_runtime/composer.rs
1//! Composition-layer geometry and frame-loop option types.
2
3use ailloli_ui_core::math::Scale;
4use ailloli_ui_core::Color;
5use ailloli_ui_render_vulkan::VulkanRendererOptions;
6use openxr as xr;
7
8use super::input::OpenXrUiInputOptions;
9use super::swapchain::OpenXrQuadSwapchain;
10
11#[derive(Clone, Copy)]
12/// Options for a host-managed loop that clears and submits a quad layer.
13///
14/// Defaults to a 1024x576 image, opaque blue clear, and a 1.6x0.9 metre quad
15/// two metres in front of the reference-space origin.
16///
17/// # Examples
18///
19/// ```
20/// use ailloli_ui_openxr::OpenXrQuadFrameLoopOptions;
21/// let options = OpenXrQuadFrameLoopOptions::default();
22/// assert_eq!((options.pixel_width, options.pixel_height), (1024, 576));
23/// ```
24pub struct OpenXrQuadFrameLoopOptions {
25 /// Swapchain width in physical pixels; zero is rejected by swapchain creation.
26 pub pixel_width: u32,
27 /// Swapchain height in physical pixels; zero is rejected by swapchain creation.
28 pub pixel_height: u32,
29 /// Linear RGBA clear value forwarded to Vulkan.
30 pub clear_color: [f32; 4],
31 /// Quad pose, size, visibility, and flags.
32 pub layer: OpenXrQuadLayerOptions,
33}
34
35impl Default for OpenXrQuadFrameLoopOptions {
36 fn default() -> Self {
37 Self {
38 pixel_width: 1024,
39 pixel_height: 576,
40 clear_color: [0.05, 0.20, 0.80, 1.0],
41 layer: OpenXrQuadLayerOptions::default(),
42 }
43 }
44}
45
46#[derive(Clone, Copy)]
47/// Options for rendering Ailloli draw commands through Vulkan into a quad.
48///
49/// # Examples
50///
51/// ```
52/// use ailloli_ui_openxr::OpenXrRenderVulkanFrameLoopOptions;
53/// let options = OpenXrRenderVulkanFrameLoopOptions::default();
54/// assert_eq!(options.scale.dpr, 1.0);
55/// ```
56pub struct OpenXrRenderVulkanFrameLoopOptions {
57 /// Swapchain width in physical pixels.
58 pub pixel_width: u32,
59 /// Swapchain height in physical pixels.
60 pub pixel_height: u32,
61 /// Clear color applied before UI rendering.
62 pub clear: Color,
63 /// Logical-to-physical scale used by the renderer.
64 pub scale: Scale,
65 /// Quad composition-layer configuration.
66 pub layer: OpenXrQuadLayerOptions,
67 /// Vulkan renderer configuration.
68 pub renderer: VulkanRendererOptions,
69}
70
71impl Default for OpenXrRenderVulkanFrameLoopOptions {
72 fn default() -> Self {
73 Self {
74 pixel_width: 1024,
75 pixel_height: 576,
76 clear: Color::f32(0.05, 0.20, 0.80, 1.0),
77 scale: Scale::new(1.0),
78 layer: OpenXrQuadLayerOptions::default(),
79 renderer: VulkanRendererOptions::default(),
80 }
81 }
82}
83
84#[derive(Clone, Copy)]
85/// Complete options for the built-in Ailloli UI OpenXR frame loop.
86///
87/// # Examples
88///
89/// ```
90/// use ailloli_ui_openxr::OpenXrUiFrameLoopOptions;
91/// let options = OpenXrUiFrameLoopOptions::default();
92/// assert!(options.input.enabled);
93/// assert_eq!((options.pixel_width, options.pixel_height), (1024, 576));
94/// ```
95pub struct OpenXrUiFrameLoopOptions {
96 /// Swapchain width in physical pixels.
97 pub pixel_width: u32,
98 /// Swapchain height in physical pixels.
99 pub pixel_height: u32,
100 /// Clear color applied before UI rendering.
101 pub clear: Color,
102 /// Logical-to-physical scale; the default DPR is `1.0`.
103 pub scale: Scale,
104 /// Quad composition-layer configuration.
105 pub layer: OpenXrQuadLayerOptions,
106 /// Vulkan renderer configuration.
107 pub renderer: VulkanRendererOptions,
108 /// Controller and hand-input selection configuration.
109 pub input: OpenXrUiInputOptions,
110}
111
112impl Default for OpenXrUiFrameLoopOptions {
113 fn default() -> Self {
114 Self {
115 pixel_width: 1024,
116 pixel_height: 576,
117 clear: Color::f32(0.05, 0.20, 0.80, 1.0),
118 scale: Scale::new(1.0),
119 layer: OpenXrQuadLayerOptions::default(),
120 renderer: VulkanRendererOptions::default(),
121 input: OpenXrUiInputOptions::default(),
122 }
123 }
124}
125
126#[derive(Clone, Copy)]
127/// Pose and presentation attributes for one OpenXR quad layer.
128///
129/// Size and position use metres. The default faces both eyes at `(0, 0, -2)`
130/// with identity orientation and no composition flags.
131///
132/// # Examples
133///
134/// ```
135/// use ailloli_ui_openxr::OpenXrQuadLayerOptions;
136/// let layer = OpenXrQuadLayerOptions::default();
137/// assert_eq!((layer.size.width, layer.size.height), (1.6, 0.9));
138/// assert_eq!(layer.pose.position.z, -2.0);
139/// ```
140pub struct OpenXrQuadLayerOptions {
141 /// Layer pose relative to the submitted reference space, in metres.
142 pub pose: xr::Posef,
143 /// Physical layer width and height in metres.
144 pub size: xr::Extent2Df,
145 /// Eyes to which the layer is visible.
146 pub eye_visibility: xr::EyeVisibility,
147 /// OpenXR composition flags such as source-alpha blending.
148 pub layer_flags: xr::CompositionLayerFlags,
149}
150
151impl Default for OpenXrQuadLayerOptions {
152 fn default() -> Self {
153 Self {
154 pose: xr::Posef {
155 orientation: xr::Quaternionf {
156 x: 0.0,
157 y: 0.0,
158 z: 0.0,
159 w: 1.0,
160 },
161 position: xr::Vector3f {
162 x: 0.0,
163 y: 0.0,
164 z: -2.0,
165 },
166 },
167 size: xr::Extent2Df {
168 width: 1.6,
169 height: 0.9,
170 },
171 eye_visibility: xr::EyeVisibility::BOTH,
172 layer_flags: xr::CompositionLayerFlags::EMPTY,
173 }
174 }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178/// Requested presentation surface geometry.
179///
180/// # Examples
181///
182/// ```
183/// use ailloli_ui_openxr::OpenXrScreenSurfaceMode;
184/// assert_ne!(OpenXrScreenSurfaceMode::Flat, OpenXrScreenSurfaceMode::Curved);
185/// ```
186pub enum OpenXrScreenSurfaceMode {
187 /// Submit a standard quad composition layer.
188 Flat,
189 /// Resolve to a cylinder extension or mesh fallback.
190 Curved,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194/// Backend preference used when [`OpenXrScreenSurfaceMode::Curved`] is selected.
195///
196/// # Examples
197///
198/// ```
199/// use ailloli_ui_openxr::OpenXrCurvedLayerBackend;
200/// let backend = OpenXrCurvedLayerBackend::Auto;
201/// assert!(matches!(backend, OpenXrCurvedLayerBackend::Auto));
202/// ```
203pub enum OpenXrCurvedLayerBackend {
204 /// Prefer a native cylinder when supported, otherwise select mesh fallback.
205 Auto,
206 /// Require native cylinder-layer composition.
207 Cylinder,
208 /// Request the quad-backed mesh fallback path.
209 Mesh,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq)]
213/// Physical geometry for a curved screen.
214///
215/// All fields are metres. [`Self::sanitized`] enforces positive radius and
216/// dimensions and a non-negative gap.
217///
218/// # Examples
219///
220/// ```
221/// use ailloli_ui_openxr::OpenXrCurvedScreenOptions;
222/// let options = OpenXrCurvedScreenOptions::default();
223/// assert_eq!((options.radius_m, options.gap_m), (2.0, 0.12));
224/// ```
225pub struct OpenXrCurvedScreenOptions {
226 /// Cylinder radius in metres; sanitized to at least `0.05`.
227 pub radius_m: f32,
228 /// Arc width in metres; sanitized to at least `0.01`.
229 pub screen_width_m: f32,
230 /// Screen height in metres; sanitized to at least `0.01`.
231 pub screen_height_m: f32,
232 /// Separation between adjacent curved screens in metres; clamped to zero.
233 pub gap_m: f32,
234}
235
236impl Default for OpenXrCurvedScreenOptions {
237 fn default() -> Self {
238 Self {
239 radius_m: 2.0,
240 screen_width_m: 1.6,
241 screen_height_m: 0.9,
242 gap_m: 0.12,
243 }
244 }
245}
246
247impl OpenXrCurvedScreenOptions {
248 /// Returns a copy with geometry clamped to safe positive minima.
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// use ailloli_ui_openxr::OpenXrCurvedScreenOptions;
254 /// let sanitized = OpenXrCurvedScreenOptions { radius_m: 0.0, screen_width_m: -1.0, screen_height_m: 0.0, gap_m: -2.0 }.sanitized();
255 /// assert_eq!((sanitized.radius_m, sanitized.screen_width_m, sanitized.screen_height_m, sanitized.gap_m), (0.05, 0.01, 0.01, 0.0));
256 /// ```
257 pub fn sanitized(self) -> Self {
258 Self {
259 radius_m: self.radius_m.max(0.05),
260 screen_width_m: self.screen_width_m.max(0.01),
261 screen_height_m: self.screen_height_m.max(0.01),
262 gap_m: self.gap_m.max(0.0),
263 }
264 }
265
266 /// Returns the cylinder's central angle in radians (`width / radius`).
267 ///
268 /// Sanitization occurs before division.
269 ///
270 /// # Examples
271 ///
272 /// ```
273 /// use ailloli_ui_openxr::OpenXrCurvedScreenOptions;
274 /// assert!((OpenXrCurvedScreenOptions::default().central_angle() - 0.8).abs() < 1e-6);
275 /// ```
276 pub fn central_angle(self) -> f32 {
277 let options = self.sanitized();
278 options.screen_width_m / options.radius_m
279 }
280
281 /// Returns the sanitized physical width-to-height ratio.
282 ///
283 /// # Examples
284 ///
285 /// ```
286 /// use ailloli_ui_openxr::OpenXrCurvedScreenOptions;
287 /// assert!((OpenXrCurvedScreenOptions::default().aspect_ratio() - 16.0 / 9.0).abs() < 1e-6);
288 /// ```
289 pub fn aspect_ratio(self) -> f32 {
290 let options = self.sanitized();
291 options.screen_width_m / options.screen_height_m
292 }
293}
294
295#[derive(Clone, Copy)]
296/// Selects flat or curved composition together with each geometry configuration.
297///
298/// # Examples
299///
300/// ```
301/// use ailloli_ui_openxr::{OpenXrScreenLayerOptions, OpenXrScreenSurfaceMode};
302/// let options = OpenXrScreenLayerOptions::default();
303/// assert_eq!(options.surface_mode, OpenXrScreenSurfaceMode::Flat);
304/// ```
305pub struct OpenXrScreenLayerOptions {
306 /// Requested flat or curved surface.
307 pub surface_mode: OpenXrScreenSurfaceMode,
308 /// Preferred implementation for a curved surface.
309 pub curved_backend: OpenXrCurvedLayerBackend,
310 /// Pose, visibility, flags, and fallback quad dimensions.
311 pub flat: OpenXrQuadLayerOptions,
312 /// Physical cylinder or mesh geometry.
313 pub curved: OpenXrCurvedScreenOptions,
314}
315
316impl Default for OpenXrScreenLayerOptions {
317 fn default() -> Self {
318 Self {
319 surface_mode: OpenXrScreenSurfaceMode::Flat,
320 curved_backend: OpenXrCurvedLayerBackend::Auto,
321 flat: OpenXrQuadLayerOptions::default(),
322 curved: OpenXrCurvedScreenOptions::default(),
323 }
324 }
325}
326
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328/// Concrete composition backend selected for the current runtime capabilities.
329///
330/// # Examples
331///
332/// ```
333/// use ailloli_ui_openxr::OpenXrResolvedScreenBackend;
334/// let backend = OpenXrResolvedScreenBackend::Flat;
335/// assert_eq!(backend, OpenXrResolvedScreenBackend::Flat);
336/// ```
337pub enum OpenXrResolvedScreenBackend {
338 /// Standard OpenXR quad composition layer.
339 Flat,
340 /// `XR_KHR_composition_layer_cylinder` composition layer.
341 Cylinder,
342 /// Renderer-owned curved mesh submitted through a quad image.
343 Mesh,
344}
345
346/// Borrowed OpenXR composition layer produced by a screen composer.
347///
348/// # Examples
349///
350/// ```no_run
351/// use ailloli_ui_openxr::OpenXrScreenLayer;
352/// fn submit(layer: &OpenXrScreenLayer<'_>) {
353/// let _base: &openxr::CompositionLayerBase<'_, openxr::Vulkan> = layer.as_base();
354/// }
355/// ```
356pub enum OpenXrScreenLayer<'a> {
357 /// Flat quad layer, also used as the current mesh-fallback submission shell.
358 Quad(xr::CompositionLayerQuad<'a, xr::Vulkan>),
359 /// Native cylinder-extension layer.
360 Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>),
361}
362
363impl<'a> OpenXrScreenLayer<'a> {
364 /// Erases the concrete layer type for `Session::end_frame` submission.
365 ///
366 /// # Examples
367 ///
368 /// ```no_run
369 /// use ailloli_ui_openxr::OpenXrScreenLayer;
370 /// fn base<'a>(layer: &'a OpenXrScreenLayer<'a>) -> &'a openxr::CompositionLayerBase<'a, openxr::Vulkan> {
371 /// layer.as_base()
372 /// }
373 /// ```
374 pub fn as_base(&self) -> &xr::CompositionLayerBase<'_, xr::Vulkan> {
375 match self {
376 Self::Quad(layer) => layer,
377 Self::Cylinder(layer) => layer,
378 }
379 }
380}
381
382/// Builds a standard quad layer referencing one Ailloli swapchain.
383///
384/// # Examples
385///
386/// ```
387/// use ailloli_ui_openxr::{OpenXrQuadComposer, OpenXrQuadLayerOptions};
388/// let _composer = OpenXrQuadComposer::new(OpenXrQuadLayerOptions::default());
389/// ```
390pub struct OpenXrQuadComposer {
391 /// Immutable layer attributes copied into each submitted layer.
392 layer: OpenXrQuadLayerOptions,
393}
394
395impl OpenXrQuadComposer {
396 /// Creates a composer from fixed quad attributes.
397 ///
398 /// # Examples
399 ///
400 /// ```
401 /// use ailloli_ui_openxr::{OpenXrQuadComposer, OpenXrQuadLayerOptions};
402 /// let _: OpenXrQuadComposer = OpenXrQuadComposer::new(OpenXrQuadLayerOptions::default());
403 /// ```
404 pub fn new(layer: OpenXrQuadLayerOptions) -> Self {
405 Self { layer }
406 }
407
408 /// Builds a borrowed quad composition layer for one frame submission.
409 ///
410 /// The result references both `reference_space` and `swapchain`; keep both
411 /// alive through `end_frame`. Array image index is always zero.
412 ///
413 /// # Examples
414 ///
415 /// ```no_run
416 /// use ailloli_ui_openxr::{OpenXrQuadComposer, OpenXrQuadLayerOptions, OpenXrQuadSwapchain};
417 /// fn layer<'a>(composer: &OpenXrQuadComposer, space: &'a openxr::Space, swapchain: &'a OpenXrQuadSwapchain) -> openxr::CompositionLayerQuad<'a, openxr::Vulkan> {
418 /// composer.build_layer(space, swapchain)
419 /// }
420 /// # let _ = OpenXrQuadLayerOptions::default();
421 /// ```
422 pub fn build_layer<'a>(
423 &self,
424 reference_space: &'a xr::Space,
425 swapchain: &'a OpenXrQuadSwapchain,
426 ) -> xr::CompositionLayerQuad<'a, xr::Vulkan> {
427 xr::CompositionLayerQuad::new()
428 .space(reference_space)
429 .eye_visibility(self.layer.eye_visibility)
430 .sub_image(
431 xr::SwapchainSubImage::new()
432 .swapchain(swapchain.handle())
433 .image_array_index(0)
434 .image_rect(swapchain.image_rect()),
435 )
436 .pose(self.layer.pose)
437 .size(self.layer.size)
438 .layer_flags(self.layer.layer_flags)
439 }
440}
441
442/// Resolves screen geometry and builds its OpenXR composition layer.
443///
444/// # Examples
445///
446/// ```
447/// use ailloli_ui_openxr::{OpenXrResolvedScreenBackend, OpenXrScreenComposer, OpenXrScreenLayerOptions};
448/// let composer = OpenXrScreenComposer::new(OpenXrScreenLayerOptions::default());
449/// assert_eq!(composer.resolve_backend(false), OpenXrResolvedScreenBackend::Flat);
450/// ```
451pub struct OpenXrScreenComposer {
452 /// Requested surface and layer configuration.
453 options: OpenXrScreenLayerOptions,
454}
455
456impl OpenXrScreenComposer {
457 /// Creates a composer with immutable options.
458 ///
459 /// # Examples
460 ///
461 /// ```
462 /// use ailloli_ui_openxr::{OpenXrScreenComposer, OpenXrScreenLayerOptions};
463 /// let _: OpenXrScreenComposer = OpenXrScreenComposer::new(OpenXrScreenLayerOptions::default());
464 /// ```
465 pub fn new(options: OpenXrScreenLayerOptions) -> Self {
466 Self { options }
467 }
468
469 /// Resolves the requested surface against cylinder-extension availability.
470 ///
471 /// Explicit `Cylinder` remains cylinder even when the capability argument is
472 /// false; callers must validate that forced choice before submission.
473 ///
474 /// # Examples
475 ///
476 /// ```
477 /// use ailloli_ui_openxr::{OpenXrCurvedLayerBackend, OpenXrResolvedScreenBackend, OpenXrScreenComposer, OpenXrScreenLayerOptions, OpenXrScreenSurfaceMode};
478 /// let composer = OpenXrScreenComposer::new(OpenXrScreenLayerOptions { surface_mode: OpenXrScreenSurfaceMode::Curved, curved_backend: OpenXrCurvedLayerBackend::Auto, ..Default::default() });
479 /// assert_eq!(composer.resolve_backend(true), OpenXrResolvedScreenBackend::Cylinder);
480 /// assert_eq!(composer.resolve_backend(false), OpenXrResolvedScreenBackend::Mesh);
481 /// ```
482 pub fn resolve_backend(
483 &self,
484 composition_layer_cylinder_supported: bool,
485 ) -> OpenXrResolvedScreenBackend {
486 resolve_screen_backend(
487 self.options.surface_mode,
488 self.options.curved_backend,
489 composition_layer_cylinder_supported,
490 )
491 }
492
493 /// Builds the resolved borrowed layer for one frame.
494 ///
495 /// `Mesh` currently uses a quad submission shell; the renderer is responsible
496 /// for producing curved imagery in that swapchain.
497 ///
498 /// # Examples
499 ///
500 /// ```no_run
501 /// use ailloli_ui_openxr::{OpenXrQuadSwapchain, OpenXrScreenComposer, OpenXrScreenLayer, OpenXrScreenLayerOptions};
502 /// fn build<'a>(composer: &OpenXrScreenComposer, space: &'a openxr::Space, swapchain: &'a OpenXrQuadSwapchain) -> OpenXrScreenLayer<'a> {
503 /// composer.build_layer(space, swapchain, true)
504 /// }
505 /// # let _ = OpenXrScreenLayerOptions::default();
506 /// ```
507 pub fn build_layer<'a>(
508 &self,
509 reference_space: &'a xr::Space,
510 swapchain: &'a OpenXrQuadSwapchain,
511 composition_layer_cylinder_supported: bool,
512 ) -> OpenXrScreenLayer<'a> {
513 match self.resolve_backend(composition_layer_cylinder_supported) {
514 OpenXrResolvedScreenBackend::Flat | OpenXrResolvedScreenBackend::Mesh => {
515 OpenXrScreenLayer::Quad(
516 OpenXrQuadComposer::new(self.options.flat)
517 .build_layer(reference_space, swapchain),
518 )
519 }
520 OpenXrResolvedScreenBackend::Cylinder => {
521 let curved = self.options.curved.sanitized();
522 OpenXrScreenLayer::Cylinder(
523 xr::CompositionLayerCylinderKHR::new()
524 .space(reference_space)
525 .eye_visibility(self.options.flat.eye_visibility)
526 .sub_image(
527 xr::SwapchainSubImage::new()
528 .swapchain(swapchain.handle())
529 .image_array_index(0)
530 .image_rect(swapchain.image_rect()),
531 )
532 .pose(self.options.flat.pose)
533 .radius(curved.radius_m)
534 .central_angle(curved.central_angle())
535 .aspect_ratio(curved.aspect_ratio())
536 .layer_flags(self.options.flat.layer_flags),
537 )
538 }
539 }
540 }
541}
542
543/// Resolves a surface/backend request without accessing an OpenXR instance.
544///
545/// Flat always resolves to [`OpenXrResolvedScreenBackend::Flat`]. Auto-curved
546/// uses cylinder when available and mesh otherwise; explicit backends are kept.
547///
548/// # Examples
549///
550/// ```
551/// use ailloli_ui_openxr::{OpenXrCurvedLayerBackend, OpenXrResolvedScreenBackend, OpenXrScreenSurfaceMode};
552/// use ailloli_ui_openxr::openxr_runtime::composer::resolve_screen_backend;
553/// assert_eq!(resolve_screen_backend(OpenXrScreenSurfaceMode::Curved, OpenXrCurvedLayerBackend::Auto, false), OpenXrResolvedScreenBackend::Mesh);
554/// ```
555pub fn resolve_screen_backend(
556 surface_mode: OpenXrScreenSurfaceMode,
557 curved_backend: OpenXrCurvedLayerBackend,
558 composition_layer_cylinder_supported: bool,
559) -> OpenXrResolvedScreenBackend {
560 match surface_mode {
561 OpenXrScreenSurfaceMode::Flat => OpenXrResolvedScreenBackend::Flat,
562 OpenXrScreenSurfaceMode::Curved => match curved_backend {
563 OpenXrCurvedLayerBackend::Auto => {
564 if composition_layer_cylinder_supported {
565 OpenXrResolvedScreenBackend::Cylinder
566 } else {
567 OpenXrResolvedScreenBackend::Mesh
568 }
569 }
570 OpenXrCurvedLayerBackend::Cylinder => OpenXrResolvedScreenBackend::Cylinder,
571 OpenXrCurvedLayerBackend::Mesh => OpenXrResolvedScreenBackend::Mesh,
572 },
573 }
574}
575
576#[cfg(test)]
577/// Covers defaults, cylinder geometry, and deterministic backend resolution.
578mod tests {
579 use super::*;
580
581 #[test]
582 fn ui_frame_loop_options_default_matches_quad_defaults() {
583 let options = OpenXrUiFrameLoopOptions::default();
584 assert_eq!(options.pixel_width, 1024);
585 assert_eq!(options.pixel_height, 576);
586 assert_eq!(options.clear.to_array(), [0.05, 0.20, 0.80, 1.0]);
587 assert_eq!(options.scale.dpr, 1.0);
588 assert_eq!(options.layer.size.width, 1.6);
589 assert_eq!(options.layer.size.height, 0.9);
590 assert!(options.input.enabled);
591 }
592
593 #[test]
594 fn curved_options_compute_cylinder_geometry() {
595 let options = OpenXrCurvedScreenOptions::default();
596 assert!((options.central_angle() - 0.8).abs() < 1e-6);
597 assert!((options.aspect_ratio() - (16.0 / 9.0)).abs() < 1e-6);
598 }
599
600 #[test]
601 fn auto_curved_backend_prefers_cylinder_when_available() {
602 assert_eq!(
603 resolve_screen_backend(
604 OpenXrScreenSurfaceMode::Curved,
605 OpenXrCurvedLayerBackend::Auto,
606 true,
607 ),
608 OpenXrResolvedScreenBackend::Cylinder
609 );
610 assert_eq!(
611 resolve_screen_backend(
612 OpenXrScreenSurfaceMode::Curved,
613 OpenXrCurvedLayerBackend::Auto,
614 false,
615 ),
616 OpenXrResolvedScreenBackend::Mesh
617 );
618 }
619
620 #[test]
621 fn flat_mode_ignores_curved_backend() {
622 assert_eq!(
623 resolve_screen_backend(
624 OpenXrScreenSurfaceMode::Flat,
625 OpenXrCurvedLayerBackend::Cylinder,
626 true,
627 ),
628 OpenXrResolvedScreenBackend::Flat
629 );
630 }
631}