diff --git a/Cargo.lock b/Cargo.lock index b5c04e04773e..31e49c2698ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,6 +316,7 @@ dependencies = [ "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", "servo_config 0.0.1", "webrender_api 0.53.1 (git+https://github.com/servo/webrender)", + "webvr_traits 0.0.1", ] [[package]] diff --git a/components/canvas_traits/Cargo.toml b/components/canvas_traits/Cargo.toml index bdd486c0f7e5..aa81204338ea 100644 --- a/components/canvas_traits/Cargo.toml +++ b/components/canvas_traits/Cargo.toml @@ -21,3 +21,4 @@ offscreen_gl_context = { version = "0.12", features = ["serde"] } serde = "1.0" servo_config = {path = "../config"} webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]} +webvr_traits = {path = "../webvr_traits"} \ No newline at end of file diff --git a/components/canvas_traits/lib.rs b/components/canvas_traits/lib.rs index f05a91f596a9..03e0f5d2dccc 100644 --- a/components/canvas_traits/lib.rs +++ b/components/canvas_traits/lib.rs @@ -18,6 +18,7 @@ extern crate offscreen_gl_context; #[macro_use] extern crate serde; extern crate servo_config; extern crate webrender_api; +extern crate webvr_traits; pub mod canvas; pub mod webgl; diff --git a/components/canvas_traits/webgl.rs b/components/canvas_traits/webgl.rs index a6082209c69c..dd341913d5fd 100644 --- a/components/canvas_traits/webgl.rs +++ b/components/canvas_traits/webgl.rs @@ -7,6 +7,7 @@ use nonzero::NonZero; use offscreen_gl_context::{GLContextAttributes, GLLimits}; use std::fmt; use webrender_api::{DocumentId, ImageKey, PipelineId}; +use webvr_traits::{WebVRFramebuffer, WebVRFramebufferAttributes}; /// Sender type used in WebGLCommands. pub use ::webgl_channel::WebGLSender; @@ -111,6 +112,12 @@ impl WebGLMsgSender { self.sender.send(WebGLMsg::WebGLCommand(self.ctx_id, command)) } + /// Send a generic WebGLMsg command + #[inline] + pub fn send_msg(&self, msg: WebGLMsg) -> WebGLSendResult { + self.sender.send(msg) + } + /// Send a WebVRCommand message #[inline] pub fn send_vr(&self, command: WebVRCommand) -> WebGLSendResult { @@ -384,9 +391,13 @@ pub type WebVRDeviceId = u32; #[derive(Clone, Deserialize, Serialize)] pub enum WebVRCommand { /// Start presenting to a VR device. - Create(WebVRDeviceId), + Create(WebVRDeviceId, WebVRFramebufferAttributes, WebGLSender>), /// Synchronize the pose information to be used in the frame. SyncPoses(WebVRDeviceId, f64, f64, WebGLSender, ()>>), + /// Binds a framebuffer exposed by the VR device. + BindFramebuffer(WebVRDeviceId, u32), + /// Unbinds a framebuffer exposed by the VR device. + UnbindFramebuffer(WebVRDeviceId, u32), /// Submit the frame to a VR device using the specified texture coordinates. SubmitFrame(WebVRDeviceId, [f32; 4], [f32; 4]), /// Stop presenting to a VR device diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index affc70401ed9..368e4a606df2 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -33,7 +33,7 @@ use app_units::Au; use canvas_traits::canvas::{CanvasGradientStop, LinearGradientStyle, RadialGradientStyle}; use canvas_traits::canvas::{CompositionOrBlending, LineCapStyle, LineJoinStyle, RepetitionStyle}; use canvas_traits::webgl::{WebGLBufferId, WebGLFramebufferId, WebGLProgramId, WebGLRenderbufferId}; -use canvas_traits::webgl::{WebGLChan, WebGLContextShareMode, WebGLError, WebGLPipeline, WebGLMsgSender, WebGLVersion}; +use canvas_traits::webgl::{WebGLChan, WebGLContextShareMode, WebGLError, WebGLPipeline, WebGLMsg, WebGLMsgSender, WebGLVersion}; use canvas_traits::webgl::{WebGLReceiver, WebGLSender, WebGLShaderId, WebGLTextureId, WebGLVertexArrayId}; use cssparser::RGBA; use devtools_traits::{CSSError, TimelineMarkerType, WorkerId}; @@ -112,7 +112,7 @@ use style::values::specified::Length; use time::Duration; use uuid::Uuid; use webrender_api::{DocumentId, ImageKey}; -use webvr_traits::WebVRGamepadHand; +use webvr_traits::{WebVRFramebuffer, WebVRFramebufferAttributes, WebVRGamepadHand}; /// A trait to allow tracing (only) DOM objects. pub unsafe trait JSTraceable { @@ -404,6 +404,7 @@ unsafe_no_jsmanaged_fields!(WebGLBufferId); unsafe_no_jsmanaged_fields!(WebGLChan); unsafe_no_jsmanaged_fields!(WebGLContextShareMode); unsafe_no_jsmanaged_fields!(WebGLFramebufferId); +unsafe_no_jsmanaged_fields!(WebGLMsg); unsafe_no_jsmanaged_fields!(WebGLMsgSender); unsafe_no_jsmanaged_fields!(WebGLPipeline); unsafe_no_jsmanaged_fields!(WebGLProgramId); @@ -413,6 +414,8 @@ unsafe_no_jsmanaged_fields!(WebGLTextureId); unsafe_no_jsmanaged_fields!(WebGLVertexArrayId); unsafe_no_jsmanaged_fields!(WebGLVersion); unsafe_no_jsmanaged_fields!(MediaList); +unsafe_no_jsmanaged_fields!(WebVRFramebuffer); +unsafe_no_jsmanaged_fields!(WebVRFramebufferAttributes); unsafe_no_jsmanaged_fields!(WebVRGamepadHand); unsafe_no_jsmanaged_fields!(ScriptToConstellationChan); unsafe_no_jsmanaged_fields!(InteractiveMetrics); diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 5ae3f477a067..0e7184c66033 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -464,6 +464,8 @@ pub mod vreyeparameters; pub mod vrfieldofview; pub mod vrframedata; pub mod vrpose; +pub mod vrview; +pub mod vrviewlist; pub mod vrstageparameters; pub mod webgl_extensions; pub use self::webgl_extensions::ext::*; diff --git a/components/script/dom/vrdisplay.rs b/components/script/dom/vrdisplay.rs index 72b8e29a15c4..c5775d6e0e22 100644 --- a/components/script/dom/vrdisplay.rs +++ b/components/script/dom/vrdisplay.rs @@ -10,6 +10,7 @@ use dom::bindings::codegen::Bindings::VRDisplayBinding; use dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods; use dom::bindings::codegen::Bindings::VRDisplayBinding::VREye; use dom::bindings::codegen::Bindings::VRLayerBinding::VRLayer; +use dom::bindings::codegen::Bindings::VRViewBinding::VRAttributes; use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods; use dom::bindings::codegen::Bindings::WindowBinding::FrameRequestCallback; use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; @@ -17,7 +18,7 @@ use dom::bindings::inheritance::Castable; use dom::bindings::num::Finite; use dom::bindings::refcounted::Trusted; use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::root::{DomRoot, MutDom, MutNullableDom}; +use dom::bindings::root::{DomRoot, MutDom, MutNullableDom, RootedReference}; use dom::bindings::str::DOMString; use dom::event::Event; use dom::eventtarget::EventTarget; @@ -29,6 +30,8 @@ use dom::vreyeparameters::VREyeParameters; use dom::vrframedata::VRFrameData; use dom::vrpose::VRPose; use dom::vrstageparameters::VRStageParameters; +use dom::vrview::VRView; +use dom::vrviewlist::VRViewList; use dom::webglrenderingcontext::WebGLRenderingContext; use dom_struct::dom_struct; use ipc_channel::ipc::{self, IpcSender}; @@ -40,7 +43,8 @@ use std::ops::Deref; use std::rc::Rc; use std::sync::mpsc; use std::thread; -use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVRFrameData, WebVRLayer, WebVRMsg}; +use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVRFrameData}; +use webvr_traits::{WebVRFramebufferAttributes, WebVRLayer, WebVRMsg}; #[dom_struct] pub struct VRDisplay { @@ -71,6 +75,9 @@ pub struct VRDisplay { running_display_raf: Cell, paused: Cell, stopped_on_pause: Cell, + #[ignore_malloc_size_of = "Defined in rust-webvr"] + attributes: Cell, + vr_views: MutNullableDom, } unsafe_no_jsmanaged_fields!(WebVRDisplayData); @@ -116,7 +123,9 @@ impl VRDisplay { paused: Cell::new(false), // This flag is set when the Display was presenting when it received a VR Pause event. // When the VR Resume event is received and the flag is set, VR presentation automatically restarts. - stopped_on_pause: Cell::new(false) + stopped_on_pause: Cell::new(false), + attributes: Cell::new(Default::default()), + vr_views: MutNullableDom::default(), } } @@ -304,9 +313,10 @@ impl VRDisplayMethods for VRDisplay { let layer_ctx; match layer { - Ok((bounds, ctx)) => { + Ok((bounds, ctx, attributes)) => { layer_bounds = bounds; layer_ctx = ctx; + self.attributes.set(attributes); }, Err(msg) => { let msg = msg.to_string(); @@ -396,13 +406,28 @@ impl VRDisplayMethods for VRDisplay { } let layer = self.layer.borrow(); + let attributes = self.attributes.get(); vec![VRLayer { leftBounds: Some(bounds_to_vec(&layer.left_bounds)), rightBounds: Some(bounds_to_vec(&layer.right_bounds)), source: self.layer_ctx.get().map(|ctx| ctx.Canvas()), + attributes: VRAttributes { + depth: attributes.depth, + multiview: attributes.multiview, + antialias: attributes.multisampling, + }, }] } + + // https://w3c.github.io/webvr/spec/latest/#vrview-interface + fn GetViews(&self) -> Option> { + if !self.presenting.get() { + return None; + } + + self.vr_views.get() + } } impl VRDisplay { @@ -495,6 +520,18 @@ impl VRDisplay { let far_init = self.depth_far.get(); let pipeline_id = self.global().pipeline_id(); + // Initialize compositor + let (fbos_sender, fbos_receiver) = webgl_channel().unwrap(); + api_sender.send_vr(WebVRCommand::Create(display_id, self.attributes.get(), fbos_sender)).unwrap(); + let mut fbos = fbos_receiver.recv().unwrap(); + rooted_vec!(let list <- fbos.drain(0..) + .map(|fbo| VRView::new(&self.global(), + api_sender.clone(), + display_id, + fbo))); + let viewlist = VRViewList::new(self.global().as_window(), list.r()); + self.vr_views.set(Some(&viewlist)); + // The render loop at native headset frame rate is implemented using a dedicated thread. // Every loop iteration syncs pose data with the HMD, submits the pixels to the display and waits for Vsync. // Both the requestAnimationFrame call of a VRDisplay in the JavaScript thread and the VRSyncPoses call @@ -506,8 +543,6 @@ impl VRDisplay { let mut near = near_init; let mut far = far_init; - // Initialize compositor - api_sender.send_vr(WebVRCommand::Create(display_id)).unwrap(); loop { // Run RAF callbacks on JavaScript thread let this = address.clone(); @@ -628,13 +663,18 @@ fn parse_bounds(src: &Option>>, dst: &mut [f32; 4]) -> Result<() } } -fn validate_layer(layer: &VRLayer) -> Result<(WebVRLayer, DomRoot), &'static str> { +fn validate_layer(layer: &VRLayer) -> Result<(WebVRLayer, DomRoot, WebVRFramebufferAttributes), &'static str> { let ctx = layer.source.as_ref().map(|ref s| s.get_base_webgl_context()).unwrap_or(None); if let Some(ctx) = ctx { let mut data = WebVRLayer::default(); parse_bounds(&layer.leftBounds, &mut data.left_bounds)?; parse_bounds(&layer.rightBounds, &mut data.right_bounds)?; - Ok((data, ctx)) + let attributes = WebVRFramebufferAttributes { + depth: layer.attributes.depth, + multiview: layer.attributes.multiview, + multisampling: layer.attributes.antialias, + }; + Ok((data, ctx, attributes)) } else { Err("VRLayer source must be a WebGL Context") } diff --git a/components/script/dom/vrview.rs b/components/script/dom/vrview.rs new file mode 100644 index 000000000000..befd0d88baa4 --- /dev/null +++ b/components/script/dom/vrview.rs @@ -0,0 +1,90 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use canvas_traits::webgl::{WebGLFramebufferId, WebGLMsg, WebGLMsgSender, WebVRCommand, WebVRDeviceId}; +use dom::bindings::codegen::Bindings::VRViewBinding; +use dom::bindings::codegen::Bindings::VRViewBinding::{VRAttributes, VRViewMethods, VRViewport}; +use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; +use dom::bindings::root::{MutNullableDom, DomRoot}; +use dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use dom::webglframebuffer::{OpaqueFBOMessages, WebGLFramebuffer}; +use webvr_traits::WebVRFramebuffer; + +#[dom_struct] +pub struct VRView { + reflector_: Reflector, + renderer: WebGLMsgSender, + device_id: WebVRDeviceId, + #[ignore_malloc_size_of = "Defined in rust-webvr"] + fbo: WebVRFramebuffer, + webgl_fbo: MutNullableDom, +} + +impl VRView { + fn new_inherited(renderer: WebGLMsgSender, + device_id: WebVRDeviceId, + fbo: WebVRFramebuffer) -> VRView { + VRView { + reflector_: Reflector::new(), + renderer, + device_id, + fbo, + webgl_fbo: Default::default(), + } + } + + pub fn new(global: &GlobalScope, + renderer: WebGLMsgSender, + device_id: WebVRDeviceId, + fbo: WebVRFramebuffer) -> DomRoot { + reflect_dom_object(Box::new(VRView::new_inherited(renderer, device_id, fbo)), + global, + VRViewBinding::Wrap) + } +} + +impl VRViewMethods for VRView { + // https://w3c.github.io/webvr/#interface-interface-vrfieldofview + #[allow(unsafe_code)] + fn Framebuffer(&self) -> DomRoot { + self.webgl_fbo.or_init(|| { + let fbo_id = unsafe { + // Generate a dummy FBO id that avoids collisions with the real WebGL FBOs. + WebGLFramebufferId::new(self.device_id * 1000 + self.fbo.eye_index) + }; + let bind_msg = WebGLMsg::WebVRCommand(self.renderer.context_id(), + WebVRCommand::BindFramebuffer(self.device_id, + self.fbo.eye_index)); + let unbind_msg = Some(WebGLMsg::WebVRCommand(self.renderer.context_id(), + WebVRCommand::UnbindFramebuffer(self.device_id, + self.fbo.eye_index))); + let opaque_messages = OpaqueFBOMessages { + bind_msg, unbind_msg + }; + + WebGLFramebuffer::new_opaque(self.global().as_window(), + self.renderer.clone(), + fbo_id, + opaque_messages) + }) + } + + fn GetViewport(&self) -> VRViewport { + VRViewport { + x: Some(self.fbo.viewport.x), + y: Some(self.fbo.viewport.y), + width: Some(self.fbo.viewport.width), + height: Some(self.fbo.viewport.height), + } + } + + fn GetAttributes(&self) -> VRAttributes { + VRAttributes { + depth: self.fbo.attributes.depth, + multiview: self.fbo.attributes.multiview, + antialias: self.fbo.attributes.multisampling, + } + } +} diff --git a/components/script/dom/vrviewlist.rs b/components/script/dom/vrviewlist.rs new file mode 100644 index 000000000000..ac6ca53d29d9 --- /dev/null +++ b/components/script/dom/vrviewlist.rs @@ -0,0 +1,48 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::codegen::Bindings::VRViewListBinding; +use dom::bindings::codegen::Bindings::VRViewListBinding::VRViewListMethods; +use dom::bindings::reflector::{Reflector, reflect_dom_object}; +use dom::bindings::root::{Dom, DomRoot}; +use dom::vrview::VRView; +use dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct VRViewList { + reflector_: Reflector, + views: Vec>, +} + +impl VRViewList { + fn new_inherited(views: &[&VRView]) -> VRViewList { + VRViewList { + reflector_: Reflector::new(), + views: views.iter().map(|VRView| Dom::from_ref(*VRView)).collect(), + } + } + + pub fn new(window: &Window, views: &[&VRView]) -> DomRoot { + reflect_dom_object(Box::new(VRViewList::new_inherited(views)), + window, VRViewListBinding::Wrap) + } +} + +impl VRViewListMethods for VRViewList { + /// https://w3c.github.io/VRView-events/#widl-VRViewList-length + fn Length(&self) -> u32 { + self.views.len() as u32 + } + + /// https://w3c.github.io/VRView-events/#widl-VRViewList-item-getter-VRView-unsigned-long-index + fn Item(&self, index: u32) -> Option> { + self.views.get(index as usize).map(|js| DomRoot::from_ref(&**js)) + } + + /// https://w3c.github.io/VRView-events/#widl-VRViewList-item-getter-VRView-unsigned-long-index + fn IndexedGetter(&self, index: u32) -> Option> { + self.Item(index) + } +} diff --git a/components/script/dom/webglframebuffer.rs b/components/script/dom/webglframebuffer.rs index c9165609cdbd..50e021444e71 100644 --- a/components/script/dom/webglframebuffer.rs +++ b/components/script/dom/webglframebuffer.rs @@ -4,7 +4,7 @@ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl use canvas_traits::webgl::{WebGLCommand, WebGLFramebufferBindingRequest, WebGLFramebufferId}; -use canvas_traits::webgl::{WebGLMsgSender, WebGLResult, WebGLError}; +use canvas_traits::webgl::{WebGLMsg, WebGLMsgSender, WebGLResult, WebGLError}; use canvas_traits::webgl::webgl_channel; use dom::bindings::cell::DomRefCell; use dom::bindings::codegen::Bindings::WebGLFramebufferBinding; @@ -43,24 +43,29 @@ pub struct WebGLFramebuffer { depth: DomRefCell>, stencil: DomRefCell>, depthstencil: DomRefCell>, + + // Opaque framebuffers only allow custom bind commands. + opaque_msgs: Option, } impl WebGLFramebuffer { fn new_inherited(renderer: WebGLMsgSender, - id: WebGLFramebufferId) + id: WebGLFramebufferId, + opaque_msgs: Option) -> WebGLFramebuffer { WebGLFramebuffer { webgl_object: WebGLObject::new_inherited(), - id: id, + id, target: Cell::new(None), is_deleted: Cell::new(false), - renderer: renderer, + renderer, size: Cell::new(None), status: Cell::new(constants::FRAMEBUFFER_UNSUPPORTED), color: DomRefCell::new(None), depth: DomRefCell::new(None), stencil: DomRefCell::new(None), depthstencil: DomRefCell::new(None), + opaque_msgs, } } @@ -77,12 +82,26 @@ impl WebGLFramebuffer { renderer: WebGLMsgSender, id: WebGLFramebufferId) -> DomRoot { - reflect_dom_object(Box::new(WebGLFramebuffer::new_inherited(renderer, id)), + reflect_dom_object(Box::new(WebGLFramebuffer::new_inherited(renderer, id, None)), window, WebGLFramebufferBinding::Wrap) } -} + pub fn new_opaque(window: &Window, + renderer: WebGLMsgSender, + id: WebGLFramebufferId, + opaque_msgs: OpaqueFBOMessages) + -> DomRoot { + let fbo = reflect_dom_object(Box::new(WebGLFramebuffer::new_inherited(renderer, id, Some(opaque_msgs))), + window, + WebGLFramebufferBinding::Wrap); + // Opaque framebuffers are always complete. + fbo.status.set(constants::FRAMEBUFFER_COMPLETE); + + fbo + } + +} impl WebGLFramebuffer { pub fn id(&self) -> WebGLFramebufferId { @@ -94,14 +113,27 @@ impl WebGLFramebuffer { // changed if its attachments were resized or deleted while // we've been unbound. self.update_status(); - self.target.set(Some(target)); - let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Explicit(self.id)); - self.renderer.send(cmd).unwrap(); + if let Some(messages) = self.opaque_msgs.as_ref() { + self.renderer.send_msg(messages.bind_msg.clone()).unwrap(); + } else { + let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Explicit(self.id)); + self.renderer.send(cmd).unwrap(); + } + + } + + pub fn unbind(&self) { + // Some Opaque FBO implementation may require unbind messages before binding a different FBO. + if let Some(messages) = self.opaque_msgs.as_ref() { + if let Some(unbind_msg) = messages.unbind_msg.as_ref() { + self.renderer.send_msg(unbind_msg.clone()).unwrap(); + } + } } pub fn delete(&self) { - if !self.is_deleted.get() { + if !self.is_deleted.get() && !self.is_opaque() { self.is_deleted.set(true); let _ = self.renderer.send(WebGLCommand::DeleteFramebuffer(self.id)); } @@ -116,6 +148,10 @@ impl WebGLFramebuffer { } fn update_status(&self) { + if self.is_opaque() { + return; + } + let c = self.color.borrow(); let z = self.depth.borrow(); let s = self.stencil.borrow(); @@ -183,7 +219,18 @@ impl WebGLFramebuffer { return self.status.get(); } + fn is_opaque(&self) -> bool { + self.opaque_msgs.is_some() + } + pub fn renderbuffer(&self, attachment: u32, rb: Option<&WebGLRenderbuffer>) -> WebGLResult<()> { + if self.is_opaque() { + // Calling framebufferRenderbuffer, framebufferTexture2D, framebufferTextureLayer, or any other call + // that could change framebuffer attachments with an opaque multiview framebuffer bound to target + // generates an INVALID_OPERATION error. + return Err(WebGLError::InvalidOperation); + } + let binding = match attachment { constants::COLOR_ATTACHMENT0 => &self.color, constants::DEPTH_ATTACHMENT => &self.depth, @@ -215,6 +262,13 @@ impl WebGLFramebuffer { pub fn texture2d(&self, attachment: u32, textarget: u32, texture: Option<&WebGLTexture>, level: i32) -> WebGLResult<()> { + if self.is_opaque() { + // Calling framebufferRenderbuffer, framebufferTexture2D, framebufferTextureLayer, or any other call + // that could change framebuffer attachments with an opaque multiview framebuffer bound to target + // generates an INVALID_OPERATION error. + return Err(WebGLError::InvalidOperation); + } + let binding = match attachment { constants::COLOR_ATTACHMENT0 => &self.color, constants::DEPTH_ATTACHMENT => &self.depth, @@ -372,3 +426,15 @@ impl Drop for WebGLFramebuffer { self.delete(); } } + +/// Custom messages used for Opaque Framebuffers implementation (e.g. WEBGL_multiview) +/// See https://www.khronos.org/registry/webgl/extensions/proposals/WEBGL_multiview/ +/// Opaque framebuffers only allow bind commands. +/// Unbind command may be required in some kind of implementaion (e.g. Daydream swap chain) +#[derive(MallocSizeOf, JSTraceable)] +pub struct OpaqueFBOMessages { + #[ignore_malloc_size_of = "Channels are hard"] + pub bind_msg: WebGLMsg, + #[ignore_malloc_size_of = "Channels are hard"] + pub unbind_msg: Option, +} \ No newline at end of file diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index d687b17cc980..67a9446f5a87 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -1519,10 +1519,18 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // current binding will remain untouched." return self.webgl_error(InvalidOperation); } else { + if let Some(bound_framebuffer) = self.bound_framebuffer.get() { + if bound_framebuffer.id() != framebuffer.id() { + bound_framebuffer.unbind(); + } + } framebuffer.bind(target); self.bound_framebuffer.set(Some(framebuffer)); } } else { + if let Some(bound_framebuffer) = self.bound_framebuffer.get() { + bound_framebuffer.unbind(); + } // Bind the default framebuffer let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Default); self.send_command(cmd); diff --git a/components/script/dom/webglshader.rs b/components/script/dom/webglshader.rs index 3ce6b93ad778..8d12848e2904 100644 --- a/components/script/dom/webglshader.rs +++ b/components/script/dom/webglshader.rs @@ -109,22 +109,15 @@ impl WebGLShader { let mut params = BuiltInResources::default(); params.FragmentPrecisionHigh = 1; params.OES_standard_derivatives = ext.is_enabled::() as i32; - let validator = match version { - WebGLVersion::WebGL1 => { - ShaderValidator::for_webgl(self.gl_type, - SHADER_OUTPUT_FORMAT, - ¶ms).unwrap() - }, - WebGLVersion::WebGL2 => { - ShaderValidator::for_webgl2(self.gl_type, - SHADER_OUTPUT_FORMAT, - ¶ms).unwrap() - }, - }; - + params.OVR_multiview = 1; + params.MaxViewsOVR = 2; + let validator = ShaderValidator::for_webgl2(self.gl_type, + SHADER_OUTPUT_FORMAT, + ¶ms).unwrap(); + params.OES_standard_derivatives = ext.is_enabled::() as i32; match validator.compile_and_translate(&[source]) { Ok(translated_source) => { - debug!("Shader translated: {}", translated_source); + println!("Shader translated: {}", translated_source); // NOTE: At this point we should be pretty sure that the compilation in the paint thread // will succeed. // It could be interesting to retrieve the info log from the paint thread though diff --git a/components/script/dom/webidls/VRDisplay.webidl b/components/script/dom/webidls/VRDisplay.webidl index 6c8ec7c43208..a4ba62a7a003 100644 --- a/components/script/dom/webidls/VRDisplay.webidl +++ b/components/script/dom/webidls/VRDisplay.webidl @@ -128,4 +128,9 @@ interface VRDisplay : EventTarget { * created without preserveDrawingBuffer set to true will be cleared. */ void submitFrame(); + + /** + * Get the layers currently being presented. + */ + VRViewList? getViews(); }; diff --git a/components/script/dom/webidls/VRLayer.webidl b/components/script/dom/webidls/VRLayer.webidl index 47b30b324f72..cad60febbfc8 100644 --- a/components/script/dom/webidls/VRLayer.webidl +++ b/components/script/dom/webidls/VRLayer.webidl @@ -10,4 +10,5 @@ dictionary VRLayer { HTMLCanvasElement source; sequence leftBounds; sequence rightBounds; + VRAttributes attributes; }; diff --git a/components/script/dom/webidls/VRView.webidl b/components/script/dom/webidls/VRView.webidl new file mode 100644 index 000000000000..195bf479e7f1 --- /dev/null +++ b/components/script/dom/webidls/VRView.webidl @@ -0,0 +1,24 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +// https://w3c.github.io/webvr/#interface-vrstageparameters +[NoInterfaceObject] +interface VRView { + readonly attribute WebGLFramebuffer framebuffer; + VRViewport getViewport(); + VRAttributes getAttributes(); +}; + +dictionary VRViewport { + long x; + long y; + long width; + long height; +}; + +dictionary VRAttributes { + boolean depth = false; + boolean multiview = false; + boolean antialias = false; +}; diff --git a/components/script/dom/webidls/VRViewList.webidl b/components/script/dom/webidls/VRViewList.webidl new file mode 100644 index 000000000000..cbe14c034341 --- /dev/null +++ b/components/script/dom/webidls/VRViewList.webidl @@ -0,0 +1,9 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +// https://w3c.github.io/webvr/spec/latest/#dom-vrpresentationframe-views +interface VRViewList { + readonly attribute unsigned long length; + getter VRView? item (unsigned long index); +}; diff --git a/components/webvr/webvr_thread.rs b/components/webvr/webvr_thread.rs index e8aa06af8be8..ef6acad850c4 100644 --- a/components/webvr/webvr_thread.rs +++ b/components/webvr/webvr_thread.rs @@ -219,7 +219,7 @@ impl WebVRThread { } fn handle_create_compositor(&mut self, display_id: u32) { - let compositor = self.service.get_display(display_id).map(|d| WebVRCompositor(d.as_ptr())); + let compositor = self.service.get_display(display_id).map(|d| WebVRCompositor::new(d.as_ptr())); self.vr_compositor_chan.send(compositor).unwrap(); } @@ -306,7 +306,22 @@ impl WebVRThread { /// * WebVRThread and its VRDisplays are destroyed after all tabs are dropped and the browser is about to exit. /// WebVRThread is closed using the Exit message. -pub struct WebVRCompositor(*mut VRDisplay); +pub struct WebVRCompositor { + pub display: *mut VRDisplay, + pub bound_eye_fbo: Option, + pub direct_draw: bool, +} + +impl WebVRCompositor { + pub fn new(display: *mut VRDisplay) -> Self { + Self { + display, + bound_eye_fbo: None, + direct_draw: false, + } + } +} + pub struct WebVRCompositorHandler { compositors: HashMap, webvr_thread_receiver: Receiver>, @@ -335,22 +350,53 @@ impl webgl::WebVRRenderHandler for WebVRCompositorHandler { #[allow(unsafe_code)] fn handle(&mut self, cmd: webgl::WebVRCommand, texture: Option<(u32, Size2D)>) { match cmd { - webgl::WebVRCommand::Create(compositor_id) => { + webgl::WebVRCommand::Create(compositor_id, attributes, sender) => { self.create_compositor(compositor_id); + // TODO handle error + if let Some(compositor) = self.compositors.get(&compositor_id) { + let fbos = unsafe { + (*compositor.display).start_present(Some(attributes)); + (*compositor.display).get_framebuffers() + }; + sender.send(fbos).unwrap(); + } } webgl::WebVRCommand::SyncPoses(compositor_id, near, far, sender) => { if let Some(compositor) = self.compositors.get(&compositor_id) { let pose = unsafe { - (*compositor.0).sync_poses(); - (*compositor.0).synced_frame_data(near, far).to_bytes() + (*compositor.display).sync_poses(); + (*compositor.display).synced_frame_data(near, far).to_bytes() }; + // FBOs exposed by the headset must be rebound every frame because + // they may use double or tripple buffered swap_chains. + if let Some(eye_index) = compositor.bound_eye_fbo { + unsafe { + (*compositor.display).bind_framebuffer(eye_index); + } + } let _ = sender.send(Ok(pose)); } else { let _ = sender.send(Err(())); } } + webgl::WebVRCommand::BindFramebuffer(compositor_id, eye_index) => { + if let Some(compositor) = self.compositors.get_mut(&compositor_id) { + compositor.direct_draw = true; + if compositor.bound_eye_fbo != Some(eye_index) { + unsafe { + (*compositor.display).bind_framebuffer(eye_index); + } + compositor.bound_eye_fbo = Some(eye_index); + } + } + } + webgl::WebVRCommand::UnbindFramebuffer(compositor_id, eye_index) => { + if let Some(compositor) = self.compositors.get_mut(&compositor_id) { + compositor.bound_eye_fbo = None; + } + } webgl::WebVRCommand::SubmitFrame(compositor_id, left_bounds, right_bounds) => { - if let Some(compositor) = self.compositors.get(&compositor_id) { + if let Some(compositor) = self.compositors.get_mut(&compositor_id) { if let Some((texture_id, size)) = texture { let layer = VRLayer { texture_id: texture_id, @@ -359,8 +405,10 @@ impl webgl::WebVRRenderHandler for WebVRCompositorHandler { texture_size: Some((size.width as u32, size.height as u32)) }; unsafe { - (*compositor.0).render_layer(&layer); - (*compositor.0).submit_frame(); + if !compositor.direct_draw { + (*compositor.display).render_layer(&layer); + } + (*compositor.display).submit_frame(); } } } diff --git a/components/webvr_traits/lib.rs b/components/webvr_traits/lib.rs index 9fd8c85074b9..054ab09df959 100644 --- a/components/webvr_traits/lib.rs +++ b/components/webvr_traits/lib.rs @@ -19,6 +19,8 @@ pub use webvr::VREvent as WebVREvent; pub use webvr::VREye as WebVREye; pub use webvr::VREyeParameters as WebVREyeParameters; pub use webvr::VRFieldOfView as WebVRFieldOfView; +pub use webvr::VRFramebuffer as WebVRFramebuffer; +pub use webvr::VRFramebufferAttributes as WebVRFramebufferAttributes; pub use webvr::VRGamepadButton as WebVRGamepadButton; pub use webvr::VRGamepadData as WebVRGamepadData; pub use webvr::VRGamepadEvent as WebVRGamepadEvent; diff --git a/resources/prefs.json b/resources/prefs.json index d430b9f4e4ba..16dad052a810 100644 --- a/resources/prefs.json +++ b/resources/prefs.json @@ -5,7 +5,7 @@ "dom.compositionevent.enabled": false, "dom.customelements.enabled": true, "dom.forcetouch.enabled": false, - "dom.gamepad.enabled": false, + "dom.gamepad.enabled": true, "dom.mouseevent.which.enabled": false, "dom.mozbrowser.enabled": false, "dom.mutation_observer.enabled": false, @@ -17,7 +17,7 @@ "dom.testbinding.enabled": false, "dom.webgl.dom_to_texture.enabled": false, "dom.webgl2.enabled": false, - "dom.webvr.enabled": false, + "dom.webvr.enabled": true, "dom.webvr.event_polling_interval": 500, "js.asmjs.enabled": true, "js.asyncstack.enabled": false, diff --git a/tests/html/webvr/js/vr-cube-island.js b/tests/html/webvr/js/vr-cube-island.js index e21a10e11bd2..d84c3ad303f4 100644 --- a/tests/html/webvr/js/vr-cube-island.js +++ b/tests/html/webvr/js/vr-cube-island.js @@ -25,6 +25,27 @@ window.VRCubeIsland = (function () { "}", ].join("\n"); + var cubeIslandVSMultiview = [ + "#version 300 es", + "#extension GL_OVR_multiview2 : require", + "#define VIEW_ID gl_ViewID_OVR", + "layout(num_views=2) in;", + + "uniform mat4 leftProjectionMat;", + "uniform mat4 leftModelViewMat;", + "uniform mat4 rightProjectionMat;", + "uniform mat4 rightModelViewMat;", + "in vec3 position;", + "in vec2 texCoord;", + "out vec2 vTexCoord;", + + "void main() {", + " vTexCoord = texCoord;", + " mat4 m = VIEW_ID == 0u ? (leftProjectionMat * leftModelViewMat) : (rightProjectionMat * rightModelViewMat);", + " gl_Position = m * vec4( position, 1.0 );", + "}", + ].join("\n"); + var cubeIslandFS = [ "precision mediump float;", "uniform sampler2D diffuse;", @@ -35,6 +56,19 @@ window.VRCubeIsland = (function () { "}", ].join("\n"); + var cubeIslandFSMultiview = [ + "#version 300 es", + "precision mediump float;", + "uniform sampler2D diffuse;", + "in vec2 vTexCoord;", + "out vec4 color;", + + "void main() {", + " color = texture(diffuse, vTexCoord);", + "}", + ].join("\n"); + + var CubeIsland = function (gl, texture, width, depth) { this.gl = gl; @@ -51,6 +85,15 @@ window.VRCubeIsland = (function () { }); this.program.link(); + this.program_multiview = new WGLUProgram(gl); + this.program_multiview.attachShaderSource(cubeIslandVSMultiview, gl.VERTEX_SHADER); + this.program_multiview.attachShaderSource(cubeIslandFSMultiview, gl.FRAGMENT_SHADER); + this.program_multiview.bindAttribLocation({ + position: 0, + texCoord: 1 + }); + this.program_multiview.link(); + this.vertBuffer = gl.createBuffer(); this.indexBuffer = gl.createBuffer(); @@ -171,14 +214,25 @@ window.VRCubeIsland = (function () { this.indexCount = cubeIndices.length; }; - CubeIsland.prototype.render = function (projectionMat, modelViewMat, stats) { + CubeIsland.prototype.render = function (projectionMat, modelViewMat, stats, multiview) { var gl = this.gl; var program = this.program; - program.use(); + if (multiview) { + console.log("render multiview"); + program = this.program_multiview; + program.use(); + gl.uniformMatrix4fv(program.uniform.leftProjectionMat, false, projectionMat[0]); + gl.uniformMatrix4fv(program.uniform.rightProjectionMat, false, projectionMat[1]); + gl.uniformMatrix4fv(program.uniform.leftModelViewMat, false, modelViewMat[0]); + gl.uniformMatrix4fv(program.uniform.rightModelViewMat, false, modelViewMat[1]); + } + else { + program.use(); + gl.uniformMatrix4fv(program.uniform.projectionMat, false, projectionMat); + gl.uniformMatrix4fv(program.uniform.modelViewMat, false, modelViewMat); + } - gl.uniformMatrix4fv(program.uniform.projectionMat, false, projectionMat); - gl.uniformMatrix4fv(program.uniform.modelViewMat, false, modelViewMat); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertBuffer); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); @@ -195,7 +249,7 @@ window.VRCubeIsland = (function () { gl.drawElements(gl.TRIANGLES, this.indexCount, gl.UNSIGNED_SHORT, 0); - if (stats) { + if (stats && !multiview) { // To ensure that the FPS counter is visible in VR mode we have to // render it as part of the scene. mat4.fromTranslation(this.statsMat, [0, 1.5, -this.depth * 0.5]); diff --git a/tests/html/webvr/room-scale.html b/tests/html/webvr/room-scale.html index fcd35de041c2..c40f30718c88 100644 --- a/tests/html/webvr/room-scale.html +++ b/tests/html/webvr/room-scale.html @@ -79,6 +79,7 @@ var frameData = null; var projectionMat = mat4.create(); var viewMat = mat4.create(); + var viewMat2 = mat4.create(); var vrPresentButton = null; // =================================================== @@ -132,7 +133,11 @@ // ================================ function onVRRequestPresent () { - vrDisplay.requestPresent([{ source: webglCanvas }]).then(function () { + var attributes = { + depth: true, + multiview: true, + }; + vrDisplay.requestPresent([{ source: webglCanvas, attributes: attributes}]).then(function () { }, function () { VRSamplesUtil.addError("requestPresent failed.", 2000); }); @@ -250,12 +255,16 @@ } } - function renderSceneView (projection, view, pose) { - cubeIsland.render(projection, view, stats); + function renderSceneView (projection, view, pose, multiview) { + cubeIsland.render(projection, view, stats, multiview); // For fun, draw a blue cube where the players head would have been if // we weren't taking the stageParameters into account. It'll start in // the center of the floor. + if (multiview) { + // TODO: adapt debugGeom for multiview + return; + } var orientation = pose.orientation; var position = pose.position; if (!orientation) { orientation = [0, 0, 0, 1]; } @@ -264,27 +273,67 @@ debugGeom.drawCube(orientation, position, 0.2, [0, 0, 1, 1]); } + var testSingleBind = false; function onAnimationFrame (t) { stats.begin(); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - if (vrDisplay) { vrDisplay.requestAnimationFrame(onAnimationFrame); vrDisplay.getFrameData(frameData); if (vrDisplay.isPresenting) { - gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height); - getStandingViewMatrix(viewMat, frameData.leftViewMatrix); - renderSceneView(frameData.leftProjectionMatrix, viewMat, frameData.pose); + var views = vrDisplay.getViews ? vrDisplay.getViews() : []; + console.log("views: " + vrDisplay.getViews); + if (views.length > 0) { + var view = views[0]; + gl.enable(gl.SCISSOR_TEST); + for (var i = 0; i < views.length; ++i) { + var view = views[i]; + var multiview = view.getAttributes().multiview; + console.log("multiview: " + multiview); + var viewport = view.getViewport(); + if (!testSingleBind) { + gl.bindFramebuffer(gl.FRAMEBUFFER, view.framebuffer); + testSingleBind = true; + } + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height); + gl.scissor(viewport.x, viewport.y, viewport.width, viewport.height); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + if (multiview) { + var projections = [frameData.leftProjectionMatrix, frameData.rightProjectionMatrix]; + getStandingViewMatrix(viewMat, frameData.leftViewMatrix); + getStandingViewMatrix(viewMat2, frameData.rightViewMatrix); + var viewMats = [viewMat, viewMat2]; + renderSceneView(projections, viewMats, frameData.pose, /*multiview*/ true); + break; + } + else { + // Direct render to VR framebuffer + getStandingViewMatrix(viewMat, i == 0 ? frameData.leftViewMatrix : frameData.rightViewMatrix); + renderSceneView(i == 0 ? frameData.leftProjectionMatrix : frameData.rightProjectionMatrix, viewMat, frameData.pose); + } + + } + gl.disable(gl.SCISSOR_TEST); + } + else { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height); + getStandingViewMatrix(viewMat, frameData.leftViewMatrix); + renderSceneView(frameData.leftProjectionMatrix, viewMat, frameData.pose); + + gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height); + getStandingViewMatrix(viewMat, frameData.rightViewMatrix); + renderSceneView(frameData.rightProjectionMatrix, viewMat, frameData.pose); + } + - gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height); - getStandingViewMatrix(viewMat, frameData.rightViewMatrix); - renderSceneView(frameData.rightProjectionMatrix, viewMat, frameData.pose); vrDisplay.submitFrame(); } else { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.viewport(0, 0, webglCanvas.width, webglCanvas.height); mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0); getStandingViewMatrix(viewMat, frameData.leftViewMatrix); @@ -294,6 +343,7 @@ } else { window.requestAnimationFrame(onAnimationFrame); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // No VRDisplay found. gl.viewport(0, 0, webglCanvas.width, webglCanvas.height); mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);