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