use std::cell::Cell;
use std::f64::consts::{FRAC_PI_2, PI};
use std::mem;
use std::rc::Rc;
use dom_struct::dom_struct;
use euclid::{RigidTransform3D, Transform3D, Vector3D};
use ipc_channel::ipc::IpcReceiver;
use ipc_channel::router::ROUTER;
use metrics::ToMs;
use profile_traits::ipc;
use webxr_api::{
self, util, ApiSpace, ContextId as WebXRContextId, Display, EntityTypes, EnvironmentBlendMode,
Event as XREvent, Frame, FrameUpdateEvent, HitTestId, HitTestSource, Ray, SelectEvent,
SelectKind, Session, SessionId, View, Viewer, Visibility,
};
use super::bindings::trace::HashMapTracedValues;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::{
XRHitTestOptionsInit, XRHitTestTrackableType,
};
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType;
use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::{
XRRenderStateInit, XRRenderStateMethods,
};
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::{
XREnvironmentBlendMode, XRFrameRequestCallback, XRSessionMethods, XRVisibilityState,
};
use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode;
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, MutDom, MutNullableDom};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::performance::reduce_timing_resolution;
use crate::dom::promise::Promise;
use crate::dom::xrframe::XRFrame;
use crate::dom::xrhittestsource::XRHitTestSource;
use crate::dom::xrinputsourcearray::XRInputSourceArray;
use crate::dom::xrinputsourceevent::XRInputSourceEvent;
use crate::dom::xrreferencespace::XRReferenceSpace;
use crate::dom::xrrenderstate::XRRenderState;
use crate::dom::xrsessionevent::XRSessionEvent;
use crate::dom::xrspace::XRSpace;
use crate::realms::InRealm;
use crate::task_source::TaskSource;
#[dom_struct]
pub struct XRSession {
eventtarget: EventTarget,
blend_mode: XREnvironmentBlendMode,
mode: XRSessionMode,
visibility_state: Cell<XRVisibilityState>,
viewer_space: MutNullableDom<XRSpace>,
#[ignore_malloc_size_of = "defined in webxr"]
#[no_trace]
session: DomRefCell<Session>,
frame_requested: Cell<bool>,
pending_render_state: MutNullableDom<XRRenderState>,
active_render_state: MutDom<XRRenderState>,
#[no_trace]
inline_projection_matrix: DomRefCell<Transform3D<f32, Viewer, Display>>,
next_raf_id: Cell<i32>,
#[ignore_malloc_size_of = "closures are hard"]
raf_callback_list: DomRefCell<Vec<(i32, Option<Rc<XRFrameRequestCallback>>)>>,
#[ignore_malloc_size_of = "closures are hard"]
current_raf_callback_list: DomRefCell<Vec<(i32, Option<Rc<XRFrameRequestCallback>>)>>,
input_sources: Dom<XRInputSourceArray>,
#[ignore_malloc_size_of = "promises are hard"]
end_promises: DomRefCell<Vec<Rc<Promise>>>,
ended: Cell<bool>,
#[ignore_malloc_size_of = "defined in webxr"]
#[no_trace]
next_hit_test_id: Cell<HitTestId>,
#[ignore_malloc_size_of = "defined in webxr"]
pending_hit_test_promises: DomRefCell<HashMapTracedValues<HitTestId, Rc<Promise>>>,
outside_raf: Cell<bool>,
}
impl XRSession {
fn new_inherited(
session: Session,
render_state: &XRRenderState,
input_sources: &XRInputSourceArray,
mode: XRSessionMode,
) -> XRSession {
XRSession {
eventtarget: EventTarget::new_inherited(),
blend_mode: session.environment_blend_mode().into(),
mode,
visibility_state: Cell::new(XRVisibilityState::Visible),
viewer_space: Default::default(),
session: DomRefCell::new(session),
frame_requested: Cell::new(false),
pending_render_state: MutNullableDom::new(None),
active_render_state: MutDom::new(render_state),
inline_projection_matrix: Default::default(),
next_raf_id: Cell::new(0),
raf_callback_list: DomRefCell::new(vec![]),
current_raf_callback_list: DomRefCell::new(vec![]),
input_sources: Dom::from_ref(input_sources),
end_promises: DomRefCell::new(vec![]),
ended: Cell::new(false),
next_hit_test_id: Cell::new(HitTestId(0)),
pending_hit_test_promises: DomRefCell::new(HashMapTracedValues::new()),
outside_raf: Cell::new(true),
}
}
pub fn new(
global: &GlobalScope,
session: Session,
mode: XRSessionMode,
frame_receiver: IpcReceiver<Frame>,
) -> DomRoot<XRSession> {
let ivfov = if mode == XRSessionMode::Inline {
Some(FRAC_PI_2)
} else {
None
};
let render_state = XRRenderState::new(global, 0.1, 1000.0, ivfov, None, Vec::new());
let input_sources = XRInputSourceArray::new(global);
let ret = reflect_dom_object(
Box::new(XRSession::new_inherited(
session,
&render_state,
&input_sources,
mode,
)),
global,
);
ret.attach_event_handler();
ret.setup_raf_loop(frame_receiver);
ret
}
pub fn with_session<R, F: FnOnce(&Session) -> R>(&self, with: F) -> R {
let session = self.session.borrow();
with(&session)
}
pub fn is_ended(&self) -> bool {
self.ended.get()
}
pub fn is_immersive(&self) -> bool {
self.mode != XRSessionMode::Inline
}
pub fn has_layers_feature(&self) -> bool {
false
}
fn setup_raf_loop(&self, frame_receiver: IpcReceiver<Frame>) {
let this = Trusted::new(self);
let global = self.global();
let window = global.as_window();
let (task_source, canceller) = window
.task_manager()
.dom_manipulation_task_source_with_canceller();
ROUTER.add_route(
frame_receiver.to_opaque(),
Box::new(move |message| {
#[allow(unused)]
let mut frame: Frame = message.to().unwrap();
#[cfg(feature = "xr-profile")]
{
let received = time::precise_time_ns();
println!(
"WEBXR PROFILING [raf receive]:\t{}ms",
(received - frame.sent_time) as f64 / 1_000_000.
);
frame.sent_time = received;
}
let this = this.clone();
let _ = task_source.queue_with_canceller(
task!(xr_raf_callback: move || {
this.root().raf_callback(frame);
}),
&canceller,
);
}),
);
self.session.borrow_mut().start_render_loop();
}
pub fn is_outside_raf(&self) -> bool {
self.outside_raf.get()
}
fn attach_event_handler(&self) {
let this = Trusted::new(self);
let global = self.global();
let window = global.as_window();
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 this = this.clone();
let _ = task_source.queue_with_canceller(
task!(xr_event_callback: move || {
this.root().event_callback(message.to().unwrap());
}),
&canceller,
);
}),
);
self.session.borrow_mut().set_event_dest(sender);
}
pub fn setup_initial_inputs(&self) {
let initial_inputs = self.session.borrow().initial_inputs().to_owned();
if initial_inputs.is_empty() {
return;
}
let global = self.global();
let window = global.as_window();
let (task_source, canceller) = window
.task_manager()
.dom_manipulation_task_source_with_canceller();
let this = Trusted::new(self);
let _ = task_source.queue_with_canceller(
task!(session_initial_inputs: move || {
let this = this.root();
this.input_sources.add_input_sources(&this, &initial_inputs);
}),
&canceller,
);
}
fn event_callback(&self, event: XREvent) {
match event {
XREvent::SessionEnd => {
self.ended.set(true);
self.global().as_window().Navigator().Xr().end_session(self);
for promise in self.end_promises.borrow_mut().drain(..) {
promise.resolve_native(&());
}
let event = XRSessionEvent::new(&self.global(), atom!("end"), false, false, self);
event.upcast::<Event>().fire(self.upcast());
},
XREvent::Select(input, kind, ty, frame) => {
use servo_atoms::Atom;
const START_ATOMS: [Atom; 2] = [atom!("selectstart"), atom!("squeezestart")];
const EVENT_ATOMS: [Atom; 2] = [atom!("select"), atom!("squeeze")];
const END_ATOMS: [Atom; 2] = [atom!("selectend"), atom!("squeezeend")];
let source = self.input_sources.find(input);
let atom_index = if kind == SelectKind::Squeeze { 1 } else { 0 };
if let Some(source) = source {
let frame = XRFrame::new(&self.global(), self, frame);
frame.set_active(true);
if ty == SelectEvent::Start {
let event = XRInputSourceEvent::new(
&self.global(),
START_ATOMS[atom_index].clone(),
false,
false,
&frame,
&source,
);
event.upcast::<Event>().fire(self.upcast());
} else {
if ty == SelectEvent::Select {
let event = XRInputSourceEvent::new(
&self.global(),
EVENT_ATOMS[atom_index].clone(),
false,
false,
&frame,
&source,
);
event.upcast::<Event>().fire(self.upcast());
}
let event = XRInputSourceEvent::new(
&self.global(),
END_ATOMS[atom_index].clone(),
false,
false,
&frame,
&source,
);
event.upcast::<Event>().fire(self.upcast());
}
frame.set_active(false);
}
},
XREvent::VisibilityChange(v) => {
let v = match v {
Visibility::Visible => XRVisibilityState::Visible,
Visibility::VisibleBlurred => XRVisibilityState::Visible_blurred,
Visibility::Hidden => XRVisibilityState::Hidden,
};
self.visibility_state.set(v);
let event = XRSessionEvent::new(
&self.global(),
atom!("visibilitychange"),
false,
false,
self,
);
event.upcast::<Event>().fire(self.upcast());
self.dirty_layers();
},
XREvent::AddInput(info) => {
self.input_sources.add_input_sources(self, &[info]);
},
XREvent::RemoveInput(id) => {
self.input_sources.remove_input_source(self, id);
},
XREvent::UpdateInput(id, source) => {
self.input_sources.add_remove_input_source(self, id, source);
},
}
}
fn raf_callback(&self, mut frame: Frame) {
debug!("WebXR RAF callback {:?}", frame);
#[cfg(feature = "xr-profile")]
let raf_start = time::precise_time_ns();
#[cfg(feature = "xr-profile")]
println!(
"WEBXR PROFILING [raf queued]:\t{}ms",
(raf_start - frame.sent_time) as f64 / 1_000_000.
);
if let Some(pending) = self.pending_render_state.take() {
self.active_render_state.set(&pending);
if !self.is_immersive() {
self.update_inline_projection_matrix()
}
}
for event in frame.events.drain(..) {
self.handle_frame_event(event);
}
if !self
.active_render_state
.get()
.has_sub_images(&frame.sub_images[..])
{
warn!("Rendering blank XR frame");
self.session.borrow_mut().render_animation_frame();
return;
}
{
let mut current = self.current_raf_callback_list.borrow_mut();
assert!(current.is_empty());
mem::swap(&mut *self.raf_callback_list.borrow_mut(), &mut current);
}
let start = self.global().as_window().get_navigation_start();
let time = reduce_timing_resolution((frame.time_ns - start).to_ms());
let frame = XRFrame::new(&self.global(), self, frame);
frame.set_active(true);
frame.set_animation_frame(true);
self.apply_frame_updates(&frame);
self.layers_begin_frame(&frame);
self.outside_raf.set(false);
let len = self.current_raf_callback_list.borrow().len();
for i in 0..len {
let callback = self.current_raf_callback_list.borrow()[i]
.1
.as_ref()
.map(Rc::clone);
if let Some(callback) = callback {
let _ = callback.Call__(time, &frame, ExceptionHandling::Report);
}
}
self.outside_raf.set(true);
*self.current_raf_callback_list.borrow_mut() = vec![];
self.layers_end_frame(&frame);
frame.set_active(false);
self.session.borrow_mut().render_animation_frame();
#[cfg(feature = "xr-profile")]
println!(
"WEBXR PROFILING [raf execute]:\t{}ms",
(time::precise_time_ns() - raf_start) as f64 / 1_000_000.
);
}
fn update_inline_projection_matrix(&self) {
debug_assert!(!self.is_immersive());
let render_state = self.active_render_state.get();
let size = if let Some(base) = render_state.GetBaseLayer() {
base.size()
} else {
return;
};
let mut clip_planes = util::ClipPlanes::default();
let near = *render_state.DepthNear() as f32;
let far = *render_state.DepthFar() as f32;
clip_planes.update(near, far);
let top = *render_state
.GetInlineVerticalFieldOfView()
.expect("IVFOV should be non null for inline sessions") /
2.;
let top = near * top.tan() as f32;
let bottom = top;
let left = top * size.width as f32 / size.height as f32;
let right = left;
let matrix = util::frustum_to_projection_matrix(left, right, top, bottom, clip_planes);
*self.inline_projection_matrix.borrow_mut() = matrix;
}
pub fn inline_view(&self) -> View<Viewer> {
debug_assert!(!self.is_immersive());
View {
transform: RigidTransform3D::identity(),
projection: *self.inline_projection_matrix.borrow(),
}
}
pub fn session_id(&self) -> SessionId {
self.session.borrow().id()
}
pub fn dirty_layers(&self) {
if let Some(layer) = self.RenderState().GetBaseLayer() {
layer.context().mark_as_dirty();
}
}
fn layers_begin_frame(&self, frame: &XRFrame) {
if let Some(layer) = self.active_render_state.get().GetBaseLayer() {
layer.begin_frame(frame);
}
self.active_render_state.get().with_layers(|layers| {
for layer in layers {
layer.begin_frame(frame);
}
});
}
fn layers_end_frame(&self, frame: &XRFrame) {
if let Some(layer) = self.active_render_state.get().GetBaseLayer() {
layer.end_frame(frame);
}
self.active_render_state.get().with_layers(|layers| {
for layer in layers {
layer.end_frame(frame);
}
});
}
fn apply_frame_updates(&self, _frame: &XRFrame) {
}
fn handle_frame_event(&self, event: FrameUpdateEvent) {
match event {
FrameUpdateEvent::HitTestSourceAdded(id) => {
if let Some(promise) = self.pending_hit_test_promises.borrow_mut().remove(&id) {
promise.resolve_native(&XRHitTestSource::new(&self.global(), id, self));
} else {
warn!(
"received hit test add request for unknown hit test {:?}",
id
)
}
},
_ => self.session.borrow_mut().apply_event(event),
}
}
}
impl XRSessionMethods for XRSession {
event_handler!(end, GetOnend, SetOnend);
event_handler!(select, GetOnselect, SetOnselect);
event_handler!(selectstart, GetOnselectstart, SetOnselectstart);
event_handler!(selectend, GetOnselectend, SetOnselectend);
event_handler!(squeeze, GetOnsqueeze, SetOnsqueeze);
event_handler!(squeezestart, GetOnsqueezestart, SetOnsqueezestart);
event_handler!(squeezeend, GetOnsqueezeend, SetOnsqueezeend);
event_handler!(
visibilitychange,
GetOnvisibilitychange,
SetOnvisibilitychange
);
event_handler!(
inputsourceschange,
GetOninputsourceschange,
SetOninputsourceschange
);
fn RenderState(&self) -> DomRoot<XRRenderState> {
self.active_render_state.get()
}
fn UpdateRenderState(&self, init: &XRRenderStateInit, _: InRealm) -> ErrorResult {
if self.ended.get() {
return Err(Error::InvalidState);
}
if let Some(Some(ref layer)) = init.baseLayer {
if Dom::from_ref(layer.session()) != Dom::from_ref(self) {
return Err(Error::InvalidState);
}
}
if init.inlineVerticalFieldOfView.is_some() && self.is_immersive() {
return Err(Error::InvalidState);
}
if init.baseLayer.is_some() {
if self.has_layers_feature() {
return Err(Error::NotSupported);
}
if init.layers.is_some() {
return Err(Error::Type(String::from(
"Cannot set WebXR layers and baseLayer",
)));
}
}
if let Some(Some(ref layers)) = init.layers {
for layer in layers {
let count = layers
.iter()
.filter(|other| other.layer_id() == layer.layer_id())
.count();
if count > 1 {
return Err(Error::Type(String::from("Duplicate entry in WebXR layers")));
}
}
for layer in layers {
if layer.session() != self {
return Err(Error::Type(String::from(
"Layer from different session in WebXR layers",
)));
}
}
}
let pending = self
.pending_render_state
.or_init(|| self.active_render_state.get().clone_object());
if let Some(ref layers) = init.layers {
let layers = layers.as_deref().unwrap_or_default();
pending.set_base_layer(None);
pending.set_layers(layers.iter().map(|x| &**x).collect());
let layers = layers
.iter()
.filter_map(|layer| {
let context_id = WebXRContextId::from(layer.context_id());
let layer_id = layer.layer_id()?;
Some((context_id, layer_id))
})
.collect();
self.session.borrow_mut().set_layers(layers);
}
if let Some(near) = init.depthNear {
let mut near = *near;
if near < 0. {
near = 0.;
}
pending.set_depth_near(near);
}
if let Some(far) = init.depthFar {
pending.set_depth_far(*far);
}
if let Some(fov) = init.inlineVerticalFieldOfView {
let mut fov = *fov;
if fov < 0. {
fov = 0.0001;
} else if fov > PI {
fov = PI - 0.0001;
}
pending.set_inline_vertical_fov(fov);
}
if let Some(ref layer) = init.baseLayer {
pending.set_base_layer(layer.as_deref());
pending.set_layers(Vec::new());
let layers = layer
.iter()
.filter_map(|layer| {
let context_id = WebXRContextId::from(layer.context_id());
let layer_id = layer.layer_id()?;
Some((context_id, layer_id))
})
.collect();
self.session.borrow_mut().set_layers(layers);
}
if init.depthFar.is_some() || init.depthNear.is_some() {
self.session
.borrow_mut()
.update_clip_planes(*pending.DepthNear() as f32, *pending.DepthFar() as f32);
}
Ok(())
}
fn RequestAnimationFrame(&self, callback: Rc<XRFrameRequestCallback>) -> i32 {
let raf_id = self.next_raf_id.get();
self.next_raf_id.set(raf_id + 1);
self.raf_callback_list
.borrow_mut()
.push((raf_id, Some(callback)));
raf_id
}
fn CancelAnimationFrame(&self, frame: i32) {
let mut list = self.raf_callback_list.borrow_mut();
if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
pair.1 = None;
}
let mut list = self.current_raf_callback_list.borrow_mut();
if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
pair.1 = None;
}
}
fn EnvironmentBlendMode(&self) -> XREnvironmentBlendMode {
self.blend_mode
}
fn VisibilityState(&self) -> XRVisibilityState {
self.visibility_state.get()
}
fn RequestReferenceSpace(&self, ty: XRReferenceSpaceType, comp: InRealm) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp);
match ty {
XRReferenceSpaceType::Bounded_floor | XRReferenceSpaceType::Unbounded => {
p.reject_error(Error::NotSupported)
},
ty => {
if ty != XRReferenceSpaceType::Viewer &&
(!self.is_immersive() || ty != XRReferenceSpaceType::Local)
{
let s = ty.as_str();
if !self
.session
.borrow()
.granted_features()
.iter()
.any(|f| *f == s)
{
p.reject_error(Error::NotSupported);
return p;
}
}
p.resolve_native(&XRReferenceSpace::new(&self.global(), self, ty));
},
}
p
}
fn InputSources(&self) -> DomRoot<XRInputSourceArray> {
DomRoot::from_ref(&*self.input_sources)
}
fn End(&self) -> Rc<Promise> {
let global = self.global();
let p = Promise::new(&global);
if self.ended.get() && self.end_promises.borrow().is_empty() {
p.resolve_native(&());
return p;
}
self.end_promises.borrow_mut().push(p.clone());
self.ended.set(true);
global.as_window().Navigator().Xr().end_session(self);
self.session.borrow_mut().end_session();
p
}
fn RequestHitTestSource(&self, options: &XRHitTestOptionsInit) -> Rc<Promise> {
let p = Promise::new(&self.global());
if !self
.session
.borrow()
.granted_features()
.iter()
.any(|f| f == "hit-test")
{
p.reject_error(Error::NotSupported);
return p;
}
let id = self.next_hit_test_id.get();
self.next_hit_test_id.set(HitTestId(id.0 + 1));
let space = options.space.space();
let ray = if let Some(ref ray) = options.offsetRay {
ray.ray()
} else {
Ray {
origin: Vector3D::new(0., 0., 0.),
direction: Vector3D::new(0., 0., -1.),
}
};
let mut types = EntityTypes::default();
if let Some(ref tys) = options.entityTypes {
for ty in tys {
match ty {
XRHitTestTrackableType::Point => types.point = true,
XRHitTestTrackableType::Plane => types.plane = true,
XRHitTestTrackableType::Mesh => types.mesh = true,
}
}
} else {
types.plane = true;
}
let source = HitTestSource {
id,
space,
ray,
types,
};
self.pending_hit_test_promises
.borrow_mut()
.insert(id, p.clone());
self.session.borrow().request_hit_test(source);
p
}
}
pub type ApiPose = RigidTransform3D<f32, ApiSpace, webxr_api::Native>;
pub type ApiRigidTransform = RigidTransform3D<f32, ApiSpace, ApiSpace>;
#[derive(Clone, Copy)]
pub struct BaseSpace;
pub type BaseTransform = RigidTransform3D<f32, webxr_api::Native, BaseSpace>;
#[allow(unsafe_code)]
pub fn cast_transform<T, U, V, W>(
transform: RigidTransform3D<f32, T, U>,
) -> RigidTransform3D<f32, V, W> {
unsafe { mem::transmute(transform) }
}
impl From<EnvironmentBlendMode> for XREnvironmentBlendMode {
fn from(x: EnvironmentBlendMode) -> Self {
match x {
EnvironmentBlendMode::Opaque => XREnvironmentBlendMode::Opaque,
EnvironmentBlendMode::AlphaBlend => XREnvironmentBlendMode::Alpha_blend,
EnvironmentBlendMode::Additive => XREnvironmentBlendMode::Additive,
}
}
}