2017-01-09 17:39:45 +03:00
|
|
|
|
/* 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 ipc_channel::ipc;
|
|
|
|
|
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
|
|
|
|
use msg::constellation_msg::PipelineId;
|
2017-04-14 00:10:42 +03:00
|
|
|
|
use script_traits::ConstellationMsg;
|
2017-01-09 17:39:45 +03:00
|
|
|
|
use servo_config::prefs::PREFS;
|
|
|
|
|
use std::{thread, time};
|
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
use std::sync::mpsc;
|
|
|
|
|
use std::sync::mpsc::{Receiver, Sender};
|
2017-07-13 07:52:27 +03:00
|
|
|
|
use webrender_api;
|
|
|
|
|
use webrender_api::DeviceIntSize;
|
2017-01-09 17:39:45 +03:00
|
|
|
|
use webvr_traits::{WebVRMsg, WebVRResult};
|
|
|
|
|
use webvr_traits::webvr::*;
|
|
|
|
|
|
|
|
|
|
/// WebVRThread owns native VRDisplays, handles their life cycle inside Servo and
|
|
|
|
|
/// acts a doorman for untrusted VR requests from DOM Objects. These are the key components
|
|
|
|
|
/// * WebVRThread::spawn() creates a long living thread that waits for VR Commands from DOM objects
|
|
|
|
|
/// and handles them in its trusted thread. The back and forth comunication with DOM is implemented
|
|
|
|
|
/// using IPC-channels. This thread creates the VRServiceManager instance, which handles the life cycle
|
|
|
|
|
/// of all VR Vendor SDKs and owns all the native VRDisplays. These displays are guaranteed to live while
|
|
|
|
|
/// the spawned thread is alive. The WebVRThread is unique and it's closed using the Exit message when the
|
|
|
|
|
/// whole browser is going to be closed.
|
|
|
|
|
/// * A Event Polling thread is created in order to implement WebVR Events (connected, disconnected,..).
|
|
|
|
|
/// This thread wakes up the WebVRThread from time to time by sending a PollEvents message. This thread
|
|
|
|
|
/// is only created when there is at least one live JavaScript context using the WebVR APIs and shuts down it when
|
|
|
|
|
/// the tab is closed. A single instance of the thread is used to handle multiple JavaScript contexts.
|
|
|
|
|
/// Constellation channel is used to notify events to the Script Thread.
|
|
|
|
|
/// * When the WeVR APIs are used in a tab, it's pipeline_id is registered using the RegisterContext message. When
|
|
|
|
|
/// the tab is closed, UnregisterContext message is sent. This way the WebVR thread has a list of the pipeline
|
|
|
|
|
/// ids using the WebVR APIs. These ids are used to implement privacy guidelines defined in the WebVR Spec.
|
|
|
|
|
/// * When a JavaScript thread gains access to present to a headset, WebVRThread is not used as a intermediary in
|
|
|
|
|
/// the VRDisplay.requestAnimationFrame loop in order to minimize latency. A direct communication with WebRender
|
|
|
|
|
/// is used instead. See WebVRCompositorHandler and the VRCompositorCommanda for more details.
|
|
|
|
|
pub struct WebVRThread {
|
|
|
|
|
receiver: IpcReceiver<WebVRMsg>,
|
|
|
|
|
sender: IpcSender<WebVRMsg>,
|
|
|
|
|
service: VRServiceManager,
|
|
|
|
|
contexts: HashSet<PipelineId>,
|
|
|
|
|
constellation_chan: Sender<ConstellationMsg>,
|
|
|
|
|
vr_compositor_chan: WebVRCompositorSender,
|
|
|
|
|
polling_events: bool,
|
2017-04-14 00:10:42 +03:00
|
|
|
|
presenting: HashMap<u32, PipelineId>
|
2017-01-09 17:39:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WebVRThread {
|
|
|
|
|
fn new(receiver: IpcReceiver<WebVRMsg>,
|
|
|
|
|
sender: IpcSender<WebVRMsg>,
|
|
|
|
|
constellation_chan: Sender<ConstellationMsg>,
|
|
|
|
|
vr_compositor_chan: WebVRCompositorSender)
|
|
|
|
|
-> WebVRThread {
|
|
|
|
|
let mut service = VRServiceManager::new();
|
|
|
|
|
service.register_defaults();
|
|
|
|
|
WebVRThread {
|
|
|
|
|
receiver: receiver,
|
|
|
|
|
sender: sender,
|
|
|
|
|
service: service,
|
|
|
|
|
contexts: HashSet::new(),
|
|
|
|
|
constellation_chan: constellation_chan,
|
|
|
|
|
vr_compositor_chan: vr_compositor_chan,
|
|
|
|
|
polling_events: false,
|
|
|
|
|
presenting: HashMap::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn spawn(constellation_chan: Sender<ConstellationMsg>,
|
|
|
|
|
vr_compositor_chan: WebVRCompositorSender)
|
|
|
|
|
-> IpcSender<WebVRMsg> {
|
|
|
|
|
let (sender, receiver) = ipc::channel().unwrap();
|
|
|
|
|
let sender_clone = sender.clone();
|
|
|
|
|
thread::Builder::new().name("WebVRThread".into()).spawn(move || {
|
|
|
|
|
WebVRThread::new(receiver, sender_clone, constellation_chan, vr_compositor_chan).start();
|
|
|
|
|
}).expect("Thread spawning failed");
|
|
|
|
|
sender
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn start(&mut self) {
|
|
|
|
|
while let Ok(msg) = self.receiver.recv() {
|
|
|
|
|
match msg {
|
|
|
|
|
WebVRMsg::RegisterContext(context) => {
|
|
|
|
|
self.handle_register_context(context);
|
|
|
|
|
self.schedule_poll_events();
|
|
|
|
|
},
|
|
|
|
|
WebVRMsg::UnregisterContext(context) => {
|
|
|
|
|
self.handle_unregister_context(context);
|
|
|
|
|
},
|
|
|
|
|
WebVRMsg::PollEvents(sender) => {
|
|
|
|
|
self.poll_events(sender);
|
|
|
|
|
},
|
|
|
|
|
WebVRMsg::GetDisplays(sender) => {
|
|
|
|
|
self.handle_get_displays(sender);
|
|
|
|
|
self.schedule_poll_events();
|
|
|
|
|
},
|
|
|
|
|
WebVRMsg::GetFrameData(pipeline_id, display_id, near, far, sender) => {
|
|
|
|
|
self.handle_framedata(pipeline_id, display_id, near, far, sender);
|
|
|
|
|
},
|
|
|
|
|
WebVRMsg::ResetPose(pipeline_id, display_id, sender) => {
|
|
|
|
|
self.handle_reset_pose(pipeline_id, display_id, sender);
|
|
|
|
|
},
|
|
|
|
|
WebVRMsg::RequestPresent(pipeline_id, display_id, sender) => {
|
|
|
|
|
self.handle_request_present(pipeline_id, display_id, sender);
|
|
|
|
|
},
|
|
|
|
|
WebVRMsg::ExitPresent(pipeline_id, display_id, sender) => {
|
|
|
|
|
self.handle_exit_present(pipeline_id, display_id, sender);
|
|
|
|
|
},
|
|
|
|
|
WebVRMsg::CreateCompositor(display_id) => {
|
|
|
|
|
self.handle_create_compositor(display_id);
|
|
|
|
|
},
|
2017-04-14 00:10:42 +03:00
|
|
|
|
WebVRMsg::GetGamepads(synced_ids, sender) => {
|
|
|
|
|
self.handle_get_gamepads(synced_ids, sender);
|
|
|
|
|
}
|
2017-01-09 17:39:45 +03:00
|
|
|
|
WebVRMsg::Exit => {
|
|
|
|
|
break
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_register_context(&mut self, ctx: PipelineId) {
|
|
|
|
|
self.contexts.insert(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_unregister_context(&mut self, ctx: PipelineId) {
|
|
|
|
|
self.contexts.remove(&ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_get_displays(&mut self, sender: IpcSender<WebVRResult<Vec<VRDisplayData>>>) {
|
|
|
|
|
let displays = self.service.get_displays();
|
|
|
|
|
let mut result = Vec::new();
|
|
|
|
|
for display in displays {
|
|
|
|
|
result.push(display.borrow().data());
|
|
|
|
|
}
|
|
|
|
|
sender.send(Ok(result)).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_framedata(&mut self,
|
|
|
|
|
pipeline: PipelineId,
|
2017-04-14 00:10:42 +03:00
|
|
|
|
display_id: u32,
|
2017-01-09 17:39:45 +03:00
|
|
|
|
near: f64,
|
|
|
|
|
far: f64,
|
|
|
|
|
sender: IpcSender<WebVRResult<VRFrameData>>) {
|
|
|
|
|
match self.access_check(pipeline, display_id) {
|
|
|
|
|
Ok(display) => {
|
|
|
|
|
sender.send(Ok(display.borrow().inmediate_frame_data(near, far))).unwrap()
|
|
|
|
|
},
|
|
|
|
|
Err(msg) => sender.send(Err(msg.into())).unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_reset_pose(&mut self,
|
|
|
|
|
pipeline: PipelineId,
|
2017-04-14 00:10:42 +03:00
|
|
|
|
display_id: u32,
|
2017-01-09 17:39:45 +03:00
|
|
|
|
sender: IpcSender<WebVRResult<VRDisplayData>>) {
|
|
|
|
|
match self.access_check(pipeline, display_id) {
|
|
|
|
|
Ok(display) => {
|
|
|
|
|
display.borrow_mut().reset_pose();
|
|
|
|
|
sender.send(Ok(display.borrow().data())).unwrap();
|
|
|
|
|
},
|
|
|
|
|
Err(msg) => {
|
|
|
|
|
sender.send(Err(msg.into())).unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This method implements the privacy and security guidelines defined in the WebVR spec.
|
|
|
|
|
// For example a secondary tab is not allowed to read VRDisplay data or stop a VR presentation
|
|
|
|
|
// while the user is having a VR experience in the current tab.
|
|
|
|
|
// These security rules also avoid multithreading race conditions between WebVRThread and
|
|
|
|
|
// Webrender thread. See WebVRCompositorHandler implementation notes for more details about this.
|
2017-04-14 00:10:42 +03:00
|
|
|
|
fn access_check(&self, pipeline: PipelineId, display_id: u32) -> Result<&VRDisplayPtr, &'static str> {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
if *self.presenting.get(&display_id).unwrap_or(&pipeline) != pipeline {
|
|
|
|
|
return Err("No access granted to this Display because it's presenting on other JavaScript Tab");
|
|
|
|
|
}
|
|
|
|
|
self.service.get_display(display_id).ok_or("Device not found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_request_present(&mut self,
|
|
|
|
|
pipeline: PipelineId,
|
2017-04-14 00:10:42 +03:00
|
|
|
|
display_id: u32,
|
2017-01-09 17:39:45 +03:00
|
|
|
|
sender: IpcSender<WebVRResult<()>>) {
|
|
|
|
|
match self.access_check(pipeline, display_id).map(|d| d.clone()) {
|
|
|
|
|
Ok(display) => {
|
|
|
|
|
self.presenting.insert(display_id, pipeline);
|
|
|
|
|
let data = display.borrow().data();
|
|
|
|
|
sender.send(Ok(())).unwrap();
|
2017-04-14 00:10:42 +03:00
|
|
|
|
self.notify_event(VRDisplayEvent::PresentChange(data, true).into());
|
2017-01-09 17:39:45 +03:00
|
|
|
|
},
|
|
|
|
|
Err(msg) => {
|
|
|
|
|
sender.send(Err(msg.into())).unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_exit_present(&mut self,
|
|
|
|
|
pipeline: PipelineId,
|
2017-04-14 00:10:42 +03:00
|
|
|
|
display_id: u32,
|
2017-01-09 17:39:45 +03:00
|
|
|
|
sender: Option<IpcSender<WebVRResult<()>>>) {
|
|
|
|
|
match self.access_check(pipeline, display_id).map(|d| d.clone()) {
|
|
|
|
|
Ok(display) => {
|
|
|
|
|
self.presenting.remove(&display_id);
|
|
|
|
|
if let Some(sender) = sender {
|
|
|
|
|
sender.send(Ok(())).unwrap();
|
|
|
|
|
}
|
|
|
|
|
let data = display.borrow().data();
|
2017-04-14 00:10:42 +03:00
|
|
|
|
self.notify_event(VRDisplayEvent::PresentChange(data, false).into());
|
2017-01-09 17:39:45 +03:00
|
|
|
|
},
|
|
|
|
|
Err(msg) => {
|
|
|
|
|
if let Some(sender) = sender {
|
|
|
|
|
sender.send(Err(msg.into())).unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 00:10:42 +03:00
|
|
|
|
fn handle_create_compositor(&mut self, display_id: u32) {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
let compositor = self.service.get_display(display_id).map(|d| WebVRCompositor(d.as_ptr()));
|
|
|
|
|
self.vr_compositor_chan.send(compositor).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 00:10:42 +03:00
|
|
|
|
fn handle_get_gamepads(&mut self,
|
|
|
|
|
synced_ids: Vec<u32>,
|
|
|
|
|
sender: IpcSender<WebVRResult<Vec<(Option<VRGamepadData>, VRGamepadState)>>>) {
|
|
|
|
|
let gamepads = self.service.get_gamepads();
|
|
|
|
|
let data = gamepads.iter().map(|g| {
|
|
|
|
|
let g = g.borrow();
|
|
|
|
|
// Optimization, don't fetch and send gamepad static data when the gamepad is already synced.
|
|
|
|
|
let data = if synced_ids.iter().any(|v| *v == g.id()) {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(g.data())
|
|
|
|
|
};
|
|
|
|
|
(data, g.state())
|
|
|
|
|
}).collect();
|
|
|
|
|
sender.send(Ok(data)).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-09 17:39:45 +03:00
|
|
|
|
fn poll_events(&mut self, sender: IpcSender<bool>) {
|
|
|
|
|
loop {
|
|
|
|
|
let events = self.service.poll_events();
|
|
|
|
|
if events.is_empty() {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
self.notify_events(events)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop polling events if the callers are not using VR
|
|
|
|
|
self.polling_events = self.contexts.len() > 0;
|
|
|
|
|
sender.send(self.polling_events).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 00:10:42 +03:00
|
|
|
|
fn notify_events(&self, events: Vec<VREvent>) {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
let pipeline_ids: Vec<PipelineId> = self.contexts.iter().map(|c| *c).collect();
|
2017-04-14 00:10:42 +03:00
|
|
|
|
self.constellation_chan.send(ConstellationMsg::WebVREvents(pipeline_ids.clone(), events)).unwrap();
|
2017-01-09 17:39:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
2017-04-14 00:10:42 +03:00
|
|
|
|
fn notify_event(&self, event: VREvent) {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
self.notify_events(vec![event]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn schedule_poll_events(&mut self) {
|
|
|
|
|
if !self.service.is_initialized() || self.polling_events {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.polling_events = true;
|
|
|
|
|
let webvr_thread = self.sender.clone();
|
|
|
|
|
let (sender, receiver) = ipc::channel().unwrap();
|
|
|
|
|
|
|
|
|
|
// Defines the polling interval time in ms for VR Events such as VRDisplay connected, disconnected, etc.
|
|
|
|
|
let polling_interval: u64 = PREFS.get("dom.webvr.event_polling_interval").as_u64().unwrap_or(500);
|
|
|
|
|
|
|
|
|
|
thread::Builder::new().name("WebVRPollEvents".into()).spawn(move || {
|
|
|
|
|
loop {
|
|
|
|
|
if webvr_thread.send(WebVRMsg::PollEvents(sender.clone())).is_err() {
|
|
|
|
|
// WebVR Thread closed
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if !receiver.recv().unwrap_or(false) {
|
|
|
|
|
// WebVR Thread asked to unschedule this thread
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
thread::sleep(time::Duration::from_millis(polling_interval));
|
|
|
|
|
}
|
|
|
|
|
}).expect("Thread spawning failed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Notes about WebVRCompositorHandler implementation:
|
|
|
|
|
/// Raw pointers are used instead of Arc<Mutex> as a heavy optimization for latency reasons.
|
|
|
|
|
/// This also avoids "JS DDoS" attacks: like a secondary JavaScript tab degrading performance
|
|
|
|
|
/// by flooding the WebVRThread with messages while the main JavaScript tab is presenting to the headset.
|
|
|
|
|
/// Multithreading won't be a problem because:
|
|
|
|
|
/// * Thanks to the security rules implemented in the WebVRThread, when a VRDisplay is in a presenting loop
|
|
|
|
|
/// no other JSContext is granted access to the VRDisplay. So really there aren’t multithreading race conditions.
|
|
|
|
|
/// * VRDisplay implementations are designed to allow calling compositor functions
|
|
|
|
|
/// in another thread by using the Send + Sync traits.
|
|
|
|
|
/// VRDisplays pointers are guaranteed to be valid memory:
|
|
|
|
|
/// * VRDisplays are owned by the VRServiceManager which lives in the WebVRThread.
|
|
|
|
|
/// * WebVRCompositorHandler is stopped automatically when a JS tab is closed or the whole browser is closed.
|
|
|
|
|
/// * 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 WebVRCompositorHandler {
|
2017-07-13 07:52:27 +03:00
|
|
|
|
compositors: HashMap<webrender_api::VRCompositorId, WebVRCompositor>,
|
2017-01-09 17:39:45 +03:00
|
|
|
|
webvr_thread_receiver: Receiver<Option<WebVRCompositor>>,
|
|
|
|
|
webvr_thread_sender: Option<IpcSender<WebVRMsg>>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(unsafe_code)]
|
|
|
|
|
unsafe impl Send for WebVRCompositor {}
|
|
|
|
|
|
|
|
|
|
pub type WebVRCompositorSender = Sender<Option<WebVRCompositor>>;
|
|
|
|
|
|
|
|
|
|
impl WebVRCompositorHandler {
|
|
|
|
|
pub fn new() -> (Box<WebVRCompositorHandler>, WebVRCompositorSender) {
|
|
|
|
|
let (sender, receiver) = mpsc::channel();
|
|
|
|
|
let instance = Box::new(WebVRCompositorHandler {
|
|
|
|
|
compositors: HashMap::new(),
|
|
|
|
|
webvr_thread_receiver: receiver,
|
|
|
|
|
webvr_thread_sender: None
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
(instance, sender)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 07:52:27 +03:00
|
|
|
|
impl webrender_api::VRCompositorHandler for WebVRCompositorHandler {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
#[allow(unsafe_code)]
|
2017-07-13 07:52:27 +03:00
|
|
|
|
fn handle(&mut self, cmd: webrender_api::VRCompositorCommand, texture: Option<(u32, DeviceIntSize)>) {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
match cmd {
|
2017-07-13 07:52:27 +03:00
|
|
|
|
webrender_api::VRCompositorCommand::Create(compositor_id) => {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
self.create_compositor(compositor_id);
|
|
|
|
|
}
|
2017-07-13 07:52:27 +03:00
|
|
|
|
webrender_api::VRCompositorCommand::SyncPoses(compositor_id, near, far, sender) => {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
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()
|
|
|
|
|
};
|
|
|
|
|
let _ = sender.send(Ok(pose));
|
|
|
|
|
} else {
|
|
|
|
|
let _ = sender.send(Err(()));
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-13 07:52:27 +03:00
|
|
|
|
webrender_api::VRCompositorCommand::SubmitFrame(compositor_id, left_bounds, right_bounds) => {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
if let Some(compositor) = self.compositors.get(&compositor_id) {
|
2017-04-26 15:56:32 +03:00
|
|
|
|
if let Some((texture_id, size)) = texture {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
let layer = VRLayer {
|
|
|
|
|
texture_id: texture_id,
|
|
|
|
|
left_bounds: left_bounds,
|
2017-04-14 00:10:42 +03:00
|
|
|
|
right_bounds: right_bounds,
|
2017-04-26 15:56:32 +03:00
|
|
|
|
texture_size: Some((size.width as u32, size.height as u32))
|
2017-01-09 17:39:45 +03:00
|
|
|
|
};
|
|
|
|
|
unsafe {
|
|
|
|
|
(*compositor.0).submit_frame(&layer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-13 07:52:27 +03:00
|
|
|
|
webrender_api::VRCompositorCommand::Release(compositor_id) => {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
self.compositors.remove(&compositor_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WebVRCompositorHandler {
|
|
|
|
|
#[allow(unsafe_code)]
|
2017-07-13 07:52:27 +03:00
|
|
|
|
fn create_compositor(&mut self, display_id: webrender_api::VRCompositorId) {
|
2017-01-09 17:39:45 +03:00
|
|
|
|
let sender = match self.webvr_thread_sender {
|
|
|
|
|
Some(ref s) => s,
|
|
|
|
|
None => return,
|
|
|
|
|
};
|
|
|
|
|
|
2017-04-14 00:10:42 +03:00
|
|
|
|
sender.send(WebVRMsg::CreateCompositor(display_id as u32)).unwrap();
|
2017-01-09 17:39:45 +03:00
|
|
|
|
let display = self.webvr_thread_receiver.recv().unwrap();
|
|
|
|
|
|
|
|
|
|
match display {
|
|
|
|
|
Some(display) => {
|
|
|
|
|
self.compositors.insert(display_id, display);
|
|
|
|
|
},
|
|
|
|
|
None => {
|
|
|
|
|
error!("VRDisplay not found when creating a new VRCompositor");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is done on only a per-platform basis on initialization.
|
|
|
|
|
pub fn set_webvr_thread_sender(&mut self, sender: IpcSender<WebVRMsg>) {
|
|
|
|
|
self.webvr_thread_sender = Some(sender);
|
|
|
|
|
}
|
|
|
|
|
}
|