webxr/headless/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Mutex};
7use std::thread;
8
9use base::generic_channel::GenericReceiver;
10use euclid::{Point2D, RigidTransform3D};
11use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
12use surfman::chains::SwapChains;
13use webxr_api::util::{self, ClipPlanes, HitTestList};
14use webxr_api::{
15    ApiSpace, BaseSpace, ContextId, DeviceAPI, DiscoveryAPI, Error, Event, EventBuffer, Floor,
16    Frame, FrameUpdateEvent, HitTestId, HitTestResult, HitTestSource, Input, InputFrame, InputId,
17    InputSource, LayerGrandManager, LayerId, LayerInit, LayerManager, MockButton, MockDeviceInit,
18    MockDeviceMsg, MockDiscoveryAPI, MockInputMsg, MockViewInit, MockViewsInit, MockWorld, Native,
19    Quitter, Ray, SelectEvent, SelectKind, Session, SessionBuilder, SessionInit, SessionMode,
20    Space, SubImages, View, Viewer, ViewerPose, Viewports, Views,
21};
22
23use crate::{SurfmanGL, SurfmanLayerManager};
24
25pub struct HeadlessMockDiscovery {
26    enabled: Arc<AtomicBool>,
27}
28
29impl HeadlessMockDiscovery {
30    pub fn new(enabled: Arc<AtomicBool>) -> Self {
31        Self { enabled }
32    }
33}
34
35struct HeadlessDiscovery {
36    data: Arc<Mutex<HeadlessDeviceData>>,
37    supports_vr: bool,
38    supports_inline: bool,
39    supports_ar: bool,
40}
41
42struct InputInfo {
43    source: InputSource,
44    active: bool,
45    pointer: Option<RigidTransform3D<f32, Input, Native>>,
46    grip: Option<RigidTransform3D<f32, Input, Native>>,
47    clicking: bool,
48    buttons: Vec<MockButton>,
49}
50
51struct HeadlessDevice {
52    data: Arc<Mutex<HeadlessDeviceData>>,
53    id: u32,
54    hit_tests: HitTestList,
55    granted_features: Vec<String>,
56    grand_manager: LayerGrandManager<SurfmanGL>,
57    layer_manager: Option<LayerManager>,
58}
59
60struct PerSessionData {
61    id: u32,
62    mode: SessionMode,
63    clip_planes: ClipPlanes,
64    quitter: Option<Quitter>,
65    events: EventBuffer,
66    needs_vp_update: bool,
67}
68
69struct HeadlessDeviceData {
70    floor_transform: Option<RigidTransform3D<f32, Native, Floor>>,
71    viewer_origin: Option<RigidTransform3D<f32, Viewer, Native>>,
72    supported_features: Vec<String>,
73    views: MockViewsInit,
74    needs_floor_update: bool,
75    inputs: Vec<InputInfo>,
76    sessions: Vec<PerSessionData>,
77    disconnected: bool,
78    world: Option<MockWorld>,
79    next_id: u32,
80    bounds_geometry: Vec<Point2D<f32, Floor>>,
81}
82
83impl MockDiscoveryAPI<SurfmanGL> for HeadlessMockDiscovery {
84    fn simulate_device_connection(
85        &mut self,
86        init: MockDeviceInit,
87        receiver: GenericReceiver<MockDeviceMsg>,
88    ) -> Result<Box<dyn DiscoveryAPI<SurfmanGL>>, Error> {
89        if !self.enabled.load(Ordering::Relaxed) {
90            return Err(Error::NoMatchingDevice);
91        }
92
93        let viewer_origin = init.viewer_origin;
94        let floor_transform = init.floor_origin.map(|f| f.inverse());
95        let views = init.views.clone();
96        let data = HeadlessDeviceData {
97            floor_transform,
98            viewer_origin,
99            supported_features: init.supported_features,
100            views,
101            needs_floor_update: false,
102            inputs: vec![],
103            sessions: vec![],
104            disconnected: false,
105            world: init.world,
106            next_id: 0,
107            bounds_geometry: vec![],
108        };
109        let data = Arc::new(Mutex::new(data));
110        let data_ = data.clone();
111
112        thread::spawn(move || {
113            run_loop(receiver, data_);
114        });
115        Ok(Box::new(HeadlessDiscovery {
116            data,
117            supports_vr: init.supports_vr,
118            supports_inline: init.supports_inline,
119            supports_ar: init.supports_ar,
120        }))
121    }
122}
123
124fn run_loop(receiver: GenericReceiver<MockDeviceMsg>, data: Arc<Mutex<HeadlessDeviceData>>) {
125    while let Ok(msg) = receiver.recv() {
126        if !data.lock().expect("Mutex poisoned").handle_msg(msg) {
127            break;
128        }
129    }
130}
131
132impl DiscoveryAPI<SurfmanGL> for HeadlessDiscovery {
133    fn request_session(
134        &mut self,
135        mode: SessionMode,
136        init: &SessionInit,
137        xr: SessionBuilder<SurfmanGL>,
138    ) -> Result<Session, Error> {
139        if !self.supports_session(mode) {
140            return Err(Error::NoMatchingDevice);
141        }
142        let data = self.data.clone();
143        let mut d = data.lock().unwrap();
144        let id = d.next_id;
145        d.next_id += 1;
146        let per_session = PerSessionData {
147            id,
148            mode,
149            clip_planes: Default::default(),
150            quitter: Default::default(),
151            events: Default::default(),
152            needs_vp_update: false,
153        };
154        d.sessions.push(per_session);
155
156        let granted_features = init.validate(mode, &d.supported_features)?;
157        let layer_manager = None;
158        drop(d);
159        xr.spawn(move |grand_manager| {
160            Ok(HeadlessDevice {
161                data,
162                id,
163                granted_features,
164                hit_tests: HitTestList::default(),
165                grand_manager,
166                layer_manager,
167            })
168        })
169    }
170
171    fn supports_session(&self, mode: SessionMode) -> bool {
172        if self.data.lock().unwrap().disconnected {
173            return false;
174        }
175        match mode {
176            SessionMode::Inline => self.supports_inline,
177            SessionMode::ImmersiveVR => self.supports_vr,
178            SessionMode::ImmersiveAR => self.supports_ar,
179        }
180    }
181}
182
183fn view<Eye>(
184    init: MockViewInit<Eye>,
185    viewer: RigidTransform3D<f32, Viewer, Native>,
186    clip_planes: ClipPlanes,
187) -> View<Eye> {
188    let projection = if let Some((l, r, t, b)) = init.fov {
189        util::fov_to_projection_matrix(l, r, t, b, clip_planes)
190    } else {
191        init.projection
192    };
193
194    View {
195        transform: init.transform.inverse().then(&viewer),
196        projection,
197    }
198}
199
200impl HeadlessDevice {
201    fn with_per_session<R>(&self, f: impl FnOnce(&mut PerSessionData) -> R) -> R {
202        f(self
203            .data
204            .lock()
205            .unwrap()
206            .sessions
207            .iter_mut()
208            .find(|s| s.id == self.id)
209            .unwrap())
210    }
211
212    fn layer_manager(&mut self) -> Result<&mut LayerManager, Error> {
213        if let Some(ref mut manager) = self.layer_manager {
214            return Ok(manager);
215        }
216        let swap_chains = SwapChains::new();
217        let viewports = self.viewports();
218        let layer_manager = self
219            .grand_manager
220            .create_layer_manager(move |_| Ok(SurfmanLayerManager::new(viewports, swap_chains)))?;
221        self.layer_manager = Some(layer_manager);
222        Ok(self.layer_manager.as_mut().unwrap())
223    }
224}
225
226impl DeviceAPI for HeadlessDevice {
227    fn floor_transform(&self) -> Option<RigidTransform3D<f32, Native, Floor>> {
228        self.data.lock().unwrap().floor_transform
229    }
230
231    fn viewports(&self) -> Viewports {
232        let d = self.data.lock().unwrap();
233        let per_session = d.sessions.iter().find(|s| s.id == self.id).unwrap();
234        d.viewports(per_session.mode)
235    }
236
237    fn create_layer(&mut self, context_id: ContextId, init: LayerInit) -> Result<LayerId, Error> {
238        self.layer_manager()?.create_layer(context_id, init)
239    }
240
241    fn destroy_layer(&mut self, context_id: ContextId, layer_id: LayerId) {
242        self.layer_manager()
243            .unwrap()
244            .destroy_layer(context_id, layer_id)
245    }
246
247    fn begin_animation_frame(&mut self, layers: &[(ContextId, LayerId)]) -> Option<Frame> {
248        let sub_images = self.layer_manager().ok()?.begin_frame(layers).ok()?;
249        let mut data = self.data.lock().unwrap();
250        let mut frame = data.get_frame(
251            data.sessions.iter().find(|s| s.id == self.id).unwrap(),
252            sub_images,
253        );
254        let per_session = data.sessions.iter_mut().find(|s| s.id == self.id).unwrap();
255        if per_session.needs_vp_update {
256            per_session.needs_vp_update = false;
257            let mode = per_session.mode;
258            let vp = data.viewports(mode);
259            frame.events.push(FrameUpdateEvent::UpdateViewports(vp));
260        }
261        let events = self.hit_tests.commit_tests();
262        frame.events = events;
263
264        if let Some(ref world) = data.world {
265            for source in self.hit_tests.tests() {
266                let ray = data.native_ray(source.ray, source.space);
267                let ray = if let Some(ray) = ray { ray } else { break };
268                let hits = world
269                    .regions
270                    .iter()
271                    .filter(|region| source.types.is_type(region.ty))
272                    .flat_map(|region| &region.faces)
273                    .filter_map(|triangle| triangle.intersect(ray))
274                    .map(|space| HitTestResult {
275                        space,
276                        id: source.id,
277                    });
278                frame.hit_test_results.extend(hits);
279            }
280        }
281
282        if data.needs_floor_update {
283            frame
284                .events
285                .push(FrameUpdateEvent::UpdateFloorTransform(data.floor_transform));
286            data.needs_floor_update = false;
287        }
288        Some(frame)
289    }
290
291    fn end_animation_frame(&mut self, layers: &[(ContextId, LayerId)]) {
292        let _ = self.layer_manager().unwrap().end_frame(layers);
293        thread::sleep(std::time::Duration::from_millis(20));
294    }
295
296    fn initial_inputs(&self) -> Vec<InputSource> {
297        vec![]
298    }
299
300    fn set_event_dest(&mut self, dest: ProfileGenericCallback<Event>) {
301        self.with_per_session(|s| s.events.upgrade(dest))
302    }
303
304    fn quit(&mut self) {
305        self.with_per_session(|s| s.events.callback(Event::SessionEnd))
306    }
307
308    fn set_quitter(&mut self, quitter: Quitter) {
309        self.with_per_session(|s| s.quitter = Some(quitter))
310    }
311
312    fn update_clip_planes(&mut self, near: f32, far: f32) {
313        self.with_per_session(|s| s.clip_planes.update(near, far));
314    }
315
316    fn granted_features(&self) -> &[String] {
317        &self.granted_features
318    }
319
320    fn request_hit_test(&mut self, source: HitTestSource) {
321        self.hit_tests.request_hit_test(source)
322    }
323
324    fn cancel_hit_test(&mut self, id: HitTestId) {
325        self.hit_tests.cancel_hit_test(id)
326    }
327
328    fn reference_space_bounds(&self) -> Option<Vec<Point2D<f32, Floor>>> {
329        let bounds = self.data.lock().unwrap().bounds_geometry.clone();
330        Some(bounds)
331    }
332}
333
334macro_rules! with_all_sessions {
335    ($self:ident, |$s:ident| $e:expr) => {
336        for $s in &mut $self.sessions {
337            $e;
338        }
339    };
340}
341
342impl HeadlessDeviceData {
343    fn get_frame(&self, s: &PerSessionData, sub_images: Vec<SubImages>) -> Frame {
344        let views = self.views.clone();
345
346        let pose = self.viewer_origin.map(|transform| {
347            let views = if s.mode == SessionMode::Inline {
348                Views::Inline
349            } else {
350                match views {
351                    MockViewsInit::Mono(one) => Views::Mono(view(one, transform, s.clip_planes)),
352                    MockViewsInit::Stereo(one, two) => Views::Stereo(
353                        view(one, transform, s.clip_planes),
354                        view(two, transform, s.clip_planes),
355                    ),
356                }
357            };
358
359            ViewerPose { transform, views }
360        });
361        let inputs = self
362            .inputs
363            .iter()
364            .filter(|i| i.active)
365            .map(|i| InputFrame {
366                id: i.source.id,
367                target_ray_origin: i.pointer,
368                grip_origin: i.grip,
369                pressed: false,
370                squeezed: false,
371                hand: None,
372                button_values: vec![],
373                axis_values: vec![],
374                input_changed: false,
375            })
376            .collect();
377        Frame {
378            pose,
379            inputs,
380            events: vec![],
381            sub_images,
382            hit_test_results: vec![],
383            predicted_display_time: 0.0,
384        }
385    }
386
387    fn viewports(&self, mode: SessionMode) -> Viewports {
388        let vec = if mode == SessionMode::Inline {
389            vec![]
390        } else {
391            match &self.views {
392                MockViewsInit::Mono(one) => vec![one.viewport],
393                MockViewsInit::Stereo(one, two) => vec![one.viewport, two.viewport],
394            }
395        };
396        Viewports { viewports: vec }
397    }
398
399    fn trigger_select(&mut self, id: InputId, kind: SelectKind, event: SelectEvent) {
400        for i in 0..self.sessions.len() {
401            let frame = self.get_frame(&self.sessions[i], Vec::new());
402            self.sessions[i]
403                .events
404                .callback(Event::Select(id, kind, event, frame));
405        }
406    }
407
408    fn handle_msg(&mut self, msg: MockDeviceMsg) -> bool {
409        match msg {
410            MockDeviceMsg::SetWorld(w) => self.world = Some(w),
411            MockDeviceMsg::ClearWorld => self.world = None,
412            MockDeviceMsg::SetViewerOrigin(viewer_origin) => {
413                self.viewer_origin = viewer_origin;
414            },
415            MockDeviceMsg::SetFloorOrigin(floor_origin) => {
416                self.floor_transform = floor_origin.map(|f| f.inverse());
417                self.needs_floor_update = true;
418            },
419            MockDeviceMsg::SetViews(views) => {
420                self.views = views;
421                with_all_sessions!(self, |s| {
422                    s.needs_vp_update = true;
423                })
424            },
425            MockDeviceMsg::VisibilityChange(v) => {
426                with_all_sessions!(self, |s| s.events.callback(Event::VisibilityChange(v)))
427            },
428            MockDeviceMsg::AddInputSource(init) => {
429                self.inputs.push(InputInfo {
430                    source: init.source.clone(),
431                    pointer: init.pointer_origin,
432                    grip: init.grip_origin,
433                    active: true,
434                    clicking: false,
435                    buttons: init.supported_buttons,
436                });
437                with_all_sessions!(self, |s| s
438                    .events
439                    .callback(Event::AddInput(init.source.clone())))
440            },
441            MockDeviceMsg::MessageInputSource(id, msg) => {
442                if let Some(ref mut input) = self.inputs.iter_mut().find(|i| i.source.id == id) {
443                    match msg {
444                        MockInputMsg::SetHandedness(h) => {
445                            input.source.handedness = h;
446                            with_all_sessions!(self, |s| {
447                                s.events
448                                    .callback(Event::UpdateInput(id, input.source.clone()))
449                            });
450                        },
451                        MockInputMsg::SetProfiles(p) => {
452                            input.source.profiles = p;
453                            with_all_sessions!(self, |s| {
454                                s.events
455                                    .callback(Event::UpdateInput(id, input.source.clone()))
456                            });
457                        },
458                        MockInputMsg::SetTargetRayMode(t) => {
459                            input.source.target_ray_mode = t;
460                            with_all_sessions!(self, |s| {
461                                s.events
462                                    .callback(Event::UpdateInput(id, input.source.clone()))
463                            });
464                        },
465                        MockInputMsg::SetPointerOrigin(p) => input.pointer = p,
466                        MockInputMsg::SetGripOrigin(p) => input.grip = p,
467                        MockInputMsg::TriggerSelect(kind, event) => {
468                            if !input.active {
469                                return true;
470                            }
471                            let clicking = input.clicking;
472                            input.clicking = event == SelectEvent::Start;
473                            match event {
474                                SelectEvent::Start => {
475                                    self.trigger_select(id, kind, event);
476                                },
477                                SelectEvent::End => {
478                                    if clicking {
479                                        self.trigger_select(id, kind, SelectEvent::Select);
480                                    } else {
481                                        self.trigger_select(id, kind, SelectEvent::End);
482                                    }
483                                },
484                                SelectEvent::Select => {
485                                    self.trigger_select(id, kind, SelectEvent::Start);
486                                    self.trigger_select(id, kind, SelectEvent::Select);
487                                },
488                            }
489                        },
490                        MockInputMsg::Disconnect => {
491                            if input.active {
492                                with_all_sessions!(self, |s| s
493                                    .events
494                                    .callback(Event::RemoveInput(input.source.id)));
495                                input.active = false;
496                                input.clicking = false;
497                            }
498                        },
499                        MockInputMsg::Reconnect => {
500                            if !input.active {
501                                with_all_sessions!(self, |s| s
502                                    .events
503                                    .callback(Event::AddInput(input.source.clone())));
504                                input.active = true;
505                            }
506                        },
507                        MockInputMsg::SetSupportedButtons(buttons) => {
508                            input.buttons = buttons;
509                            with_all_sessions!(self, |s| s.events.callback(Event::UpdateInput(
510                                input.source.id,
511                                input.source.clone()
512                            )));
513                        },
514                        MockInputMsg::UpdateButtonState(state) => {
515                            if let Some(button) = input
516                                .buttons
517                                .iter_mut()
518                                .find(|b| b.button_type == state.button_type)
519                            {
520                                *button = state;
521                            }
522                        },
523                    }
524                }
525            },
526            MockDeviceMsg::Disconnect(s) => {
527                self.disconnected = true;
528                with_all_sessions!(self, |s| s.quitter.as_ref().map(|q| q.quit()));
529                // notify the client that we're done disconnecting
530                let _ = s.send(());
531                return false;
532            },
533            MockDeviceMsg::SetBoundsGeometry(g) => {
534                self.bounds_geometry = g;
535            },
536            MockDeviceMsg::SimulateResetPose => {
537                with_all_sessions!(self, |s| s.events.callback(Event::ReferenceSpaceChanged(
538                    BaseSpace::Local,
539                    RigidTransform3D::identity()
540                )));
541            },
542        }
543        true
544    }
545
546    fn native_ray(&self, ray: Ray<ApiSpace>, space: Space) -> Option<Ray<Native>> {
547        let origin: RigidTransform3D<f32, ApiSpace, Native> = match space.base {
548            BaseSpace::Local => RigidTransform3D::identity(),
549            BaseSpace::Floor => self.floor_transform?.inverse().cast_unit(),
550            BaseSpace::Viewer => self.viewer_origin?.cast_unit(),
551            BaseSpace::BoundedFloor => self.floor_transform?.inverse().cast_unit(),
552            BaseSpace::TargetRay(id) => self
553                .inputs
554                .iter()
555                .find(|i| i.source.id == id)?
556                .pointer?
557                .cast_unit(),
558            BaseSpace::Grip(id) => self
559                .inputs
560                .iter()
561                .find(|i| i.source.id == id)?
562                .grip?
563                .cast_unit(),
564            BaseSpace::Joint(..) => panic!("Cannot request mocking backend with hands"),
565        };
566        let space_origin = space.offset.then(&origin);
567
568        let origin_rigid: RigidTransform3D<f32, ApiSpace, ApiSpace> = ray.origin.into();
569        Some(Ray {
570            origin: origin_rigid.then(&space_origin).translation,
571            direction: space_origin.rotation.transform_vector3d(ray.direction),
572        })
573    }
574}