diff --git a/components/config/prefs.rs b/components/config/prefs.rs index 1bd1a7457996..7e4fb7551a5e 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -296,6 +296,8 @@ mod gen { enabled: bool, #[serde(default)] test: bool, + #[serde(default)] + glwindow: bool, }, worklet: { blockingsleep: { diff --git a/components/script/dom/fakexrdevice.rs b/components/script/dom/fakexrdevice.rs index 070ba5fa89c1..e8aa3e26dff7 100644 --- a/components/script/dom/fakexrdevice.rs +++ b/components/script/dom/fakexrdevice.rs @@ -7,43 +7,39 @@ use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::{ }; use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye; use crate::dom::bindings::error::{Error, Fallible}; -use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; use crate::dom::bindings::root::DomRoot; use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; -use webvr_traits::{MockVRControlMsg, MockVRView, WebVRMsg}; +use euclid::{TypedRigidTransform3D, TypedRotation3D, TypedTransform3D, TypedVector3D}; +use ipc_channel::ipc::IpcSender; +use webxr_api::{MockDeviceMsg, View, Views}; #[dom_struct] pub struct FakeXRDevice { reflector: Reflector, + #[ignore_malloc_size_of = "defined in ipc-channel"] + sender: IpcSender, } impl FakeXRDevice { - pub fn new_inherited() -> FakeXRDevice { + pub fn new_inherited(sender: IpcSender) -> FakeXRDevice { FakeXRDevice { reflector: Reflector::new(), + sender, } } - pub fn new(global: &GlobalScope) -> DomRoot { + pub fn new(global: &GlobalScope, sender: IpcSender) -> DomRoot { reflect_dom_object( - Box::new(FakeXRDevice::new_inherited()), + Box::new(FakeXRDevice::new_inherited(sender)), global, FakeXRDeviceBinding::Wrap, ) } - - fn send_msg(&self, msg: MockVRControlMsg) { - self.global() - .as_window() - .webvr_thread() - .unwrap() - .send(WebVRMsg::MessageMockDisplay(msg)) - .unwrap(); - } } -pub fn get_views(views: &[FakeXRViewInit]) -> Fallible<(MockVRView, MockVRView)> { +pub fn get_views(views: &[FakeXRViewInit]) -> Fallible { if views.len() != 2 { return Err(Error::NotSupported); } @@ -66,45 +62,53 @@ pub fn get_views(views: &[FakeXRViewInit]) -> Fallible<(MockVRView, MockVRView)> let mut proj_r = [0.; 16]; let v: Vec<_> = left.projectionMatrix.iter().map(|x| **x).collect(); proj_l.copy_from_slice(&v); + let proj_l = TypedTransform3D::from_array(proj_l); let v: Vec<_> = right.projectionMatrix.iter().map(|x| **x).collect(); proj_r.copy_from_slice(&v); + let proj_r = TypedTransform3D::from_array(proj_r); + + // spec defines offsets as origins, but mock API expects the inverse transform + let offset_l = get_origin(&left.viewOffset)?.inverse(); + let offset_r = get_origin(&right.viewOffset)?.inverse(); - let mut offset_l = [0.; 3]; - let mut offset_r = [0.; 3]; - let v: Vec<_> = left.viewOffset.position.iter().map(|x| **x).collect(); - offset_l.copy_from_slice(&v); - let v: Vec<_> = right.viewOffset.position.iter().map(|x| **x).collect(); - offset_r.copy_from_slice(&v); - let left = MockVRView { + let left = View { projection: proj_l, - offset: offset_l, + transform: offset_l, }; - let right = MockVRView { + let right = View { projection: proj_r, - offset: offset_r, + transform: offset_r, }; - Ok((left, right)) + Ok(Views::Stereo(left, right)) } -pub fn get_origin(origin: &FakeXRRigidTransformInit) -> Fallible<([f32; 3], [f32; 4])> { +pub fn get_origin( + origin: &FakeXRRigidTransformInit, +) -> Fallible> { if origin.position.len() != 3 || origin.orientation.len() != 4 { return Err(Error::Type("Incorrectly sized array".into())); } - let mut p = [0.; 3]; - let mut o = [0.; 4]; - let v: Vec<_> = origin.position.iter().map(|x| **x).collect(); - p.copy_from_slice(&v[0..3]); - let v: Vec<_> = origin.orientation.iter().map(|x| **x).collect(); - o.copy_from_slice(&v); + let p = TypedVector3D::new( + *origin.position[0], + *origin.position[1], + *origin.position[2], + ); + let o = TypedRotation3D::unit_quaternion( + *origin.orientation[0], + *origin.orientation[1], + *origin.orientation[2], + *origin.orientation[3], + ); - Ok((p, o)) + Ok(TypedRigidTransform3D::new(o, p)) } impl FakeXRDeviceMethods for FakeXRDevice { /// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md fn SetViews(&self, views: Vec) -> Fallible<()> { - let (left, right) = get_views(&views)?; - self.send_msg(MockVRControlMsg::SetViews(left, right)); + let _ = self + .sender + .send(MockDeviceMsg::SetViews(get_views(&views)?)); Ok(()) } @@ -114,8 +118,9 @@ impl FakeXRDeviceMethods for FakeXRDevice { origin: &FakeXRRigidTransformInit, _emulated_position: bool, ) -> Fallible<()> { - let (position, orientation) = get_origin(origin)?; - self.send_msg(MockVRControlMsg::SetViewerPose(position, orientation)); + let _ = self + .sender + .send(MockDeviceMsg::SetViewerOrigin(get_origin(origin)?)); Ok(()) } } diff --git a/components/script/dom/webidls/FakeXRDevice.webidl b/components/script/dom/webidls/FakeXRDevice.webidl index 5e310c4d75fa..dcfc116e87b9 100644 --- a/components/script/dom/webidls/FakeXRDevice.webidl +++ b/components/script/dom/webidls/FakeXRDevice.webidl @@ -36,13 +36,11 @@ dictionary FakeXRViewInit { // https://immersive-web.github.io/webxr/#view-offset required FakeXRRigidTransformInit viewOffset; // https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport - required FakeXRViewportInit viewport; + required FakeXRDeviceResolution resolution; }; // https://immersive-web.github.io/webxr/#xrviewport -dictionary FakeXRViewportInit { - required long x; - required long y; +dictionary FakeXRDeviceResolution { required long width; required long height; }; diff --git a/components/script/dom/webidls/XRTest.webidl b/components/script/dom/webidls/XRTest.webidl index f74b9b823310..1b1d131d562f 100644 --- a/components/script/dom/webidls/XRTest.webidl +++ b/components/script/dom/webidls/XRTest.webidl @@ -29,7 +29,7 @@ dictionary FakeXRDeviceInit { // The bounds coordinates. If null, bounded reference spaces are not supported. sequence boundsCoodinates; // Eye level used for calculating floor-level spaces - float eyeLevel = 1.5; + FakeXRRigidTransformInit floorOrigin; FakeXRRigidTransformInit viewerOrigin; }; diff --git a/components/script/dom/xrtest.rs b/components/script/dom/xrtest.rs index 6808ce262cdf..6068a9a3e3a0 100644 --- a/components/script/dom/xrtest.rs +++ b/components/script/dom/xrtest.rs @@ -9,15 +9,21 @@ use crate::dom::bindings::codegen::Bindings::XRTestBinding::{ self, FakeXRDeviceInit, XRTestMethods, }; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; use crate::dom::bindings::root::DomRoot; use crate::dom::fakexrdevice::{get_origin, get_views, FakeXRDevice}; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; +use crate::task_source::TaskSource; use dom_struct::dom_struct; +use euclid::TypedRigidTransform3D; +use ipc_channel::ipc::IpcSender; +use ipc_channel::router::ROUTER; +use profile_traits::ipc; use std::cell::Cell; use std::rc::Rc; -use webvr_traits::{MockVRInit, WebVRMsg}; +use webxr_api::{self, Error as XRError, MockDeviceInit, MockDeviceMsg}; #[dom_struct] pub struct XRTest { @@ -40,11 +46,39 @@ impl XRTest { XRTestBinding::Wrap, ) } + + fn device_obtained( + &self, + response: Result, XRError>, + trusted: TrustedPromise, + ) { + let promise = trusted.root(); + if let Ok(sender) = response { + let device = FakeXRDevice::new(&self.global(), sender); + promise.resolve_native(&device); + } else { + promise.reject_native(&()); + } + } } impl XRTestMethods for XRTest { /// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md fn SimulateDeviceConnection(&self, init: &FakeXRDeviceInit) -> Rc { + #[derive(serde::Serialize, serde::Deserialize)] + pub struct MockDevice { + sender: IpcSender, XRError>>, + } + + #[typetag::serde] + impl webxr_api::MockDeviceCallback for MockDevice { + fn callback(&mut self, result: Result, XRError>) { + self.sender + .send(result) + .expect("mock device callback failed"); + } + } + let p = Promise::new(&self.global()); if !init.supportsImmersive || self.session_started.get() { @@ -61,7 +95,19 @@ impl XRTestMethods for XRTest { }, } } else { - Default::default() + TypedRigidTransform3D::identity() + }; + + let floor_origin = if let Some(ref o) = init.floorOrigin { + match get_origin(&o) { + Ok(origin) => origin, + Err(e) => { + p.reject_error(e); + return p; + }, + } + } else { + TypedRigidTransform3D::identity() }; let views = match get_views(&init.views) { @@ -72,20 +118,47 @@ impl XRTestMethods for XRTest { }, }; - let init = MockVRInit { - viewer_origin: Some(origin), - views: Some(views), - eye_level: None, + let init = MockDeviceInit { + viewer_origin: origin, + views, + supports_immersive: init.supportsImmersive, + supports_unbounded: init.supportsUnbounded, + floor_origin, }; self.session_started.set(true); - self.global() - .as_window() - .webvr_thread() - .unwrap() - .send(WebVRMsg::CreateMockDisplay(init)) - .unwrap(); - p.resolve_native(&FakeXRDevice::new(&self.global())); + + let global = self.global(); + let window = global.as_window(); + let this = Trusted::new(self); + let mut trusted = Some(TrustedPromise::new(p.clone())); + + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |message| { + let trusted = trusted + .take() + .expect("SimulateDeviceConnection callback called twice"); + let this = this.clone(); + let message = message + .to() + .expect("SimulateDeviceConnection callback given incorrect payload"); + + let _ = task_source.queue_with_canceller( + task!(request_session: move || { + this.root().device_obtained(message, trusted); + }), + &canceller, + ); + }), + ); + window + .webxr_registry() + .simulate_device_connection(init, MockDevice { sender }); p } diff --git a/ports/glutin/Cargo.toml b/ports/glutin/Cargo.toml index cae8a6287813..a8c3ab753864 100644 --- a/ports/glutin/Cargo.toml +++ b/ports/glutin/Cargo.toml @@ -60,7 +60,7 @@ rust-webvr = { version = "0.13", features = ["glwindow"] } servo-media = {git = "https://github.com/servo/media"} tinyfiledialogs = "3.0" webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } -webxr = { git = "https://github.com/servo/webxr", features = ["ipc", "glwindow"] } +webxr = { git = "https://github.com/servo/webxr", features = ["ipc", "glwindow", "headless"] } [target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies] image = "0.21" diff --git a/ports/glutin/embedder.rs b/ports/glutin/embedder.rs index 36756d64a2fc..878b282fa6cf 100644 --- a/ports/glutin/embedder.rs +++ b/ports/glutin/embedder.rs @@ -73,18 +73,19 @@ impl EmbedderMethods for EmbedderCallbacks { } fn register_webxr(&mut self, xr: &mut webxr_api::MainThreadRegistry) { - if !opts::get().headless { - if pref!(dom.webxr.test) { - warn!("Creating test XR device"); - let gl = self.gl.clone(); - let events_loop_clone = self.events_loop.clone(); - let events_loop_factory = Box::new(move || { - events_loop_clone.borrow_mut().take().ok_or(EventsLoopClosed) - }); - let gl_version = app::gl_version(); - let discovery = webxr::glwindow::GlWindowDiscovery::new(gl, events_loop_factory, gl_version); - xr.register(discovery); - } + if pref!(dom.webxr.test) { + let gl = self.gl.clone(); + xr.register_mock(webxr::headless::HeadlessMockDiscovery::new(gl)); + } else if !opts::get().headless && pref!(dom.webxr.glwindow) { + warn!("Creating test XR device"); + let gl = self.gl.clone(); + let events_loop_clone = self.events_loop.clone(); + let events_loop_factory = Box::new(move || { + events_loop_clone.borrow_mut().take().ok_or(EventsLoopClosed) + }); + let gl_version = app::gl_version(); + let discovery = webxr::glwindow::GlWindowDiscovery::new(gl, events_loop_factory, gl_version); + xr.register(discovery); } } } diff --git a/resources/prefs.json b/resources/prefs.json index 8e7efaf60692..9139c55c36e2 100644 --- a/resources/prefs.json +++ b/resources/prefs.json @@ -31,6 +31,7 @@ "dom.webvr.event_polling_interval": 500, "dom.webvr.test": false, "dom.webxr.enabled": false, + "dom.webxr.glwindow": false, "dom.webxr.test": false, "dom.worklet.timeout_ms": 10, "gfx.subpixel-text-antialiasing.enabled": true, diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 9d98e1d36c5d..9b7e4182f722 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -19687,11 +19687,11 @@ "testharness" ], "webxr/obtain_frame.html": [ - "e2b4424d5779baedf6bdb50f1b3151336f31a4cb", + "063008c7ebc0df9997b8286296b4f7fe4663b331", "testharness" ], "webxr/resources/webxr-util.js": [ - "554c1c183d3710e54dc60704dad0aac542ffd67c", + "f0c166e097271fd6a2709428fab2ccffea1eb08a", "support" ] }, diff --git a/tests/wpt/mozilla/tests/webxr/obtain_frame.html b/tests/wpt/mozilla/tests/webxr/obtain_frame.html index e2b4424d5779..063008c7ebc0 100644 --- a/tests/wpt/mozilla/tests/webxr/obtain_frame.html +++ b/tests/wpt/mozilla/tests/webxr/obtain_frame.html @@ -54,9 +54,9 @@ pose = frame.getViewerPose(offset); for (view of pose.views) { if (view.eye == "left") { - assert_matrix_approx_equals(view.transform.matrix, [-1/3,-2/3,2/3,0,-2/3,2/3,1/3,0,-2/3,-1/3,-2/3,0,3.4,-1.9,-0.9,1], 0.001, "left offset transform"); + assert_matrix_approx_equals(view.transform.matrix, [-1/3,-2/3,2/3,0,-2/3,2/3,1/3,0,-2/3,-1/3,-2/3,0,3.5 + 1/30,-1.9 + 2/30,-0.9 - 2/30,1], 0.001, "left offset transform"); } else if (view.eye == "right") { - assert_matrix_approx_equals(view.transform.matrix, [-1/3,-2/3,2/3,0,-2/3,2/3,1/3,0,-2/3,-1/3,-2/3,0,3.6,-1.9,-0.9,1], 0.001, "right offset transform"); + assert_matrix_approx_equals(view.transform.matrix, [-1/3,-2/3,2/3,0,-2/3,2/3,1/3,0,-2/3,-1/3,-2/3,0,3.5 - 1/30,-1.9 - 2/30,-0.9 + 2/30,1], 0.001, "right offset transform"); } else { throw "got unknown view"; } diff --git a/tests/wpt/mozilla/tests/webxr/resources/webxr-util.js b/tests/wpt/mozilla/tests/webxr/resources/webxr-util.js index 554c1c183d37..f0c166e09727 100644 --- a/tests/wpt/mozilla/tests/webxr/resources/webxr-util.js +++ b/tests/wpt/mozilla/tests/webxr/resources/webxr-util.js @@ -1,10 +1,9 @@ // pieced together from various things in wpt/webxr/resources const VALID_PROJECTION_MATRIX = [1, 0, 0, 0, 0, 1, 0, 0, 3, 2, -1, -1, 0, 0, -0.2, 0]; -const LEFT_OFFSET = {position: [-0.1, 0, 0], orientation: [0,0,0,0]}; -const RIGHT_OFFSET = {position: [0.1, 0, 0], orientation: [0,0,0,0]}; -const LEFT_VIEWPORT = {x: 0, y: 0, width: 320, height: 480}; -const RIGHT_VIEWPORT = {x: 320, y: 0, width: 320, height: 480}; +const LEFT_OFFSET = {position: [-0.1, 0, 0], orientation: [0,0,0,1]}; +const RIGHT_OFFSET = {position: [0.1, 0, 0], orientation: [0,0,0,1]}; +const RESOLUTION = {width: 320, height: 480}; let assert_matrix_approx_equals = function(m1, m2, epsilon, prefix = "") { assert_equals(m1.length, m2.length, prefix + "Matrix lengths should match"); for(var i = 0; i < m1.length; ++i) { @@ -13,6 +12,6 @@ let assert_matrix_approx_equals = function(m1, m2, epsilon, prefix = "") { } const TEST_VIEWS = [ - {eye: "left", projectionMatrix: VALID_PROJECTION_MATRIX, viewOffset: LEFT_OFFSET, viewport: LEFT_VIEWPORT}, - {eye: "right", projectionMatrix: VALID_PROJECTION_MATRIX, viewOffset: RIGHT_OFFSET, viewport: RIGHT_VIEWPORT} + {eye: "left", projectionMatrix: VALID_PROJECTION_MATRIX, viewOffset: LEFT_OFFSET, resolution: RESOLUTION}, + {eye: "right", projectionMatrix: VALID_PROJECTION_MATRIX, viewOffset: RIGHT_OFFSET, resolution: RESOLUTION} ];