diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 5377c028e4a..8736eb35ad1 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -13,6 +13,7 @@ path = "lib.rs" [features] webgl_backtrace = ["canvas_traits/webgl_backtrace"] +webxr = ["dep:webxr", "dep:webxr-api"] [dependencies] app_units = { workspace = true } @@ -46,5 +47,5 @@ unicode-script = { workspace = true } webrender = { workspace = true } webrender_api = { workspace = true } webrender_traits = { workspace = true } -webxr = { workspace = true, features = ["ipc"] } -webxr-api = { workspace = true, features = ["ipc"] } +webxr = { workspace = true, features = ["ipc"], optional = true } +webxr-api = { workspace = true, features = ["ipc"], optional = true } diff --git a/components/canvas/lib.rs b/components/canvas/lib.rs index 5a1716fcd49..d2c62c1d8b6 100644 --- a/components/canvas/lib.rs +++ b/components/canvas/lib.rs @@ -13,3 +13,5 @@ pub mod canvas_paint_thread; mod webgl_limits; mod webgl_mode; pub mod webgl_thread; +#[cfg(feature = "webxr")] +mod webxr; diff --git a/components/canvas/webgl_mode/inprocess.rs b/components/canvas/webgl_mode/inprocess.rs index eb627d5ca99..5a63dfb66c1 100644 --- a/components/canvas/webgl_mode/inprocess.rs +++ b/components/canvas/webgl_mode/inprocess.rs @@ -17,14 +17,17 @@ use webrender_traits::{ RenderingContext, WebrenderExternalImageApi, WebrenderExternalImageRegistry, WebrenderImageSource, }; +#[cfg(feature = "webxr")] use webxr::SurfmanGL as WebXRSurfman; +#[cfg(feature = "webxr")] use webxr_api::LayerGrandManager as WebXRLayerGrandManager; -use crate::webgl_thread::{WebGLThread, WebGLThreadInit, WebXRBridgeInit}; +use crate::webgl_thread::{WebGLThread, WebGLThreadInit}; pub struct WebGLComm { pub webgl_threads: WebGLThreads, pub image_handler: Box, + #[cfg(feature = "webxr")] pub webxr_layer_grand_manager: WebXRLayerGrandManager, } @@ -40,7 +43,9 @@ impl WebGLComm { debug!("WebGLThreads::new()"); let (sender, receiver) = webgl_channel::().unwrap(); let webrender_swap_chains = SwapChains::new(); - let webxr_init = WebXRBridgeInit::new(sender.clone()); + #[cfg(feature = "webxr")] + let webxr_init = crate::webxr::WebXRBridgeInit::new(sender.clone()); + #[cfg(feature = "webxr")] let webxr_layer_grand_manager = webxr_init.layer_grand_manager(); // This implementation creates a single `WebGLThread` for all the pipelines. @@ -54,6 +59,7 @@ impl WebGLComm { connection: surfman.connection(), adapter: surfman.adapter(), api_type, + #[cfg(feature = "webxr")] webxr_init, }; @@ -64,6 +70,7 @@ impl WebGLComm { WebGLComm { webgl_threads: WebGLThreads(sender), image_handler: Box::new(external), + #[cfg(feature = "webxr")] webxr_layer_grand_manager, } } diff --git a/components/canvas/webgl_thread.rs b/components/canvas/webgl_thread.rs index dd9ddff07b5..4d330197ac5 100644 --- a/components/canvas/webgl_thread.rs +++ b/components/canvas/webgl_thread.rs @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::borrow::Cow; -use std::collections::HashMap; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::{slice, thread}; @@ -11,14 +10,16 @@ use std::{slice, thread}; use bitflags::bitflags; use byteorder::{ByteOrder, NativeEndian, WriteBytesExt}; use canvas_traits::webgl; +#[cfg(feature = "webxr")] +use canvas_traits::webgl::WebXRCommand; use canvas_traits::webgl::{ - webgl_channel, ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, AlphaTreatment, + ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, AlphaTreatment, GLContextAttributes, GLLimits, GlType, InternalFormatIntVec, ProgramLinkInfo, TexDataType, TexFormat, WebGLBufferId, WebGLChan, WebGLCommand, WebGLCommandBacktrace, WebGLContextId, WebGLCreateContextResult, WebGLFramebufferBindingRequest, WebGLFramebufferId, WebGLMsg, WebGLMsgSender, WebGLProgramId, WebGLQueryId, WebGLReceiver, WebGLRenderbufferId, WebGLSLVersion, WebGLSamplerId, WebGLSender, WebGLShaderId, WebGLSyncId, WebGLTextureId, - WebGLVersion, WebGLVertexArrayId, WebXRCommand, WebXRLayerManagerId, YAxisTreatment, + WebGLVersion, WebGLVertexArrayId, YAxisTreatment, }; use euclid::default::Size2D; use fnv::FnvHashMap; @@ -39,22 +40,15 @@ use webrender_api::{ ImageData, ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey, }; use webrender_traits::{WebrenderExternalImageRegistry, WebrenderImageHandlerType}; -use webxr::SurfmanGL as WebXRSurfman; -use webxr_api::{ - ContextId as WebXRContextId, Error as WebXRError, GLContexts as WebXRContexts, - GLTypes as WebXRTypes, LayerGrandManager as WebXRLayerGrandManager, - LayerGrandManagerAPI as WebXRLayerGrandManagerAPI, LayerId as WebXRLayerId, - LayerInit as WebXRLayerInit, LayerManager as WebXRLayerManager, - LayerManagerAPI as WebXRLayerManagerAPI, LayerManagerFactory as WebXRLayerManagerFactory, - SubImages as WebXRSubImages, -}; use crate::webgl_limits::GLLimitsDetect; +#[cfg(feature = "webxr")] +use crate::webxr::{WebXRBridge, WebXRBridgeContexts, WebXRBridgeInit}; -struct GLContextData { - ctx: Context, - gl: Rc, - glow: glow::Context, +pub(crate) struct GLContextData { + pub(crate) ctx: Context, + pub(crate) gl: Rc, + pub(crate) glow: glow::Context, state: GLState, attributes: GLContextAttributes, } @@ -212,6 +206,7 @@ pub(crate) struct WebGLThread { webrender_swap_chains: SwapChains, /// Whether this context is a GL or GLES context. api_type: GlType, + #[cfg(feature = "webxr")] /// The bridge to WebXR pub webxr_bridge: WebXRBridge, } @@ -227,6 +222,7 @@ pub(crate) struct WebGLThreadInit { pub connection: Connection, pub adapter: Adapter, pub api_type: GlType, + #[cfg(feature = "webxr")] pub webxr_init: WebXRBridgeInit, } @@ -246,6 +242,7 @@ impl WebGLThread { connection, adapter, api_type, + #[cfg(feature = "webxr")] webxr_init, }: WebGLThreadInit, ) -> Self { @@ -263,6 +260,7 @@ impl WebGLThread { receiver: receiver.into_inner(), webrender_swap_chains, api_type, + #[cfg(feature = "webxr")] webxr_bridge: WebXRBridge::new(webxr_init), } } @@ -363,8 +361,9 @@ impl WebGLThread { WebGLMsg::WebGLCommand(ctx_id, command, backtrace) => { self.handle_webgl_command(ctx_id, command, backtrace); }, - WebGLMsg::WebXRCommand(command) => { - self.handle_webxr_command(command); + WebGLMsg::WebXRCommand(_command) => { + #[cfg(feature = "webxr")] + self.handle_webxr_command(_command); }, WebGLMsg::SwapBuffers(swap_ids, sender, sent_time) => { self.handle_swap_buffers(swap_ids, sender, sent_time); @@ -380,6 +379,7 @@ impl WebGLThread { false } + #[cfg(feature = "webxr")] /// Handles a WebXR message fn handle_webxr_command(&mut self, command: WebXRCommand) { trace!("processing {:?}", command); @@ -723,17 +723,20 @@ impl WebGLThread { &mut self.bound_context_id, ); - // Destroy WebXR layers associated with this context - let webxr_context_id = WebXRContextId::from(context_id); - let mut webxr_contexts = WebXRBridgeContexts { - contexts: &mut self.contexts, - bound_context_id: &mut self.bound_context_id, - }; - self.webxr_bridge.destroy_all_layers( - &mut self.device, - &mut webxr_contexts, - webxr_context_id, - ); + #[cfg(feature = "webxr")] + { + // Destroy WebXR layers associated with this context + let webxr_context_id = webxr_api::ContextId::from(context_id); + let mut webxr_contexts = WebXRBridgeContexts { + contexts: &mut self.contexts, + bound_context_id: &mut self.bound_context_id, + }; + self.webxr_bridge.destroy_all_layers( + &mut self.device, + &mut webxr_contexts, + webxr_context_id, + ); + } // Release GL context. let mut data = match self.contexts.remove(&context_id) { @@ -849,7 +852,7 @@ impl WebGLThread { } /// Gets a reference to a Context for a given WebGLContextId and makes it current if required. - fn make_current_if_needed<'a>( + pub(crate) fn make_current_if_needed<'a>( device: &Device, context_id: WebGLContextId, contexts: &'a FnvHashMap, @@ -868,7 +871,7 @@ impl WebGLThread { } /// Gets a mutable reference to a GLContextWrapper for a WebGLContextId and makes it current if required. - fn make_current_if_needed_mut<'a>( + pub(crate) fn make_current_if_needed_mut<'a>( device: &Device, context_id: WebGLContextId, contexts: &'a mut FnvHashMap, @@ -3016,313 +3019,3 @@ impl FramebufferRebindingInfo { ); } } - -/// Bridge between WebGL and WebXR -pub(crate) struct WebXRBridge { - factory_receiver: crossbeam_channel::Receiver>, - managers: HashMap>>, - next_manager_id: u32, -} - -impl WebXRBridge { - pub(crate) fn new(init: WebXRBridgeInit) -> WebXRBridge { - let WebXRBridgeInit { - factory_receiver, .. - } = init; - let managers = HashMap::new(); - let next_manager_id = 1; - WebXRBridge { - factory_receiver, - managers, - next_manager_id, - } - } -} - -impl WebXRBridge { - #[allow(unsafe_code)] - fn create_layer_manager( - &mut self, - device: &mut Device, - contexts: &mut dyn WebXRContexts, - ) -> Result { - let factory = self - .factory_receiver - .recv() - .map_err(|_| WebXRError::CommunicationError)?; - let manager = factory.build(device, contexts)?; - let manager_id = unsafe { WebXRLayerManagerId::new(self.next_manager_id) }; - self.next_manager_id += 1; - self.managers.insert(manager_id, manager); - Ok(manager_id) - } - - fn destroy_layer_manager(&mut self, manager_id: WebXRLayerManagerId) { - self.managers.remove(&manager_id); - } - - fn create_layer( - &mut self, - manager_id: WebXRLayerManagerId, - device: &mut Device, - contexts: &mut dyn WebXRContexts, - context_id: WebXRContextId, - layer_init: WebXRLayerInit, - ) -> Result { - let manager = self - .managers - .get_mut(&manager_id) - .ok_or(WebXRError::NoMatchingDevice)?; - manager.create_layer(device, contexts, context_id, layer_init) - } - - fn destroy_layer( - &mut self, - manager_id: WebXRLayerManagerId, - device: &mut Device, - contexts: &mut dyn WebXRContexts, - context_id: WebXRContextId, - layer_id: WebXRLayerId, - ) { - if let Some(manager) = self.managers.get_mut(&manager_id) { - manager.destroy_layer(device, contexts, context_id, layer_id); - } - } - - fn destroy_all_layers( - &mut self, - device: &mut Device, - contexts: &mut dyn WebXRContexts, - context_id: WebXRContextId, - ) { - for manager in self.managers.values_mut() { - #[allow(clippy::unnecessary_to_owned)] // Needs mutable borrow later in destroy - for (other_id, layer_id) in manager.layers().to_vec() { - if other_id == context_id { - manager.destroy_layer(device, contexts, context_id, layer_id); - } - } - } - } - - fn begin_frame( - &mut self, - manager_id: WebXRLayerManagerId, - device: &mut Device, - contexts: &mut dyn WebXRContexts, - layers: &[(WebXRContextId, WebXRLayerId)], - ) -> Result, WebXRError> { - let manager = self - .managers - .get_mut(&manager_id) - .ok_or(WebXRError::NoMatchingDevice)?; - manager.begin_frame(device, contexts, layers) - } - - fn end_frame( - &mut self, - manager_id: WebXRLayerManagerId, - device: &mut Device, - contexts: &mut dyn WebXRContexts, - layers: &[(WebXRContextId, WebXRLayerId)], - ) -> Result<(), WebXRError> { - let manager = self - .managers - .get_mut(&manager_id) - .ok_or(WebXRError::NoMatchingDevice)?; - manager.end_frame(device, contexts, layers) - } -} - -pub(crate) struct WebXRBridgeInit { - sender: WebGLSender, - factory_receiver: crossbeam_channel::Receiver>, - factory_sender: crossbeam_channel::Sender>, -} - -impl WebXRBridgeInit { - pub(crate) fn new(sender: WebGLSender) -> WebXRBridgeInit { - let (factory_sender, factory_receiver) = crossbeam_channel::unbounded(); - WebXRBridgeInit { - sender, - factory_sender, - factory_receiver, - } - } - - pub(crate) fn layer_grand_manager(&self) -> WebXRLayerGrandManager { - WebXRLayerGrandManager::new(WebXRBridgeGrandManager { - sender: self.sender.clone(), - factory_sender: self.factory_sender.clone(), - }) - } -} - -struct WebXRBridgeGrandManager { - sender: WebGLSender, - // WebXR layer manager factories use generic trait objects under the - // hood, which aren't deserializable (even using typetag) - // so we can't send them over the regular webgl channel. - // Fortunately, the webgl thread runs in the same process as - // the webxr threads, so we can use a crossbeam channel to send - // factories. - factory_sender: crossbeam_channel::Sender>, -} - -impl WebXRLayerGrandManagerAPI for WebXRBridgeGrandManager { - fn create_layer_manager( - &self, - factory: WebXRLayerManagerFactory, - ) -> Result { - let (sender, receiver) = webgl_channel().ok_or(WebXRError::CommunicationError)?; - let _ = self.factory_sender.send(factory); - let _ = self - .sender - .send(WebGLMsg::WebXRCommand(WebXRCommand::CreateLayerManager( - sender, - ))); - let sender = self.sender.clone(); - let manager_id = receiver - .recv() - .map_err(|_| WebXRError::CommunicationError)??; - let layers = Vec::new(); - Ok(WebXRLayerManager::new(WebXRBridgeManager { - manager_id, - sender, - layers, - })) - } - - fn clone_layer_grand_manager(&self) -> WebXRLayerGrandManager { - WebXRLayerGrandManager::new(WebXRBridgeGrandManager { - sender: self.sender.clone(), - factory_sender: self.factory_sender.clone(), - }) - } -} - -struct WebXRBridgeManager { - sender: WebGLSender, - manager_id: WebXRLayerManagerId, - layers: Vec<(WebXRContextId, WebXRLayerId)>, -} - -impl WebXRLayerManagerAPI for WebXRBridgeManager { - fn create_layer( - &mut self, - _: &mut GL::Device, - _: &mut dyn WebXRContexts, - context_id: WebXRContextId, - init: WebXRLayerInit, - ) -> Result { - let (sender, receiver) = webgl_channel().ok_or(WebXRError::CommunicationError)?; - let _ = self - .sender - .send(WebGLMsg::WebXRCommand(WebXRCommand::CreateLayer( - self.manager_id, - context_id, - init, - sender, - ))); - let layer_id = receiver - .recv() - .map_err(|_| WebXRError::CommunicationError)??; - self.layers.push((context_id, layer_id)); - Ok(layer_id) - } - - fn destroy_layer( - &mut self, - _: &mut GL::Device, - _: &mut dyn WebXRContexts, - context_id: WebXRContextId, - layer_id: WebXRLayerId, - ) { - self.layers.retain(|&ids| ids != (context_id, layer_id)); - let _ = self - .sender - .send(WebGLMsg::WebXRCommand(WebXRCommand::DestroyLayer( - self.manager_id, - context_id, - layer_id, - ))); - } - - fn layers(&self) -> &[(WebXRContextId, WebXRLayerId)] { - &self.layers[..] - } - - fn begin_frame( - &mut self, - _: &mut GL::Device, - _: &mut dyn WebXRContexts, - layers: &[(WebXRContextId, WebXRLayerId)], - ) -> Result, WebXRError> { - let (sender, receiver) = webgl_channel().ok_or(WebXRError::CommunicationError)?; - let _ = self - .sender - .send(WebGLMsg::WebXRCommand(WebXRCommand::BeginFrame( - self.manager_id, - layers.to_vec(), - sender, - ))); - receiver - .recv() - .map_err(|_| WebXRError::CommunicationError)? - } - - fn end_frame( - &mut self, - _: &mut GL::Device, - _: &mut dyn WebXRContexts, - layers: &[(WebXRContextId, WebXRLayerId)], - ) -> Result<(), WebXRError> { - let (sender, receiver) = webgl_channel().ok_or(WebXRError::CommunicationError)?; - let _ = self - .sender - .send(WebGLMsg::WebXRCommand(WebXRCommand::EndFrame( - self.manager_id, - layers.to_vec(), - sender, - ))); - receiver - .recv() - .map_err(|_| WebXRError::CommunicationError)? - } -} - -impl Drop for WebXRBridgeManager { - fn drop(&mut self) { - let _ = self - .sender - .send(WebGLMsg::WebXRCommand(WebXRCommand::DestroyLayerManager( - self.manager_id, - ))); - } -} - -struct WebXRBridgeContexts<'a> { - contexts: &'a mut FnvHashMap, - bound_context_id: &'a mut Option, -} - -impl<'a> WebXRContexts for WebXRBridgeContexts<'a> { - fn context(&mut self, device: &Device, context_id: WebXRContextId) -> Option<&mut Context> { - let data = WebGLThread::make_current_if_needed_mut( - device, - WebGLContextId::from(context_id), - self.contexts, - self.bound_context_id, - )?; - Some(&mut data.ctx) - } - fn bindings(&mut self, device: &Device, context_id: WebXRContextId) -> Option<&glow::Context> { - let data = WebGLThread::make_current_if_needed( - device, - WebGLContextId::from(context_id), - self.contexts, - self.bound_context_id, - )?; - Some(&data.glow) - } -} diff --git a/components/canvas/webxr.rs b/components/canvas/webxr.rs new file mode 100644 index 00000000000..05b122208b8 --- /dev/null +++ b/components/canvas/webxr.rs @@ -0,0 +1,332 @@ +/* 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::collections::HashMap; + +use canvas_traits::webgl::{ + webgl_channel, WebGLContextId, WebGLMsg, WebGLSender, WebXRCommand, WebXRLayerManagerId, +}; +use fnv::FnvHashMap; +use surfman::{Context, Device}; +use webxr::SurfmanGL as WebXRSurfman; +use webxr_api::{ + ContextId as WebXRContextId, Error as WebXRError, GLContexts as WebXRContexts, + GLTypes as WebXRTypes, LayerGrandManager as WebXRLayerGrandManager, + LayerGrandManagerAPI as WebXRLayerGrandManagerAPI, LayerId as WebXRLayerId, + LayerInit as WebXRLayerInit, LayerManager as WebXRLayerManager, + LayerManagerAPI as WebXRLayerManagerAPI, LayerManagerFactory as WebXRLayerManagerFactory, + SubImages as WebXRSubImages, +}; + +use crate::webgl_thread::{GLContextData, WebGLThread}; + +/// Bridge between WebGL and WebXR +pub(crate) struct WebXRBridge { + factory_receiver: crossbeam_channel::Receiver>, + managers: HashMap>>, + next_manager_id: u32, +} + +impl WebXRBridge { + pub(crate) fn new(init: WebXRBridgeInit) -> WebXRBridge { + let WebXRBridgeInit { + factory_receiver, .. + } = init; + let managers = HashMap::new(); + let next_manager_id = 1; + WebXRBridge { + factory_receiver, + managers, + next_manager_id, + } + } +} + +impl WebXRBridge { + #[allow(unsafe_code)] + pub(crate) fn create_layer_manager( + &mut self, + device: &mut Device, + contexts: &mut dyn WebXRContexts, + ) -> Result { + let factory = self + .factory_receiver + .recv() + .map_err(|_| WebXRError::CommunicationError)?; + let manager = factory.build(device, contexts)?; + let manager_id = unsafe { WebXRLayerManagerId::new(self.next_manager_id) }; + self.next_manager_id += 1; + self.managers.insert(manager_id, manager); + Ok(manager_id) + } + + pub(crate) fn destroy_layer_manager(&mut self, manager_id: WebXRLayerManagerId) { + self.managers.remove(&manager_id); + } + + pub(crate) fn create_layer( + &mut self, + manager_id: WebXRLayerManagerId, + device: &mut Device, + contexts: &mut dyn WebXRContexts, + context_id: WebXRContextId, + layer_init: WebXRLayerInit, + ) -> Result { + let manager = self + .managers + .get_mut(&manager_id) + .ok_or(WebXRError::NoMatchingDevice)?; + manager.create_layer(device, contexts, context_id, layer_init) + } + + pub(crate) fn destroy_layer( + &mut self, + manager_id: WebXRLayerManagerId, + device: &mut Device, + contexts: &mut dyn WebXRContexts, + context_id: WebXRContextId, + layer_id: WebXRLayerId, + ) { + if let Some(manager) = self.managers.get_mut(&manager_id) { + manager.destroy_layer(device, contexts, context_id, layer_id); + } + } + + pub(crate) fn destroy_all_layers( + &mut self, + device: &mut Device, + contexts: &mut dyn WebXRContexts, + context_id: WebXRContextId, + ) { + for manager in self.managers.values_mut() { + #[allow(clippy::unnecessary_to_owned)] // Needs mutable borrow later in destroy + for (other_id, layer_id) in manager.layers().to_vec() { + if other_id == context_id { + manager.destroy_layer(device, contexts, context_id, layer_id); + } + } + } + } + + pub(crate) fn begin_frame( + &mut self, + manager_id: WebXRLayerManagerId, + device: &mut Device, + contexts: &mut dyn WebXRContexts, + layers: &[(WebXRContextId, WebXRLayerId)], + ) -> Result, WebXRError> { + let manager = self + .managers + .get_mut(&manager_id) + .ok_or(WebXRError::NoMatchingDevice)?; + manager.begin_frame(device, contexts, layers) + } + + pub(crate) fn end_frame( + &mut self, + manager_id: WebXRLayerManagerId, + device: &mut Device, + contexts: &mut dyn WebXRContexts, + layers: &[(WebXRContextId, WebXRLayerId)], + ) -> Result<(), WebXRError> { + let manager = self + .managers + .get_mut(&manager_id) + .ok_or(WebXRError::NoMatchingDevice)?; + manager.end_frame(device, contexts, layers) + } +} + +pub(crate) struct WebXRBridgeInit { + sender: WebGLSender, + factory_receiver: crossbeam_channel::Receiver>, + factory_sender: crossbeam_channel::Sender>, +} + +impl WebXRBridgeInit { + pub(crate) fn new(sender: WebGLSender) -> WebXRBridgeInit { + let (factory_sender, factory_receiver) = crossbeam_channel::unbounded(); + WebXRBridgeInit { + sender, + factory_sender, + factory_receiver, + } + } + + pub(crate) fn layer_grand_manager(&self) -> WebXRLayerGrandManager { + WebXRLayerGrandManager::new(WebXRBridgeGrandManager { + sender: self.sender.clone(), + factory_sender: self.factory_sender.clone(), + }) + } +} + +struct WebXRBridgeGrandManager { + sender: WebGLSender, + // WebXR layer manager factories use generic trait objects under the + // hood, which aren't deserializable (even using typetag) + // so we can't send them over the regular webgl channel. + // Fortunately, the webgl thread runs in the same process as + // the webxr threads, so we can use a crossbeam channel to send + // factories. + factory_sender: crossbeam_channel::Sender>, +} + +impl WebXRLayerGrandManagerAPI for WebXRBridgeGrandManager { + fn create_layer_manager( + &self, + factory: WebXRLayerManagerFactory, + ) -> Result { + let (sender, receiver) = webgl_channel().ok_or(WebXRError::CommunicationError)?; + let _ = self.factory_sender.send(factory); + let _ = self + .sender + .send(WebGLMsg::WebXRCommand(WebXRCommand::CreateLayerManager( + sender, + ))); + let sender = self.sender.clone(); + let manager_id = receiver + .recv() + .map_err(|_| WebXRError::CommunicationError)??; + let layers = Vec::new(); + Ok(WebXRLayerManager::new(WebXRBridgeManager { + manager_id, + sender, + layers, + })) + } + + fn clone_layer_grand_manager(&self) -> WebXRLayerGrandManager { + WebXRLayerGrandManager::new(WebXRBridgeGrandManager { + sender: self.sender.clone(), + factory_sender: self.factory_sender.clone(), + }) + } +} + +struct WebXRBridgeManager { + sender: WebGLSender, + manager_id: WebXRLayerManagerId, + layers: Vec<(WebXRContextId, WebXRLayerId)>, +} + +impl WebXRLayerManagerAPI for WebXRBridgeManager { + fn create_layer( + &mut self, + _: &mut GL::Device, + _: &mut dyn WebXRContexts, + context_id: WebXRContextId, + init: WebXRLayerInit, + ) -> Result { + let (sender, receiver) = webgl_channel().ok_or(WebXRError::CommunicationError)?; + let _ = self + .sender + .send(WebGLMsg::WebXRCommand(WebXRCommand::CreateLayer( + self.manager_id, + context_id, + init, + sender, + ))); + let layer_id = receiver + .recv() + .map_err(|_| WebXRError::CommunicationError)??; + self.layers.push((context_id, layer_id)); + Ok(layer_id) + } + + fn destroy_layer( + &mut self, + _: &mut GL::Device, + _: &mut dyn WebXRContexts, + context_id: WebXRContextId, + layer_id: WebXRLayerId, + ) { + self.layers.retain(|&ids| ids != (context_id, layer_id)); + let _ = self + .sender + .send(WebGLMsg::WebXRCommand(WebXRCommand::DestroyLayer( + self.manager_id, + context_id, + layer_id, + ))); + } + + fn layers(&self) -> &[(WebXRContextId, WebXRLayerId)] { + &self.layers[..] + } + + fn begin_frame( + &mut self, + _: &mut GL::Device, + _: &mut dyn WebXRContexts, + layers: &[(WebXRContextId, WebXRLayerId)], + ) -> Result, WebXRError> { + let (sender, receiver) = webgl_channel().ok_or(WebXRError::CommunicationError)?; + let _ = self + .sender + .send(WebGLMsg::WebXRCommand(WebXRCommand::BeginFrame( + self.manager_id, + layers.to_vec(), + sender, + ))); + receiver + .recv() + .map_err(|_| WebXRError::CommunicationError)? + } + + fn end_frame( + &mut self, + _: &mut GL::Device, + _: &mut dyn WebXRContexts, + layers: &[(WebXRContextId, WebXRLayerId)], + ) -> Result<(), WebXRError> { + let (sender, receiver) = webgl_channel().ok_or(WebXRError::CommunicationError)?; + let _ = self + .sender + .send(WebGLMsg::WebXRCommand(WebXRCommand::EndFrame( + self.manager_id, + layers.to_vec(), + sender, + ))); + receiver + .recv() + .map_err(|_| WebXRError::CommunicationError)? + } +} + +impl Drop for WebXRBridgeManager { + fn drop(&mut self) { + let _ = self + .sender + .send(WebGLMsg::WebXRCommand(WebXRCommand::DestroyLayerManager( + self.manager_id, + ))); + } +} + +pub(crate) struct WebXRBridgeContexts<'a> { + pub(crate) contexts: &'a mut FnvHashMap, + pub(crate) bound_context_id: &'a mut Option, +} + +impl<'a> WebXRContexts for WebXRBridgeContexts<'a> { + fn context(&mut self, device: &Device, context_id: WebXRContextId) -> Option<&mut Context> { + let data = WebGLThread::make_current_if_needed_mut( + device, + WebGLContextId::from(context_id), + self.contexts, + self.bound_context_id, + )?; + Some(&mut data.ctx) + } + fn bindings(&mut self, device: &Device, context_id: WebXRContextId) -> Option<&glow::Context> { + let data = WebGLThread::make_current_if_needed( + device, + WebGLContextId::from(context_id), + self.contexts, + self.bound_context_id, + )?; + Some(&data.glow) + } +} diff --git a/components/compositing/Cargo.toml b/components/compositing/Cargo.toml index 8fbb092cdce..32acf07504d 100644 --- a/components/compositing/Cargo.toml +++ b/components/compositing/Cargo.toml @@ -15,6 +15,7 @@ path = "lib.rs" default = [] multiview = [] tracing = ["dep:tracing"] +webxr = ["dep:webxr"] [dependencies] base = { workspace = true } @@ -46,7 +47,7 @@ tracing = { workspace = true, optional = true } webrender = { workspace = true } webrender_api = { workspace = true } webrender_traits = { workspace = true } -webxr = { workspace = true } +webxr = { workspace = true, optional = true } [dev-dependencies] surfman = { workspace = true } diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index c3226162662..08df16515cd 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -169,6 +169,7 @@ pub struct IOCompositor { /// The GL bindings for webrender webrender_gl: Rc, + #[cfg(feature = "webxr")] /// Some XR devices want to run on the main thread. pub webxr_main_thread: webxr::MainThreadRegistry, @@ -409,6 +410,7 @@ impl IOCompositor { webrender_api: state.webrender_api, rendering_context: state.rendering_context, webrender_gl: state.webrender_gl, + #[cfg(feature = "webxr")] webxr_main_thread: state.webxr_main_thread, pending_paint_metrics: HashMap::new(), cursor: Cursor::None, @@ -1803,7 +1805,11 @@ impl IOCompositor { pipeline_ids.push(*pipeline_id); } } - let animation_state = if pipeline_ids.is_empty() && !self.webxr_main_thread.running() { + #[cfg(feature = "webxr")] + let webxr_running = self.webxr_main_thread.running(); + #[cfg(not(feature = "webxr"))] + let webxr_running = false; + let animation_state = if pipeline_ids.is_empty() && webxr_running { windowing::AnimationState::Idle } else { windowing::AnimationState::Animating @@ -2395,6 +2401,7 @@ impl IOCompositor { CompositionRequest::CompositeNow(_) => self.composite(), } + #[cfg(feature = "webxr")] // Run the WebXR main thread self.webxr_main_thread.run_one_frame(); diff --git a/components/compositing/lib.rs b/components/compositing/lib.rs index d28e5e853b4..3328e56138a 100644 --- a/components/compositing/lib.rs +++ b/components/compositing/lib.rs @@ -42,5 +42,6 @@ pub struct InitialCompositorState { pub webrender_api: RenderApi, pub rendering_context: RenderingContext, pub webrender_gl: Rc, + #[cfg(feature = "webxr")] pub webxr_main_thread: webxr::MainThreadRegistry, } diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index 7f56d29dc62..5d7a45bcd10 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -8,7 +8,7 @@ use std::fmt::{Debug, Error, Formatter}; use std::time::Duration; use base::id::{PipelineId, TopLevelBrowsingContextId}; -use embedder_traits::{EmbedderProxy, EventLoopWaker}; +use embedder_traits::EventLoopWaker; use euclid::Scale; use keyboard_types::KeyboardEvent; use libc::c_void; @@ -219,8 +219,14 @@ pub trait EmbedderMethods { /// Returns a thread-safe object to wake up the window's event loop. fn create_event_loop_waker(&mut self) -> Box; + #[cfg(feature = "webxr")] /// Register services with a WebXR Registry. - fn register_webxr(&mut self, _: &mut webxr::MainThreadRegistry, _: EmbedderProxy) {} + fn register_webxr( + &mut self, + _: &mut webxr::MainThreadRegistry, + _: embedder_traits::EmbedderProxy, + ) { + } /// Returns the user agent string to report in network requests. fn get_user_agent_string(&self) -> Option { diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 5e3ecc9eb47..ff959d6e35d 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -455,7 +455,7 @@ pub struct Constellation { webgl_threads: Option, /// The XR device registry - webxr_registry: webxr_api::Registry, + webxr_registry: Option, /// A channel through which messages can be sent to the canvas paint thread. canvas_sender: Sender, @@ -533,7 +533,7 @@ pub struct InitialConstellationState { pub webgl_threads: Option, /// The XR device registry - pub webxr_registry: webxr_api::Registry, + pub webxr_registry: Option, pub glplayer_threads: Option, diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 1765d98bcc3..87966cfc3bf 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -191,7 +191,7 @@ pub struct InitialPipelineState { pub webgl_chan: Option, /// The XR device registry - pub webxr_registry: webxr_api::Registry, + pub webxr_registry: Option, /// Application window's GL Context for Media player pub player_context: WindowGLContext, @@ -496,7 +496,7 @@ pub struct UnprivilegedPipelineContent { cross_process_compositor_api: CrossProcessCompositorApi, webrender_document: DocumentId, webgl_chan: Option, - webxr_registry: webxr_api::Registry, + webxr_registry: Option, player_context: WindowGLContext, user_agent: Cow<'static, str>, } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index d6f1dae848f..d065e41e7e1 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -287,7 +287,7 @@ pub struct Window { #[ignore_malloc_size_of = "defined in webxr"] #[no_trace] - webxr_registry: webxr_api::Registry, + webxr_registry: Option, /// All of the elements that have an outstanding image request that was /// initiated by layout during a reflow. They are stored in the script thread @@ -495,7 +495,7 @@ impl Window { .map(|chan| WebGLCommandSender::new(chan.clone())) } - pub fn webxr_registry(&self) -> webxr_api::Registry { + pub fn webxr_registry(&self) -> Option { self.webxr_registry.clone() } @@ -2579,7 +2579,7 @@ impl Window { creator_url: ServoUrl, navigation_start: CrossProcessInstant, webgl_chan: Option, - webxr_registry: webxr_api::Registry, + webxr_registry: Option, microtask_queue: Rc, webrender_document: DocumentId, compositor_api: CrossProcessCompositorApi, diff --git a/components/script/dom/xrsystem.rs b/components/script/dom/xrsystem.rs index 9766306750a..d6b3ed259d2 100644 --- a/components/script/dom/xrsystem.rs +++ b/components/script/dom/xrsystem.rs @@ -147,9 +147,9 @@ impl XRSystemMethods for XRSystem { }; }), ); - window - .webxr_registry() - .supports_session(mode.into(), sender); + if let Some(mut r) = window.webxr_registry() { + r.supports_session(mode.into(), sender); + } promise } @@ -265,9 +265,9 @@ impl XRSystemMethods for XRSystem { ); }), ); - window - .webxr_registry() - .request_session(mode.into(), init, sender, frame_sender); + if let Some(mut r) = window.webxr_registry() { + r.request_session(mode.into(), init, sender, frame_sender); + } promise } diff --git a/components/script/dom/xrtest.rs b/components/script/dom/xrtest.rs index c6cc3a3ee7c..0f854ad38c6 100644 --- a/components/script/dom/xrtest.rs +++ b/components/script/dom/xrtest.rs @@ -175,9 +175,9 @@ impl XRTestMethods for XRTest { ); }), ); - window - .webxr_registry() - .simulate_device_connection(init, sender); + if let Some(mut r) = window.webxr_registry() { + r.simulate_device_connection(init, sender); + } p } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 29fb9807ce4..6d747b8111d 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -669,7 +669,7 @@ pub struct ScriptThread { /// The WebXR device registry #[no_trace] - webxr_registry: webxr_api::Registry, + webxr_registry: Option, /// The worklet thread pool worklet_thread_pool: DomRefCell>>, diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index c13266d276f..1f04ee2cf30 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -31,6 +31,13 @@ webgl_backtrace = [ "canvas/webgl_backtrace", "canvas_traits/webgl_backtrace", ] +webxr = [ + "dep:webxr", + "dep:webxr-api", + "compositing/webxr", + "embedder_traits/webxr", + "canvas/webxr", +] [dependencies] background_hang_monitor = { path = "../background_hang_monitor" } @@ -82,8 +89,8 @@ webgpu = { path = "../webgpu" } webrender = { workspace = true } webrender_api = { workspace = true } webrender_traits = { workspace = true } -webxr = { workspace = true } -webxr-api = { workspace = true } +webxr = { workspace = true, optional = true } +webxr-api = { workspace = true, optional = true } [target.'cfg(all(not(target_os = "windows"), not(target_os = "ios"), not(target_os = "android"), not(target_arch = "arm"), not(target_arch = "aarch64")))'.dependencies] gaol = "0.2.1" diff --git a/components/servo/lib.rs b/components/servo/lib.rs index df2cebed998..4de6cecf501 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -396,6 +396,7 @@ where let WebGLComm { webgl_threads, + #[cfg(feature = "webxr")] webxr_layer_grand_manager, image_handler, } = WebGLComm::new( @@ -410,9 +411,11 @@ where external_image_handlers.set_handler(image_handler, WebrenderImageHandlerType::WebGL); // Create the WebXR main thread + #[cfg(feature = "webxr")] let mut webxr_main_thread = webxr::MainThreadRegistry::new(event_loop_waker, webxr_layer_grand_manager) .expect("Failed to create WebXR device registry"); + #[cfg(feature = "webxr")] if pref!(dom.webxr.enabled) { embedder.register_webxr(&mut webxr_main_thread, embedder_proxy.clone()); } @@ -454,6 +457,7 @@ where devtools_sender, webrender_document, webrender_api_sender, + #[cfg(feature = "webxr")] webxr_main_thread.registry(), player_context, Some(webgl_threads), @@ -491,6 +495,7 @@ where webrender_api, rendering_context, webrender_gl, + #[cfg(feature = "webxr")] webxr_main_thread, }, composite_target, @@ -1033,7 +1038,7 @@ fn create_constellation( devtools_sender: Option>, webrender_document: DocumentId, webrender_api_sender: RenderApiSender, - webxr_registry: webxr_api::Registry, + #[cfg(feature = "webxr")] webxr_registry: webxr_api::Registry, player_context: WindowGLContext, webgl_threads: Option, glplayer_threads: Option, @@ -1082,7 +1087,10 @@ fn create_constellation( mem_profiler_chan, webrender_document, webrender_api_sender, - webxr_registry, + #[cfg(feature = "webxr")] + webxr_registry: Some(webxr_registry), + #[cfg(not(feature = "webxr"))] + webxr_registry: None, webgl_threads, glplayer_threads, player_context, diff --git a/components/shared/embedder/Cargo.toml b/components/shared/embedder/Cargo.toml index 1973cf3cb4b..49c1b783fa6 100644 --- a/components/shared/embedder/Cargo.toml +++ b/components/shared/embedder/Cargo.toml @@ -11,6 +11,9 @@ rust-version.workspace = true name = "embedder_traits" path = "lib.rs" +[features] +webxr = ["dep:webxr-api"] + [dependencies] base = { workspace = true } cfg-if = { workspace = true } @@ -23,4 +26,4 @@ num-traits = { workspace = true } serde = { workspace = true } servo_url = { path = "../../url" } webrender_api = { workspace = true } -webxr-api = { workspace = true, features = ["ipc"] } +webxr-api = { workspace = true, features = ["ipc"], optional = true } diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index 67bf2468814..f21f3980641 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -15,7 +15,6 @@ use num_derive::FromPrimitive; use serde::{Deserialize, Serialize}; use servo_url::ServoUrl; use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize}; -pub use webxr_api::MainThreadWaker as EventLoopWaker; /// A cursor for the window. This is different from a CSS cursor (see /// `CursorKind`) in that it has no `Auto` value. @@ -59,6 +58,21 @@ pub enum Cursor { ZoomOut, } +#[cfg(feature = "webxr")] +pub use webxr_api::MainThreadWaker as EventLoopWaker; +#[cfg(not(feature = "webxr"))] +pub trait EventLoopWaker: 'static + Send { + fn clone_box(&self) -> Box; + fn wake(&self); +} + +#[cfg(not(feature = "webxr"))] +impl Clone for Box { + fn clone(&self) -> Self { + EventLoopWaker::clone_box(self.as_ref()) + } +} + /// Sends messages to the embedder. pub struct EmbedderProxy { pub sender: Sender<(Option, EmbedderMsg)>, diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index c3bb72156b4..4dad16dcbff 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -658,7 +658,7 @@ pub struct InitialScriptState { /// A channel to the WebGL thread used in this pipeline. pub webgl_chan: Option, /// The XR device registry - pub webxr_registry: webxr_api::Registry, + pub webxr_registry: Option, /// The Webrender document ID associated with this thread. pub webrender_document: DocumentId, /// Access to the compositor across a process boundary. diff --git a/ports/servoshell/Cargo.toml b/ports/servoshell/Cargo.toml index a1d0cbf3829..55d17d92c12 100644 --- a/ports/servoshell/Cargo.toml +++ b/ports/servoshell/Cargo.toml @@ -39,7 +39,7 @@ ProductName = "Servo" [features] debugmozjs = ["libservo/debugmozjs"] -default = ["layout_2013", "max_log_level", "webdriver"] +default = ["layout_2013", "max_log_level", "webdriver", "libservo/webxr"] jitspew = ["libservo/jitspew"] js_backtrace = ["libservo/js_backtrace"] layout_2013 = ["libservo/layout_2013"]