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

Commit

Permalink
Auto merge of #66 - asajeffrey:glwindow, r=paulrouget
Browse files Browse the repository at this point in the history
 Add glwindow implementation, that renders to a GlWindow

Add glwindow implementation, that renders to a GlWindow, mainly for testing, but also this forms a basis for VRDisplays that have code that needs to run in the main thread.

Fixes #52 and #65.
  • Loading branch information
bors-servo authored Mar 5, 2019
2 parents 61c6151 + b61b1e5 commit 19fb68b
Show file tree
Hide file tree
Showing 10 changed files with 547 additions and 3 deletions.
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(
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

0 comments on commit 19fb68b

Please sign in to comment.