ailloli_ui_openxr/openxr_runtime/
ui_loop.rs1use std::time::Duration;
4
5use ailloli_ui_core::Size;
6use ailloli_ui_runtime::app::RuntimeHandle;
7use ailloli_ui_runtime::component::IntoView;
8use openxr as xr;
9
10use super::composer::{OpenXrQuadComposer, OpenXrUiFrameLoopOptions};
11use super::error::OpenXrRuntimeError;
12use super::input::OpenXrActionInput;
13use super::session_loop::{combine_render_release, OpenXrRuntime, SessionLoopState};
14use super::swapchain::OpenXrQuadSwapchain;
15use super::ui_layer::{OpenXrExternalHostFrame, OpenXrExternalVulkanContext, OpenXrUiLayer};
16
17impl OpenXrRuntime {
18 pub fn run_ailloli_ui_frame_loop<A: 'static>(
40 &mut self,
41 options: OpenXrUiFrameLoopOptions,
42 runtime_handle: RuntimeHandle<A>,
43 root: impl IntoView<A>,
44 mut shutdown: impl FnMut() -> bool,
45 ) -> Result<(), OpenXrRuntimeError> {
46 let mut swapchain = OpenXrQuadSwapchain::new_with_vulkan_context(
47 &self.session,
48 &self.vk,
49 options.pixel_width,
50 options.pixel_height,
51 )?;
52 let composer = OpenXrQuadComposer::new(options.layer);
53 let mut ui_layer = OpenXrUiLayer::new(runtime_handle, root, options.into())?;
54 let mut input = OpenXrActionInput::new_for_runtime(&self.xr, options.input)?;
55 if let Some(input) = input.as_mut() {
56 input.attach_session(&self.session)?;
57 }
58 let mut event_storage = xr::EventDataBuffer::new();
59 let mut session_state = SessionLoopState::default();
60
61 while !shutdown() {
62 let session_outcome =
63 self.poll_session_events(&mut event_storage, &mut session_state)?;
64 if session_outcome.reset_input {
65 ui_layer.clear_input();
66 if let Some(input) = input.as_mut() {
67 input.clear();
68 }
69 }
70 if session_outcome.exit_requested {
71 return Ok(());
72 }
73
74 if !session_state.running {
75 std::thread::sleep(Duration::from_millis(16));
76 continue;
77 }
78
79 let frame_state = self
80 .frame_waiter
81 .wait()
82 .map_err(|result| OpenXrRuntimeError::FrameWait { result })?;
83 self.frame_stream
84 .begin()
85 .map_err(|result| OpenXrRuntimeError::FrameBegin { result })?;
86
87 if !frame_state.should_render {
88 self.frame_stream
89 .end(frame_state.predicted_display_time, self.blend_mode, &[])
90 .map_err(|result| OpenXrRuntimeError::FrameEnd { result })?;
91 continue;
92 }
93
94 let (logical_width, logical_height) = logical_size(options);
95 let logical_size = Size::new(logical_width, logical_height);
96 let input_frame = if session_state.focused {
97 match input.as_mut() {
98 Some(input) => match input.poll_frame(
99 &self.xr.instance,
100 &self.session,
101 &self.reference_space,
102 options.layer,
103 frame_state.predicted_display_time,
104 logical_size,
105 ) {
106 Ok(frame) => Some(frame.pointer_frame),
107 Err(error) => {
108 let _ = self.frame_stream.end(
109 frame_state.predicted_display_time,
110 self.blend_mode,
111 &[],
112 );
113 return Err(error);
114 }
115 },
116 None => None,
117 }
118 } else {
119 None
120 };
121
122 let acquired = match swapchain.acquire_wait() {
123 Ok(acquired) => acquired,
124 Err(error) => {
125 let _ = self.frame_stream.end(
126 frame_state.predicted_display_time,
127 self.blend_mode,
128 &[],
129 );
130 return Err(error);
131 }
132 };
133
134 let render_result = {
135 let target = swapchain.frame_target(&acquired);
136 let frame = OpenXrExternalHostFrame::new(
137 OpenXrExternalVulkanContext::from(&self.vk),
138 target,
139 )
140 .with_pointer_frame(input_frame.as_ref());
141 ui_layer.layout_paint_render(frame).map(|_| ())
142 };
143 let release_result = swapchain.release();
144 if let Err(error) = combine_render_release(render_result, release_result) {
145 let _ =
146 self.frame_stream
147 .end(frame_state.predicted_display_time, self.blend_mode, &[]);
148 return Err(error);
149 }
150
151 let layer = composer.build_layer(&self.reference_space, &swapchain);
152 let layer_ref: &xr::CompositionLayerBase<'_, xr::Vulkan> = &layer;
153 self.frame_stream
154 .end(
155 frame_state.predicted_display_time,
156 self.blend_mode,
157 &[layer_ref],
158 )
159 .map_err(|result| OpenXrRuntimeError::FrameEnd { result })?;
160 }
161
162 Ok(())
163 }
164}
165
166fn logical_size(options: OpenXrUiFrameLoopOptions) -> (f32, f32) {
168 let dpr = options.scale.dpr.max(0.0001);
169 (
170 options.pixel_width as f32 / dpr,
171 options.pixel_height as f32 / dpr,
172 )
173}