mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
- Run `cargo fmt` on `webxr` and `webxr-api` - Fix clippy warnings in the existing `webxr` code - Integrate the new crates into the workspace - Expose `webxr` via the libservo API rather than requiring embedders to depend on it explicitly. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
507 lines
16 KiB
Rust
507 lines
16 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
use euclid::{Point2D, Rect, RigidTransform3D, Size2D};
|
|
use log::warn;
|
|
#[cfg(feature = "ipc")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
channel, ContextId, DeviceAPI, Error, Event, Floor, Frame, FrameUpdateEvent, HitTestId,
|
|
HitTestSource, InputSource, LayerGrandManager, LayerId, LayerInit, Native, Receiver, Sender,
|
|
Viewport, Viewports,
|
|
};
|
|
|
|
// How long to wait for an rAF.
|
|
static TIMEOUT: Duration = Duration::from_millis(5);
|
|
|
|
/// <https://www.w3.org/TR/webxr/#xrsessionmode-enum>
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
|
|
pub enum SessionMode {
|
|
Inline,
|
|
ImmersiveVR,
|
|
ImmersiveAR,
|
|
}
|
|
|
|
/// <https://immersive-web.github.io/webxr/#dictdef-xrsessioninit>
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
|
|
pub struct SessionInit {
|
|
pub required_features: Vec<String>,
|
|
pub optional_features: Vec<String>,
|
|
/// Secondary views are enabled with the `secondary-view` feature
|
|
/// but for performance reasons we also ask users to enable this pref
|
|
/// for now.
|
|
pub first_person_observer_view: bool,
|
|
}
|
|
|
|
impl SessionInit {
|
|
/// Helper function for validating a list of requested features against
|
|
/// a list of supported features for a given mode
|
|
pub fn validate(&self, mode: SessionMode, supported: &[String]) -> Result<Vec<String>, Error> {
|
|
for f in &self.required_features {
|
|
// viewer and local in immersive are granted by default
|
|
// https://immersive-web.github.io/webxr/#default-features
|
|
if f == "viewer" || (f == "local" && mode != SessionMode::Inline) {
|
|
continue;
|
|
}
|
|
|
|
if !supported.contains(f) {
|
|
return Err(Error::UnsupportedFeature(f.into()));
|
|
}
|
|
}
|
|
let mut granted = self.required_features.clone();
|
|
for f in &self.optional_features {
|
|
if f == "viewer" ||
|
|
(f == "local" && mode != SessionMode::Inline) ||
|
|
supported.contains(f)
|
|
{
|
|
granted.push(f.clone());
|
|
}
|
|
}
|
|
|
|
Ok(granted)
|
|
}
|
|
|
|
pub fn feature_requested(&self, f: &str) -> bool {
|
|
self.required_features
|
|
.iter()
|
|
.chain(self.optional_features.iter())
|
|
.any(|x| *x == f)
|
|
}
|
|
}
|
|
|
|
/// <https://immersive-web.github.io/webxr-ar-module/#xrenvironmentblendmode-enum>
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
|
|
pub enum EnvironmentBlendMode {
|
|
Opaque,
|
|
AlphaBlend,
|
|
Additive,
|
|
}
|
|
|
|
// The messages that are sent from the content thread to the session thread.
|
|
#[derive(Debug)]
|
|
#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
|
|
enum SessionMsg {
|
|
CreateLayer(ContextId, LayerInit, Sender<Result<LayerId, Error>>),
|
|
DestroyLayer(ContextId, LayerId),
|
|
SetLayers(Vec<(ContextId, LayerId)>),
|
|
SetEventDest(Sender<Event>),
|
|
UpdateClipPlanes(/* near */ f32, /* far */ f32),
|
|
StartRenderLoop,
|
|
RenderAnimationFrame,
|
|
RequestHitTest(HitTestSource),
|
|
CancelHitTest(HitTestId),
|
|
UpdateFrameRate(f32, Sender<f32>),
|
|
Quit,
|
|
GetBoundsGeometry(Sender<Option<Vec<Point2D<f32, Floor>>>>),
|
|
}
|
|
|
|
#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
|
|
#[derive(Clone)]
|
|
pub struct Quitter {
|
|
sender: Sender<SessionMsg>,
|
|
}
|
|
|
|
impl Quitter {
|
|
pub fn quit(&self) {
|
|
let _ = self.sender.send(SessionMsg::Quit);
|
|
}
|
|
}
|
|
|
|
/// An object that represents an XR session.
|
|
/// This is owned by the content thread.
|
|
/// <https://www.w3.org/TR/webxr/#xrsession-interface>
|
|
#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
|
|
pub struct Session {
|
|
floor_transform: Option<RigidTransform3D<f32, Native, Floor>>,
|
|
viewports: Viewports,
|
|
sender: Sender<SessionMsg>,
|
|
environment_blend_mode: EnvironmentBlendMode,
|
|
initial_inputs: Vec<InputSource>,
|
|
granted_features: Vec<String>,
|
|
id: SessionId,
|
|
supported_frame_rates: Vec<f32>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
#[cfg_attr(feature = "ipc", derive(Deserialize, Serialize))]
|
|
pub struct SessionId(pub(crate) u32);
|
|
|
|
impl Session {
|
|
pub fn id(&self) -> SessionId {
|
|
self.id
|
|
}
|
|
|
|
pub fn floor_transform(&self) -> Option<RigidTransform3D<f32, Native, Floor>> {
|
|
self.floor_transform
|
|
}
|
|
|
|
pub fn reference_space_bounds(&self) -> Option<Vec<Point2D<f32, Floor>>> {
|
|
let (sender, receiver) = channel().ok()?;
|
|
let _ = self.sender.send(SessionMsg::GetBoundsGeometry(sender));
|
|
receiver.recv().ok()?
|
|
}
|
|
|
|
pub fn initial_inputs(&self) -> &[InputSource] {
|
|
&self.initial_inputs
|
|
}
|
|
|
|
pub fn environment_blend_mode(&self) -> EnvironmentBlendMode {
|
|
self.environment_blend_mode
|
|
}
|
|
|
|
pub fn viewports(&self) -> &[Rect<i32, Viewport>] {
|
|
&self.viewports.viewports
|
|
}
|
|
|
|
/// A resolution large enough to contain all the viewports.
|
|
/// <https://immersive-web.github.io/webxr/#recommended-webgl-framebuffer-resolution>
|
|
///
|
|
/// Returns None if the session is inline
|
|
pub fn recommended_framebuffer_resolution(&self) -> Option<Size2D<i32, Viewport>> {
|
|
self.viewports()
|
|
.iter()
|
|
.fold(None::<Rect<_, _>>, |acc, vp| {
|
|
Some(acc.map(|a| a.union(vp)).unwrap_or(*vp))
|
|
})
|
|
.map(|rect| Size2D::new(rect.max_x(), rect.max_y()))
|
|
}
|
|
|
|
pub fn create_layer(&self, context_id: ContextId, init: LayerInit) -> Result<LayerId, Error> {
|
|
let (sender, receiver) = channel().map_err(|_| Error::CommunicationError)?;
|
|
let _ = self
|
|
.sender
|
|
.send(SessionMsg::CreateLayer(context_id, init, sender));
|
|
receiver.recv().map_err(|_| Error::CommunicationError)?
|
|
}
|
|
|
|
/// Destroy a layer
|
|
pub fn destroy_layer(&self, context_id: ContextId, layer_id: LayerId) {
|
|
let _ = self
|
|
.sender
|
|
.send(SessionMsg::DestroyLayer(context_id, layer_id));
|
|
}
|
|
|
|
pub fn set_layers(&self, layers: Vec<(ContextId, LayerId)>) {
|
|
let _ = self.sender.send(SessionMsg::SetLayers(layers));
|
|
}
|
|
|
|
pub fn start_render_loop(&mut self) {
|
|
let _ = self.sender.send(SessionMsg::StartRenderLoop);
|
|
}
|
|
|
|
pub fn update_clip_planes(&mut self, near: f32, far: f32) {
|
|
let _ = self.sender.send(SessionMsg::UpdateClipPlanes(near, far));
|
|
}
|
|
|
|
pub fn set_event_dest(&mut self, dest: Sender<Event>) {
|
|
let _ = self.sender.send(SessionMsg::SetEventDest(dest));
|
|
}
|
|
|
|
pub fn render_animation_frame(&mut self) {
|
|
let _ = self.sender.send(SessionMsg::RenderAnimationFrame);
|
|
}
|
|
|
|
pub fn end_session(&mut self) {
|
|
let _ = self.sender.send(SessionMsg::Quit);
|
|
}
|
|
|
|
pub fn apply_event(&mut self, event: FrameUpdateEvent) {
|
|
match event {
|
|
FrameUpdateEvent::UpdateFloorTransform(floor) => self.floor_transform = floor,
|
|
FrameUpdateEvent::UpdateViewports(vp) => self.viewports = vp,
|
|
FrameUpdateEvent::HitTestSourceAdded(_) => (),
|
|
}
|
|
}
|
|
|
|
pub fn granted_features(&self) -> &[String] {
|
|
&self.granted_features
|
|
}
|
|
|
|
pub fn request_hit_test(&self, source: HitTestSource) {
|
|
let _ = self.sender.send(SessionMsg::RequestHitTest(source));
|
|
}
|
|
|
|
pub fn cancel_hit_test(&self, id: HitTestId) {
|
|
let _ = self.sender.send(SessionMsg::CancelHitTest(id));
|
|
}
|
|
|
|
pub fn update_frame_rate(&mut self, rate: f32, sender: Sender<f32>) {
|
|
let _ = self.sender.send(SessionMsg::UpdateFrameRate(rate, sender));
|
|
}
|
|
|
|
pub fn supported_frame_rates(&self) -> &[f32] {
|
|
&self.supported_frame_rates
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
enum RenderState {
|
|
NotInRenderLoop,
|
|
InRenderLoop,
|
|
PendingQuit,
|
|
}
|
|
|
|
/// For devices that want to do their own thread management, the `SessionThread` type is exposed.
|
|
pub struct SessionThread<Device> {
|
|
receiver: Receiver<SessionMsg>,
|
|
sender: Sender<SessionMsg>,
|
|
layers: Vec<(ContextId, LayerId)>,
|
|
pending_layers: Option<Vec<(ContextId, LayerId)>>,
|
|
frame_count: u64,
|
|
frame_sender: Sender<Frame>,
|
|
running: bool,
|
|
device: Device,
|
|
id: SessionId,
|
|
render_state: RenderState,
|
|
}
|
|
|
|
impl<Device> SessionThread<Device>
|
|
where
|
|
Device: DeviceAPI,
|
|
{
|
|
pub fn new(
|
|
mut device: Device,
|
|
frame_sender: Sender<Frame>,
|
|
id: SessionId,
|
|
) -> Result<Self, Error> {
|
|
let (sender, receiver) = crate::channel().or(Err(Error::CommunicationError))?;
|
|
device.set_quitter(Quitter {
|
|
sender: sender.clone(),
|
|
});
|
|
let frame_count = 0;
|
|
let running = true;
|
|
let layers = Vec::new();
|
|
let pending_layers = None;
|
|
Ok(SessionThread {
|
|
sender,
|
|
receiver,
|
|
device,
|
|
layers,
|
|
pending_layers,
|
|
frame_count,
|
|
frame_sender,
|
|
running,
|
|
id,
|
|
render_state: RenderState::NotInRenderLoop,
|
|
})
|
|
}
|
|
|
|
pub fn new_session(&mut self) -> Session {
|
|
let floor_transform = self.device.floor_transform();
|
|
let viewports = self.device.viewports();
|
|
let sender = self.sender.clone();
|
|
let initial_inputs = self.device.initial_inputs();
|
|
let environment_blend_mode = self.device.environment_blend_mode();
|
|
let granted_features = self.device.granted_features().into();
|
|
let supported_frame_rates = self.device.supported_frame_rates();
|
|
Session {
|
|
floor_transform,
|
|
viewports,
|
|
sender,
|
|
initial_inputs,
|
|
environment_blend_mode,
|
|
granted_features,
|
|
id: self.id,
|
|
supported_frame_rates,
|
|
}
|
|
}
|
|
|
|
pub fn run(&mut self) {
|
|
while let Ok(msg) = self.receiver.recv() {
|
|
if !self.handle_msg(msg) {
|
|
self.running = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_msg(&mut self, msg: SessionMsg) -> bool {
|
|
log::debug!("processing {:?}", msg);
|
|
match msg {
|
|
SessionMsg::SetEventDest(dest) => {
|
|
self.device.set_event_dest(dest);
|
|
},
|
|
SessionMsg::RequestHitTest(source) => {
|
|
self.device.request_hit_test(source);
|
|
},
|
|
SessionMsg::CancelHitTest(id) => {
|
|
self.device.cancel_hit_test(id);
|
|
},
|
|
SessionMsg::CreateLayer(context_id, layer_init, sender) => {
|
|
let result = self.device.create_layer(context_id, layer_init);
|
|
let _ = sender.send(result);
|
|
},
|
|
SessionMsg::DestroyLayer(context_id, layer_id) => {
|
|
self.layers.retain(|&(_, other_id)| layer_id != other_id);
|
|
self.device.destroy_layer(context_id, layer_id);
|
|
},
|
|
SessionMsg::SetLayers(layers) => {
|
|
self.pending_layers = Some(layers);
|
|
},
|
|
SessionMsg::StartRenderLoop => {
|
|
if let Some(layers) = self.pending_layers.take() {
|
|
self.layers = layers;
|
|
}
|
|
let frame = match self.device.begin_animation_frame(&self.layers[..]) {
|
|
Some(frame) => frame,
|
|
None => {
|
|
warn!("Device stopped providing frames, exiting");
|
|
return false;
|
|
},
|
|
};
|
|
self.render_state = RenderState::InRenderLoop;
|
|
let _ = self.frame_sender.send(frame);
|
|
},
|
|
SessionMsg::UpdateClipPlanes(near, far) => self.device.update_clip_planes(near, far),
|
|
SessionMsg::RenderAnimationFrame => {
|
|
self.frame_count += 1;
|
|
|
|
self.device.end_animation_frame(&self.layers[..]);
|
|
|
|
if self.render_state == RenderState::PendingQuit {
|
|
self.quit();
|
|
return false;
|
|
}
|
|
|
|
if let Some(layers) = self.pending_layers.take() {
|
|
self.layers = layers;
|
|
}
|
|
#[allow(unused_mut)]
|
|
let mut frame = match self.device.begin_animation_frame(&self.layers[..]) {
|
|
Some(frame) => frame,
|
|
None => {
|
|
warn!("Device stopped providing frames, exiting");
|
|
return false;
|
|
},
|
|
};
|
|
|
|
let _ = self.frame_sender.send(frame);
|
|
},
|
|
SessionMsg::UpdateFrameRate(rate, sender) => {
|
|
let new_framerate = self.device.update_frame_rate(rate);
|
|
let _ = sender.send(new_framerate);
|
|
},
|
|
SessionMsg::Quit => {
|
|
if self.render_state == RenderState::NotInRenderLoop {
|
|
self.quit();
|
|
return false;
|
|
} else {
|
|
self.render_state = RenderState::PendingQuit;
|
|
}
|
|
},
|
|
SessionMsg::GetBoundsGeometry(sender) => {
|
|
let bounds = self.device.reference_space_bounds();
|
|
let _ = sender.send(bounds);
|
|
},
|
|
}
|
|
true
|
|
}
|
|
|
|
fn quit(&mut self) {
|
|
self.render_state = RenderState::NotInRenderLoop;
|
|
self.device.quit();
|
|
}
|
|
}
|
|
|
|
/// Devices that need to can run sessions on the main thread.
|
|
pub trait MainThreadSession: 'static {
|
|
fn run_one_frame(&mut self);
|
|
fn running(&self) -> bool;
|
|
}
|
|
|
|
impl<Device> MainThreadSession for SessionThread<Device>
|
|
where
|
|
Device: DeviceAPI,
|
|
{
|
|
fn run_one_frame(&mut self) {
|
|
let frame_count = self.frame_count;
|
|
while frame_count == self.frame_count && self.running {
|
|
if let Ok(msg) = crate::recv_timeout(&self.receiver, TIMEOUT) {
|
|
self.running = self.handle_msg(msg);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn running(&self) -> bool {
|
|
self.running
|
|
}
|
|
}
|
|
|
|
/// A type for building XR sessions
|
|
pub struct SessionBuilder<'a, GL> {
|
|
sessions: &'a mut Vec<Box<dyn MainThreadSession>>,
|
|
frame_sender: Sender<Frame>,
|
|
layer_grand_manager: LayerGrandManager<GL>,
|
|
id: SessionId,
|
|
}
|
|
|
|
impl<'a, GL: 'static> SessionBuilder<'a, GL> {
|
|
pub fn id(&self) -> SessionId {
|
|
self.id
|
|
}
|
|
|
|
pub(crate) fn new(
|
|
sessions: &'a mut Vec<Box<dyn MainThreadSession>>,
|
|
frame_sender: Sender<Frame>,
|
|
layer_grand_manager: LayerGrandManager<GL>,
|
|
id: SessionId,
|
|
) -> Self {
|
|
SessionBuilder {
|
|
sessions,
|
|
frame_sender,
|
|
layer_grand_manager,
|
|
id,
|
|
}
|
|
}
|
|
|
|
/// For devices which are happy to hand over thread management to webxr.
|
|
pub fn spawn<Device, Factory>(self, factory: Factory) -> Result<Session, Error>
|
|
where
|
|
Factory: 'static + FnOnce(LayerGrandManager<GL>) -> Result<Device, Error> + Send,
|
|
Device: DeviceAPI,
|
|
{
|
|
let (acks, ackr) = crate::channel().or(Err(Error::CommunicationError))?;
|
|
let frame_sender = self.frame_sender;
|
|
let layer_grand_manager = self.layer_grand_manager;
|
|
let id = self.id;
|
|
thread::spawn(move || {
|
|
match factory(layer_grand_manager)
|
|
.and_then(|device| SessionThread::new(device, frame_sender, id))
|
|
{
|
|
Ok(mut thread) => {
|
|
let session = thread.new_session();
|
|
let _ = acks.send(Ok(session));
|
|
thread.run();
|
|
},
|
|
Err(err) => {
|
|
let _ = acks.send(Err(err));
|
|
},
|
|
}
|
|
});
|
|
ackr.recv().unwrap_or(Err(Error::CommunicationError))
|
|
}
|
|
|
|
/// For devices that need to run on the main thread.
|
|
pub fn run_on_main_thread<Device, Factory>(self, factory: Factory) -> Result<Session, Error>
|
|
where
|
|
Factory: 'static + FnOnce(LayerGrandManager<GL>) -> Result<Device, Error>,
|
|
Device: DeviceAPI,
|
|
{
|
|
let device = factory(self.layer_grand_manager)?;
|
|
let frame_sender = self.frame_sender;
|
|
let mut session_thread = SessionThread::new(device, frame_sender, self.id)?;
|
|
let session = session_thread.new_session();
|
|
self.sessions.push(Box::new(session_thread));
|
|
Ok(session)
|
|
}
|
|
}
|