Skip to content
This repository has been archived by the owner on Jul 10, 2023. It is now read-only.

Add glwindow implementation, that renders to a GlWindow #66

Merged
merged 2 commits into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust-webvr-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-webvr-api"
version = "0.10.3"
version = "0.10.4"
authors = ["The Servo Project Developers"]

homepage = "https://github.com/servo/rust-webvr"
Expand Down
2 changes: 2 additions & 0 deletions rust-webvr-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub mod vr_stage_parameters;
pub mod vr_event;
pub mod vr_field_view;
pub mod vr_gamepad;
pub mod vr_main_thread_heartbeat;

pub use vr_display::{VRDisplay,VRDisplayPtr};
pub use vr_service::{VRService,VRServiceCreator};
Expand All @@ -56,3 +57,4 @@ pub use vr_event::{VREvent, VRDisplayEvent, VRDisplayEventReason, VRGamepadEvent
pub use vr_field_view::VRFieldOfView;
pub use vr_gamepad::{VRGamepad, VRGamepadPtr, VRGamepadHand,
VRGamepadData, VRGamepadState, VRGamepadButton};
pub use vr_main_thread_heartbeat::VRMainThreadHeartbeat;
10 changes: 10 additions & 0 deletions rust-webvr-api/src/vr_main_thread_heartbeat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// Each VR service may have some code which needs to run on the main thread,
/// for example window creation on MacOS is only supported on the main thread.
/// Implementations of this trait will usually be neither `Sync` nor `Send`.
pub trait VRMainThreadHeartbeat {
/// Run the heartbeat on the main thread.
fn heartbeat(&mut self);

/// Is the heartbeat expecting to be called every frame?
fn heart_racing(&self) -> bool;
}
9 changes: 7 additions & 2 deletions rust-webvr/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-webvr"
version = "0.10.1"
version = "0.10.2"
authors = ["The Servo Project Developers"]

homepage = "https://github.com/servo/rust-webvr"
Expand All @@ -21,18 +21,23 @@ build = "build.rs"
[features]
default = ["vrexternal", "openvr", "mock"]
vrexternal = []
glwindow = ["euclid", "gleam", "glutin", "winit"]
openvr = ["libloading"]
mock = []
googlevr = ["gvr-sys"]
oculusvr = ["ovr-mobile-sys"]

[dependencies]
rust-webvr-api = { path = "../rust-webvr-api", version = "0.10" }
rust-webvr-api = { path = "../rust-webvr-api", version = "0.10.4" }
log = "0.4"
libloading = { version = "0.5", optional = true, default-features = false }
gvr-sys = { version = "0.7", optional = true }
ovr-mobile-sys = { version = "0.4", optional = true }
libc = "0.2"
euclid = { version = "0.19", optional = true }
gleam = { version = "0.6", optional = true }
glutin = { version = "0.19", optional = true }
winit = { version = "0.18", optional = true }

[build-dependencies]
gl_generator = "0.10"
Expand Down
261 changes: 261 additions & 0 deletions rust-webvr/src/api/glwindow/display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
use euclid::Angle;
use euclid::Trig;
use gleam::gl;
use gleam::gl::Gl;
use rust_webvr_api::utils;
use rust_webvr_api::VRDisplay;
use rust_webvr_api::VRDisplayCapabilities;
use rust_webvr_api::VRDisplayData;
use rust_webvr_api::VREyeParameters;
use rust_webvr_api::VRFieldOfView;
use rust_webvr_api::VRFrameData;
use rust_webvr_api::VRFutureFrameData;
use rust_webvr_api::VRFramebuffer;
use rust_webvr_api::VRFramebufferAttributes;
use rust_webvr_api::VRLayer;
use rust_webvr_api::VRViewport;
use std::cell::RefCell;
use std::sync::Arc;
use std::sync::mpsc::Sender;
use super::heartbeat::GlWindowVRMessage;
use winit::dpi::PhysicalSize;

// Fake a display with a distance between eyes of 5cm.
const EYE_DISTANCE: f32 = 0.05;

pub type GlWindowVRDisplayPtr = Arc<RefCell<GlWindowVRDisplay>>;

pub struct GlWindowVRDisplay {
id: u32,
name: String,
size: PhysicalSize,
sender: Sender<GlWindowVRMessage>,
pool: ArcPool<Vec<u8>>,
}

unsafe impl Sync for GlWindowVRDisplay {}

impl Drop for GlWindowVRDisplay {
fn drop(&mut self) {
self.stop_present();
}
}

impl VRDisplay for GlWindowVRDisplay {
fn id(&self) -> u32 {
self.id
}

fn data(&self) -> VRDisplayData {
let capabilities = VRDisplayCapabilities {
has_position: false,
has_orientation: false,
has_external_display: true,
can_present: true,
presented_by_browser: false,
max_layers: 1,
};

let fov_right = GlWindowVRDisplay::fov_right(self.size).to_degrees();
let fov_up = GlWindowVRDisplay::fov_up(self.size).to_degrees();

let field_of_view = VRFieldOfView {
down_degrees: fov_up,
left_degrees: fov_right,
right_degrees: fov_right,
up_degrees: fov_up,
};

let left_eye_parameters = VREyeParameters {
offset: [-EYE_DISTANCE / 2.0, 0.0, 0.0],
render_width: self.size.width as u32 / 2,
render_height: self.size.height as u32,
field_of_view: field_of_view,
};

let right_eye_parameters = VREyeParameters {
offset: [EYE_DISTANCE / 2.0, 0.0, 0.0],
..left_eye_parameters.clone()
};

VRDisplayData {
display_id: self.id,
display_name: self.name.clone(),
connected: true,
capabilities: capabilities,
stage_parameters: None,
left_eye_parameters: left_eye_parameters,
right_eye_parameters: right_eye_parameters,
}
}

fn immediate_frame_data(&self, near: f64, far: f64) -> VRFrameData {
GlWindowVRDisplay::frame_data(0.0, self.size, near, far)
}

fn synced_frame_data(&self, near: f64, far: f64) -> VRFrameData {
self.immediate_frame_data(near, far)
}

fn reset_pose(&mut self) {}

fn sync_poses(&mut self) {}

fn future_frame_data(&mut self, near: f64, far: f64) -> VRFutureFrameData {
let (resolver, result) = VRFutureFrameData::blocked();
let _ = self.sender.send(GlWindowVRMessage::StartFrame(near, far, resolver));
result
}

fn bind_framebuffer(&mut self, _eye_index: u32) {}

fn get_framebuffers(&self) -> Vec<VRFramebuffer> {
let left_viewport = VRViewport {
x: 0,
y: 0,
width: (self.size.width as i32) / 2,
height: self.size.height as i32,
};

let right_viewport = VRViewport {
x: self.size.width as i32 - left_viewport.width,
..left_viewport
};

vec![
VRFramebuffer {
eye_index: 0,
attributes: VRFramebufferAttributes::default(),
viewport: left_viewport,
},
VRFramebuffer {
eye_index: 1,
attributes: VRFramebufferAttributes::default(),
viewport: right_viewport,
},
]
}

fn render_layer(&mut self, _layer: &VRLayer) {
unreachable!()
}

fn submit_frame(&mut self) {
unreachable!()
}

fn submit_layer(&mut self, gl: &Gl, layer: &VRLayer) {
// TODO: this assumes that the current GL framebuffer contains the texture
// TODO: what to do if the layer has no texture_size?
if let Some((width, height)) = layer.texture_size {
let num_bytes = (width as usize) * (height as usize) * 4;
let mut buffer = self.pool.remove().unwrap_or_else(Vec::new);
buffer.resize(num_bytes, 0);
gl.read_pixels_into_buffer(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we share the data in a more performant way?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd need to have GL context sharing, which got added to glutin after this PR was submitted (rust-windowing/glutin#899 (comment)). Context sharing is still not implemented on macOS. This implementation makes all platforms use a slow path, to get other platforms onto the fast path, we'd have to recreate the interface that webrender has for images that are either implemented using shared textures or using readback. I'm not sure how to do that without making rust-webvr depend on webrender_api.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec issue for this is immersive-web/webxr#528. The XR spec pretty much requires textures to go via main memory in the case that the browser doesn't share GL textures with the VR display.

0,
0,
width as gl::GLsizei,
height as gl::GLsizei,
gl::RGBA,
gl::UNSIGNED_BYTE,
&mut buffer[..],
);
let buffer = self.pool.add(buffer);
let _ = self.sender.send(GlWindowVRMessage::StopFrame(width, height, buffer));
}
}

fn start_present(&mut self, _attributes: Option<VRFramebufferAttributes>) {
let _ = self.sender.send(GlWindowVRMessage::StartPresenting);
}

fn stop_present(&mut self) {
let _ = self.sender.send(GlWindowVRMessage::StopPresenting);
}
}

impl GlWindowVRDisplay {
pub(crate) fn new(
name: String,
size: PhysicalSize,
sender: Sender<GlWindowVRMessage>
) -> GlWindowVRDisplay {
GlWindowVRDisplay {
id: utils::new_id(),
name: name,
size: size,
sender: sender,
pool: ArcPool::new(),
}
}

fn fov_up(size: PhysicalSize) -> Angle<f64> {
Angle::radians(f64::fast_atan2(
2.0 * size.height as f64,
size.width as f64,
))
}

fn fov_right(size: PhysicalSize) -> Angle<f64> {
Angle::radians(f64::fast_atan2(
2.0 * size.width as f64,
size.height as f64,
))
}

fn perspective(size: PhysicalSize, near: f64, far: f64) -> [f32; 16] {
// https://github.com/toji/gl-matrix/blob/bd3307196563fbb331b40fc6ebecbbfcc2a4722c/src/mat4.js#L1271
let near = near as f32;
let far = far as f32;
let f = 1.0 / GlWindowVRDisplay::fov_up(size).radians.tan() as f32;
let nf = 1.0 / (near - far);
let aspect = ((size.width / 2.0) as f32) / (size.height as f32);

// Dear rustfmt, This is a 4x4 matrix, please leave it alone. Best, ajeffrey.
{#[rustfmt::skip]
return [
f / aspect, 0.0, 0.0, 0.0,
0.0, f, 0.0, 0.0,
0.0, 0.0, (far + near) * nf, -1.0,
0.0, 0.0, 2.0 * far * near * nf, 0.0,
];
}
}

pub(crate) fn frame_data(timestamp: f64, size: PhysicalSize, near: f64, far: f64) -> VRFrameData {
let left_projection_matrix = GlWindowVRDisplay::perspective(size, near, far);
let right_projection_matrix = left_projection_matrix.clone();

VRFrameData {
timestamp: timestamp,
// TODO: adjust matrices for stereoscopic vision
left_projection_matrix: left_projection_matrix,
right_projection_matrix: right_projection_matrix,
..VRFrameData::default()
}
}
}

// A pool of Arc<T>'s.
// You can add a T into the pool, and get back an Arc<T>.
// You can request a T from the pool, if there's an Arc<T> with no other owners,
// it will be removed from the pool, unwrapped and returned.

struct ArcPool<T>(Vec<Arc<T>>);

impl<T> ArcPool<T> {
fn new() -> ArcPool<T> {
ArcPool(Vec::new())
}

fn add(&mut self, val: T) -> Arc<T> {
let result = Arc::new(val);
self.0.push(result.clone());
result
}

fn remove(&mut self) -> Option<T> {
let i = self.0.iter().position(|arc| Arc::strong_count(arc) == 1);
i.and_then(|i| Arc::try_unwrap(self.0.swap_remove(i)).ok())
}
}
Loading