├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── hmd_metadata.rs └── scene.rs └── src ├── ffi ├── dynamic_lib.rs └── mod.rs ├── lib.rs ├── render.rs ├── shim.rs └── target.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "rovr" 4 | version = "0.0.2" 5 | description = "A VR headset library for Rust programs targeting the Oculus Rift." 6 | authors = ["Ryan Stewart "] 7 | repository = "https://github.com/binsoftware/rovr" 8 | documentation = "http://binsoftware.github.io/rovr/doc/rovr" 9 | license = "MIT" 10 | keywords = ["vr", "ovr", "oculus", "rift", "hmd"] 11 | 12 | [features] 13 | default = ["glutin"] 14 | 15 | [dependencies] 16 | bitflags = "0.1.1" 17 | libc = "0.1.5" 18 | 19 | [dependencies.glutin] 20 | version = "^0.1" 21 | optional = true 22 | 23 | [target.i686-pc-windows-gnu.dependencies] 24 | winapi = "0.1.17" 25 | kernel32-sys = "0.1.0" 26 | 27 | [target.x86_64-pc-windows-gnu.dependencies] 28 | winapi = "0.1.17" 29 | kernel32-sys = "0.1.0" 30 | 31 | [dev-dependencies.glium] 32 | version = "^0.3" 33 | default-features = false 34 | features = ["gl_read_buffer", "gl_depth_textures"] 35 | 36 | [dev-dependencies.cgmath] 37 | version = "^0.2" 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bin Software Co. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rovr: VR headsets in Rust 2 | 3 | `rovr` provides support for orientation and positional tracking plus rendering to VR headsets, currently using the Oculus SDK. It exposes a safe, Rust-native API for working with the Oculus Rift DK2 and other supported headsets. 4 | 5 | `rovr` currently supports version **0.5.0.1** of the Oculus runtime/SDK, on Windows, OS X, and Linux. 6 | 7 | `rovr`'s API is functional, but a work in progress and should be considered unstable as the VR SDK landscape evolves. Feedback and PRs welcome. 8 | 9 | # Documentation 10 | 11 | Documentation is available [here](http://binsoftware.github.io/rovr/doc/rovr/). 12 | 13 | # Build notes 14 | 15 | `rovr` dynamically binds to the Oculus runtime, so users of `rovr` programs will need the Oculus runtime installed. 16 | 17 | -------------------------------------------------------------------------------- /examples/hmd_metadata.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate rovr; 3 | 4 | fn main() { 5 | let (w, h) = rovr::Context::new().unwrap() 6 | .build_hmd() 7 | .allow_debug() 8 | .build() 9 | .unwrap() 10 | .resolution(); 11 | println!("{}x{}", w, h); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /examples/scene.rs: -------------------------------------------------------------------------------- 1 | extern crate glutin; 2 | #[macro_use] extern crate glium; 3 | extern crate cgmath; 4 | extern crate libc; 5 | extern crate rovr; 6 | 7 | use std::string; 8 | use cgmath::{Matrix, Point, FixedArray, Vector}; 9 | 10 | fn main() { 11 | use glium::DisplayBuild; 12 | 13 | let context = rovr::Context::new().unwrap(); 14 | let hmd = context.build_hmd() 15 | .allow_debug() 16 | .track(&rovr::TrackingOptions::with_all()) 17 | .build() 18 | .ok().expect("Unable to build HMD"); 19 | 20 | let monitor = rovr::target::find_glutin_monitor(&hmd.get_display()); 21 | let builder = match monitor { 22 | Some(id) => glutin::WindowBuilder::new().with_fullscreen(id), 23 | None => { 24 | let (w, h) = hmd.resolution(); 25 | glutin::WindowBuilder::new().with_dimensions(w, h) 26 | } 27 | }; 28 | let display = builder 29 | .with_title(string::String::from("Cube")) 30 | .with_vsync() 31 | .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (4, 1))) 32 | .build_glium() 33 | .ok().expect("Unable to build Window"); 34 | 35 | // NOTE: keeping this window around will cause rebuild to panic; not sure there's a way around 36 | // this with the current glium mutability/rebuild design 37 | let window = display.get_window().unwrap(); 38 | let target = rovr::target::GlutinRenderTarget::new(&window, 1); 39 | let render = hmd.render_to(&target).unwrap(); 40 | 41 | let program = basic_shader::compile(&display); 42 | let (vertex_buffer, index_buffer) = basic_shader::cube(&display); 43 | 44 | let attachments = glium_oculus::Attachments::new(&display, &render); 45 | let mut surfaces = glium_oculus::Surfaces::new(&display, &attachments); 46 | 47 | display_loop(&display, &attachments, &mut surfaces, |m, surface| { 48 | use glium::Surface; 49 | use cgmath::FixedArray; 50 | 51 | let uniforms = uniform! { 52 | uViewProjectionMatrix: *m.as_fixed() 53 | }; 54 | 55 | let params = glium::DrawParameters { 56 | backface_culling: glium::BackfaceCullingMode::CullClockWise, 57 | depth_test: glium::DepthTest::IfLess, 58 | depth_write: true, 59 | .. std::default::Default::default() 60 | }; 61 | 62 | surface.clear_color(0f32, 0f32, 0f32, 1f32); 63 | surface.clear_depth(1f32); 64 | surface.draw(&vertex_buffer, &index_buffer, &program, &uniforms, ¶ms).unwrap(); 65 | }); 66 | } 67 | 68 | fn display_loop<'a, F: Fn(&cgmath::Matrix4, &mut glium::framebuffer::SimpleFrameBuffer)>( 69 | display: &glium::Display, 70 | attachments: &'a glium_oculus::Attachments, 71 | surfaces: &'a mut glium_oculus::Surfaces<'a>, 72 | draw: F) { 73 | use cgmath::Matrix; 74 | 75 | let mut frame_index = 0u32; 76 | let (proj_left, proj_right) = { 77 | let projections: Vec<_> = [rovr::Eye::Left, rovr::Eye::Right].iter().map(|eye| { 78 | attachments.get_render_context().projection_matrix(&eye, 0.2f32, 10f32) 79 | }).collect(); 80 | (projections[0], projections[1]) 81 | }; 82 | loop { 83 | { 84 | let frame = attachments.start_frame(); 85 | for pose in frame.eye_poses() { 86 | let projection = match pose.eye { 87 | rovr::Eye::Left => proj_left, 88 | rovr::Eye::Right => proj_right 89 | }; 90 | let fixed = cgmath::Vector3::new(0f32, 1f32, 2f32); 91 | let direction = cgmath::Vector3::new(0f32, 0f32, -1f32); 92 | let up = cgmath::Vector3::new(0f32, 1f32, 0f32); 93 | 94 | let camera_position = fixed.add_v(cgmath::Vector3::from_fixed_ref(&pose.position)); 95 | let center = cgmath::Point3::from_vec(&camera_position.add_v(&direction)); 96 | 97 | let orientation_mat = { 98 | let mat: cgmath::Matrix4<_> = { 99 | let (orientation_s, ref orientation_v) = pose.orientation; 100 | cgmath::Quaternion::from_sv(orientation_s, 101 | *cgmath::Vector3::from_fixed_ref(orientation_v)) 102 | }.into(); 103 | mat.invert().unwrap() 104 | }; 105 | 106 | let eye_transform = *cgmath::Matrix4::from_fixed_ref(&projection) * 107 | orientation_mat * 108 | cgmath::Matrix4::look_at(&cgmath::Point::from_vec(&camera_position), 109 | ¢er, 110 | &up); 111 | 112 | draw(&eye_transform, surfaces.surface_for_eye(&pose.eye)); 113 | } 114 | } 115 | 116 | for event in display.poll_events() { 117 | match event { 118 | glutin::Event::Closed => return, 119 | glutin::Event::KeyboardInput(_, _, key) => { 120 | attachments.get_render_context().dismiss_hsw(); 121 | match key { 122 | Some(glutin::VirtualKeyCode::Escape) => return, 123 | Some(glutin::VirtualKeyCode::R) => 124 | attachments.get_render_context().recenter_pose(), 125 | _ => {} 126 | } 127 | }, 128 | _ => {} 129 | } 130 | } 131 | frame_index = frame_index + 1; 132 | } 133 | } 134 | 135 | mod glium_oculus { 136 | use rovr; 137 | use glium; 138 | use glium::texture::{Texture2d, DepthTexture2d}; 139 | use glium::framebuffer::SimpleFrameBuffer; 140 | 141 | pub struct Attachments<'a> { 142 | render_context: &'a rovr::render::RenderContext<'a>, 143 | left: PerEyeAttachments, 144 | right: PerEyeAttachments, 145 | binding: rovr::render::TextureBinding, 146 | } 147 | 148 | struct PerEyeAttachments { 149 | color: Texture2d, 150 | depth: DepthTexture2d, 151 | } 152 | 153 | impl<'a> Attachments<'a> { 154 | pub fn new(display: &glium::Display, 155 | render_context: &'a rovr::render::RenderContext) -> Attachments<'a> { 156 | use glium::GlObject; 157 | 158 | let left = Attachments::create_attachment(display, render_context, rovr::Eye::Left); 159 | let right = Attachments::create_attachment(display, render_context, rovr::Eye::Right); 160 | let binding = render_context.create_binding(left.color.get_id(), right.color.get_id()); 161 | 162 | Attachments { 163 | render_context: render_context, 164 | left: left, 165 | right: right, 166 | binding: binding, 167 | } 168 | } 169 | 170 | pub fn start_frame(&self) -> rovr::render::Frame { 171 | rovr::render::Frame::new(self.render_context, &self.binding) 172 | } 173 | 174 | pub fn get_render_context(&'a self) -> &'a rovr::render::RenderContext { 175 | self.render_context 176 | } 177 | 178 | fn create_attachment(display: &glium::Display, 179 | render_context: &rovr::render::RenderContext, 180 | eye: rovr::Eye) -> PerEyeAttachments { 181 | let (w, h) = render_context.target_texture_size(&eye); 182 | let color: Texture2d = Texture2d::empty(display, w, h); 183 | let depth = DepthTexture2d::empty(display, w, h); 184 | 185 | PerEyeAttachments { 186 | color: color, 187 | depth: depth 188 | } 189 | } 190 | } 191 | 192 | pub struct Surfaces<'a> { 193 | left: SimpleFrameBuffer<'a>, 194 | right: SimpleFrameBuffer<'a> 195 | } 196 | 197 | impl<'a> Surfaces<'a> { 198 | pub fn new(display: &glium::Display, attachments: &'a Attachments) -> Surfaces<'a> { 199 | let left = SimpleFrameBuffer::with_depth_buffer(display, 200 | &attachments.left.color, 201 | &attachments.left.depth); 202 | let right = SimpleFrameBuffer::with_depth_buffer(display, 203 | &attachments.right.color, 204 | &attachments.right.depth); 205 | 206 | Surfaces { 207 | left: left, 208 | right: right 209 | } 210 | } 211 | 212 | pub fn surface_for_eye<'b>(&'b mut self, eye: &rovr::Eye) 213 | -> &'b mut SimpleFrameBuffer<'a> where 'a: 'b { 214 | match eye { 215 | &rovr::Eye::Left => &mut self.left, 216 | &rovr::Eye::Right => &mut self.right 217 | } 218 | } 219 | } 220 | } 221 | 222 | mod basic_shader { 223 | extern crate glium; 224 | 225 | #[derive(Copy, Clone)] 226 | #[allow(non_snake_case)] 227 | struct Vertex { 228 | aPosition: [f32; 3], 229 | aColor: [f32; 3] 230 | } 231 | 232 | implement_vertex!(Vertex, aPosition, aColor); 233 | 234 | impl Vertex { 235 | fn new(position: [f32; 3], color: [f32; 3]) -> Vertex { 236 | Vertex { aPosition: position, aColor: color } 237 | } 238 | } 239 | 240 | pub fn compile(display: &glium::Display) -> glium::Program { 241 | static VERTEX: &'static str = " 242 | #version 410 243 | 244 | uniform mat4 uViewProjectionMatrix; 245 | 246 | in vec3 aPosition; 247 | in vec3 aColor; 248 | 249 | out vec3 vColor; 250 | 251 | void main() { 252 | gl_Position = uViewProjectionMatrix * vec4(aPosition, 1); 253 | vColor = aColor; 254 | } 255 | "; 256 | 257 | static FRAGMENT: &'static str = " 258 | #version 410 259 | 260 | in vec3 vColor; 261 | out vec4 outColor; 262 | 263 | void main() { 264 | outColor = vec4(vColor, 1); 265 | } 266 | "; 267 | 268 | glium::Program::from_source(display, VERTEX, FRAGMENT, None).unwrap() 269 | } 270 | 271 | pub fn cube(display: &glium::Display) -> (glium::vertex::VertexBufferAny, glium::IndexBuffer) { 272 | let vertex_buffer = { 273 | let blue = [0f32, 0f32, 1f32]; 274 | let green = [0f32, 1f32, 0f32]; 275 | let red = [1f32, 0f32, 0f32]; 276 | glium::VertexBuffer::new(display, 277 | vec![ 278 | Vertex::new([-0.5f32, -0.5f32, 0.5f32], blue), 279 | Vertex::new([-0.5f32, 0.5f32, 0.5f32], red), 280 | Vertex::new([ 0.5f32, -0.5f32, 0.5f32], green), 281 | Vertex::new([ 0.5f32, 0.5f32, 0.5f32], blue), 282 | Vertex::new([-0.5f32, 0.5f32, -0.5f32], blue), 283 | Vertex::new([ 0.5f32, 0.5f32, -0.5f32], red), 284 | Vertex::new([-0.5f32, -0.5f32, -0.5f32], green), 285 | Vertex::new([ 0.5f32, -0.5f32, -0.5f32], blue) 286 | ]).into_vertex_buffer_any() 287 | }; 288 | 289 | let index_buffer = { 290 | let triangles = vec![ 291 | // front 292 | 1u32, 0u32, 2u32, 293 | 2u32, 3u32, 1u32, 294 | 295 | // top 296 | 1u32, 3u32, 4u32, 297 | 4u32, 3u32, 5u32, 298 | 299 | // right 300 | 2u32, 7u32, 3u32, 301 | 3u32, 7u32, 5u32, 302 | 303 | // left 304 | 6u32, 0u32, 4u32, 305 | 0u32, 1u32, 4u32, 306 | 307 | // bottom 308 | 7u32, 0u32, 6u32, 309 | 2u32, 0u32, 7u32, 310 | 311 | // back 312 | 7u32, 6u32, 5u32, 313 | 5u32, 6u32, 4u32 314 | ]; 315 | glium::IndexBuffer::new(display, glium::index::TrianglesList(triangles)) 316 | }; 317 | 318 | (vertex_buffer, index_buffer) 319 | } 320 | } 321 | 322 | -------------------------------------------------------------------------------- /src/ffi/dynamic_lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Dynamic library facilities. Pulled from libstd since it's currently marked as unstable in that 12 | //! context. Tweaked to remove use of unstable features. 13 | //! 14 | //! A simple wrapper over the platform's dynamic library facilities 15 | 16 | #![allow(missing_docs)] 17 | 18 | use std::env; 19 | use std::ffi::{CString, OsString}; 20 | use std::mem; 21 | use std::path::{Path, PathBuf}; 22 | 23 | /// An unsafe version modified version of UnsafeDynamicLibrary. Unsafe because thread safety is not 24 | /// guaranteed on all platforms. If not limited to a single thread per-process, undefined beahvior 25 | /// may result. Should only be used until a stable version of UnsafeDynamicLibrary lands. 26 | pub struct UnsafeDynamicLibrary { 27 | handle: *mut u8 28 | } 29 | 30 | impl Drop for UnsafeDynamicLibrary { 31 | fn drop(&mut self) { 32 | match dl::check_for_errors_in(|| { 33 | unsafe { 34 | dl::close(self.handle) 35 | } 36 | }) { 37 | Ok(()) => {}, 38 | Err(str) => panic!("{}", str) 39 | } 40 | } 41 | } 42 | 43 | impl UnsafeDynamicLibrary { 44 | // FIXME (#12938): Until DST lands, we cannot decompose &str into 45 | // & and str, so we cannot usefully take ToCStr arguments by 46 | // reference (without forcing an additional & around &str). So we 47 | // are instead temporarily adding an instance for &Path, so that 48 | // we can take ToCStr as owned. When DST lands, the &Path instance 49 | // should be removed, and arguments bound by ToCStr should be 50 | // passed by reference. (Here: in the `open` method.) 51 | 52 | /// Lazily open a dynamic library. When passed None it gives a 53 | /// handle to the calling process 54 | pub unsafe fn open(filename: Option<&Path>) -> Result { 55 | let maybe_library = dl::open(filename.map(|path| path.as_os_str())); 56 | 57 | // The dynamic library must not be constructed if there is 58 | // an error opening the library so the destructor does not 59 | // run. 60 | match maybe_library { 61 | Err(err) => Err(err), 62 | Ok(handle) => Ok(UnsafeDynamicLibrary { handle: handle }) 63 | } 64 | } 65 | 66 | /// Prepends a path to this process's search path for dynamic libraries 67 | pub unsafe fn prepend_search_path(path: &Path) { 68 | let mut search_path = UnsafeDynamicLibrary::search_path(); 69 | search_path.insert(0, path.to_path_buf()); 70 | env::set_var(UnsafeDynamicLibrary::envvar(), &UnsafeDynamicLibrary::create_path(&search_path)); 71 | } 72 | 73 | /// From a slice of paths, create a new vector which is suitable to be an 74 | /// environment variable for this platforms dylib search path. 75 | pub unsafe fn create_path(path: &[PathBuf]) -> OsString { 76 | let mut newvar = OsString::new(); 77 | for (i, path) in path.iter().enumerate() { 78 | if i > 0 { newvar.push(UnsafeDynamicLibrary::separator()); } 79 | newvar.push(path); 80 | } 81 | return newvar; 82 | } 83 | 84 | /// Returns the environment variable for this process's dynamic library 85 | /// search path 86 | pub unsafe fn envvar() -> &'static str { 87 | if cfg!(windows) { 88 | "PATH" 89 | } else if cfg!(target_os = "macos") { 90 | "DYLD_LIBRARY_PATH" 91 | } else { 92 | "LD_LIBRARY_PATH" 93 | } 94 | } 95 | 96 | fn separator() -> &'static str { 97 | if cfg!(windows) { ";" } else { ":" } 98 | } 99 | 100 | /// Returns the current search path for dynamic libraries being used by this 101 | /// process 102 | pub unsafe fn search_path() -> Vec { 103 | match env::var_os(UnsafeDynamicLibrary::envvar()) { 104 | Some(var) => env::split_paths(&var).collect(), 105 | None => Vec::new(), 106 | } 107 | } 108 | 109 | /// Access the value at the symbol of the dynamic library 110 | pub unsafe fn symbol(&self, symbol: &str) -> Result<*mut T, String> { 111 | // This function should have a lifetime constraint of 'a on 112 | // T but that feature is still unimplemented 113 | 114 | let raw_string = CString::new(symbol).unwrap(); 115 | let maybe_symbol_value = dl::check_for_errors_in(|| { 116 | dl::symbol(self.handle, raw_string.as_ptr()) 117 | }); 118 | 119 | // The value must not be constructed if there is an error so 120 | // the destructor does not run. 121 | match maybe_symbol_value { 122 | Err(err) => Err(err), 123 | Ok(symbol_value) => Ok(mem::transmute(symbol_value)) 124 | } 125 | } 126 | } 127 | 128 | #[cfg(any(target_os = "linux", 129 | target_os = "android", 130 | target_os = "macos", 131 | target_os = "ios", 132 | target_os = "freebsd", 133 | target_os = "dragonfly", 134 | target_os = "bitrig", 135 | target_os = "openbsd"))] 136 | mod dl { 137 | use std::ffi::{CStr, CString, OsStr}; 138 | use std::str; 139 | use libc; 140 | use std::ptr; 141 | 142 | pub fn open(filename: Option<&OsStr>) -> Result<*mut u8, String> { 143 | check_for_errors_in(|| { 144 | unsafe { 145 | match filename { 146 | Some(filename) => open_external(filename), 147 | None => open_internal(), 148 | } 149 | } 150 | }) 151 | } 152 | 153 | const LAZY: libc::c_int = 1; 154 | 155 | unsafe fn open_external(filename: &OsStr) -> *mut u8 { 156 | let s = CString::new(filename.to_str().unwrap()).unwrap(); 157 | dlopen(s.as_ptr(), LAZY) as *mut u8 158 | } 159 | 160 | unsafe fn open_internal() -> *mut u8 { 161 | dlopen(ptr::null(), LAZY) as *mut u8 162 | } 163 | 164 | // NOTE: Thread-safety code was removed here because StaticMutex is also unstable. The safety 165 | // is somewhat artificial in any case, since there's no guarantee external code isn't 166 | // potentially manipulating the dlerror() state at the same time. It does make dynamic_lib 167 | // itself unsafe. 168 | pub fn check_for_errors_in(f: F) -> Result where 169 | F: FnOnce() -> T, 170 | { 171 | unsafe { 172 | let _old_error = dlerror(); 173 | 174 | let result = f(); 175 | 176 | let last_error = dlerror() as *const _; 177 | let ret = if ptr::null() == last_error { 178 | Ok(result) 179 | } else { 180 | let s = CStr::from_ptr(last_error).to_bytes(); 181 | Err(str::from_utf8(s).unwrap().to_string()) 182 | }; 183 | 184 | ret 185 | } 186 | } 187 | 188 | pub unsafe fn symbol(handle: *mut u8, 189 | symbol: *const libc::c_char) -> *mut u8 { 190 | dlsym(handle as *mut libc::c_void, symbol) as *mut u8 191 | } 192 | pub unsafe fn close(handle: *mut u8) { 193 | dlclose(handle as *mut libc::c_void); () 194 | } 195 | 196 | extern { 197 | fn dlopen(filename: *const libc::c_char, 198 | flag: libc::c_int) -> *mut libc::c_void; 199 | fn dlerror() -> *mut libc::c_char; 200 | fn dlsym(handle: *mut libc::c_void, 201 | symbol: *const libc::c_char) -> *mut libc::c_void; 202 | fn dlclose(handle: *mut libc::c_void) -> libc::c_int; 203 | } 204 | } 205 | 206 | #[cfg(target_os = "windows")] 207 | mod dl { 208 | use std::ffi::OsStr; 209 | use std::option::Option::{self, Some, None}; 210 | use std::os::windows::ffi::OsStrExt; 211 | use std::ptr; 212 | use std::result::Result; 213 | use std::result::Result::{Ok, Err}; 214 | use std::string::String; 215 | use std::vec::Vec; 216 | use winapi::*; 217 | use kernel32::*; 218 | 219 | pub fn open(filename: Option<&OsStr>) -> Result<*mut u8, String> { 220 | unsafe { 221 | SetLastError(0); 222 | } 223 | 224 | let result = match filename { 225 | Some(filename) => { 226 | let filename_str: Vec<_> = 227 | to_wide(filename); 228 | let result = unsafe { 229 | LoadLibraryW(filename_str.as_ptr()) 230 | }; 231 | // beware: Vec/String may change errno during drop! 232 | // so we get error here. 233 | if result == ptr::null_mut() { 234 | Err(String::from("LoadLibraryW failed")) 235 | } else { 236 | Ok(result as *mut u8) 237 | } 238 | } 239 | None => { 240 | let mut handle = ptr::null_mut(); 241 | let succeeded = unsafe { 242 | GetModuleHandleExW(0, ptr::null(), &mut handle) 243 | }; 244 | if succeeded == FALSE { 245 | Err(String::from("GetModuleHandleExW failed")) 246 | } else { 247 | Ok(handle as *mut u8) 248 | } 249 | } 250 | }; 251 | result 252 | } 253 | 254 | pub fn check_for_errors_in(f: F) -> Result where 255 | F: FnOnce() -> T, 256 | { 257 | unsafe { 258 | SetLastError(0); 259 | 260 | let result = f(); 261 | 262 | let error = errno(); 263 | if 0 == error { 264 | Ok(result) 265 | } else { 266 | Err(format!("Error code {}", error)) 267 | } 268 | } 269 | } 270 | 271 | pub unsafe fn symbol(handle: *mut u8, symbol: LPCSTR) -> *mut u8 { 272 | GetProcAddress(handle as HMODULE, symbol) as *mut u8 273 | } 274 | pub unsafe fn close(handle: *mut u8) { 275 | FreeLibrary(handle as HMODULE); () 276 | } 277 | 278 | pub unsafe fn errno() -> u32 { 279 | GetLastError() 280 | } 281 | 282 | pub fn to_wide(s: &OsStr) -> Vec { 283 | s.encode_wide().collect() 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, non_upper_case_globals, non_camel_case_types, non_snake_case)] 2 | 3 | type ovrBool = u8; 4 | pub const ovrTrue: u8 = 1; 5 | pub const ovrFalse: u8 = 0; 6 | 7 | mod dynamic_lib; 8 | 9 | use libc; 10 | use std::default::Default; 11 | use std::mem; 12 | use std::ptr; 13 | 14 | pub use ffi::dynamic_lib::UnsafeDynamicLibrary; 15 | 16 | #[repr(C)] 17 | #[derive(Default, Clone, Copy)] 18 | pub struct ovrFovPort { 19 | pub UpTan: f32, 20 | pub DownTan: f32, 21 | pub LeftTan: f32, 22 | pub RightTan: f32 23 | } 24 | 25 | #[repr(C)] 26 | #[derive(Default, Clone, Copy)] 27 | pub struct ovrSizei { 28 | pub w: i32, 29 | pub h: i32 30 | } 31 | 32 | #[repr(C)] 33 | #[derive(Default, Clone, Copy)] 34 | pub struct ovrVector2i { 35 | pub x: i32, 36 | pub y: i32 37 | } 38 | 39 | #[repr(C)] 40 | #[derive(Default, Clone, Copy)] 41 | pub struct ovrRecti { 42 | pub Pos: ovrVector2i, 43 | pub Size: ovrSizei 44 | } 45 | 46 | #[repr(C)] 47 | #[derive(Default, Clone, Copy)] 48 | pub struct ovrVector2f { 49 | pub x: f32, 50 | pub y: f32 51 | } 52 | 53 | #[repr(C)] 54 | #[derive(Default, Clone, Copy)] 55 | pub struct ovrVector3f { 56 | pub x: f32, 57 | pub y: f32, 58 | pub z: f32 59 | } 60 | 61 | #[repr(C)] 62 | #[derive(Clone, Copy)] 63 | pub struct ovrMatrix4f { 64 | pub M: [[f32; 4]; 4] 65 | } 66 | 67 | #[repr(C)] 68 | #[derive(Default, Clone, Copy)] 69 | pub struct ovrQuatf { 70 | pub x: f32, 71 | pub y: f32, 72 | pub z: f32, 73 | pub w: f32 74 | } 75 | 76 | #[repr(C)] 77 | #[derive(Default, Clone, Copy)] 78 | pub struct ovrPosef { 79 | pub Orientation: ovrQuatf, 80 | pub Position: ovrVector3f 81 | } 82 | 83 | impl Default for ovrMatrix4f { 84 | fn default() -> ovrMatrix4f { 85 | ovrMatrix4f { 86 | M: [[1f32, 0f32, 0f32, 0f32], 87 | [0f32, 1f32, 0f32, 0f32], 88 | [0f32, 0f32, 1f32, 0f32], 89 | [0f32, 0f32, 0f32, 1f32]] 90 | } 91 | } 92 | } 93 | 94 | bitflags!( 95 | #[repr(C)] 96 | #[derive(Default)] 97 | flags ovrInitFlags: u32 { 98 | const ovrInit_Debug = 0x00000001, 99 | const ovrInit_ServerOptional = 0x00000002, 100 | const ovrInit_RequestVersion = 0x00000004, 101 | const ovrInit_ForceNoDebug = 0x00000008 102 | } 103 | ); 104 | 105 | #[repr(C)] 106 | pub struct ovrInitParams { 107 | Flags: u32, 108 | RequestedMinorVersion: u32, 109 | LogCallback: *const libc::c_void, 110 | ConnectionTimeoutMS: u32 111 | } 112 | 113 | impl Default for ovrInitParams { 114 | fn default() -> ovrInitParams { 115 | ovrInitParams { 116 | Flags: Default::default(), 117 | RequestedMinorVersion: Default::default(), 118 | LogCallback: ptr::null(), 119 | ConnectionTimeoutMS: Default::default() 120 | } 121 | } 122 | } 123 | 124 | pub type ovrHmdType = u32; 125 | pub const ovrHmd_None: ovrHmdType = 0; 126 | pub const ovrHmd_DK1: ovrHmdType = 3; 127 | pub const ovrHmd_DKHD: ovrHmdType = 4; 128 | pub const ovrHmd_DK2: ovrHmdType = 6; 129 | pub const ovrHmd_BlackStar: ovrHmdType = 7; 130 | pub const ovrHmd_CB: ovrHmdType = 8; 131 | pub const ovrHmd_Other: ovrHmdType = 9; 132 | 133 | bitflags!( 134 | #[repr(C)] 135 | #[derive(Default)] 136 | flags ovrHmdCaps: u32 { 137 | const ovrHmdCap_Present = 0x0001, 138 | const ovrHmdCap_Available = 0x0002, 139 | const ovrHmdCap_Captured = 0x0004, 140 | const ovrHmdCap_ExtendDesktop = 0x0008, 141 | const ovrHmdCap_NoMirrorToWindow = 0x2000, 142 | const ovrHmdCap_DisplayOff = 0x0040, 143 | const ovrHmdCap_LowPersistence = 0x0080, 144 | const ovrHmdCap_DynamicPrediction = 0x0200, 145 | const ovrHmdCap_NoVSync = 0x1000, 146 | const ovrHmdCap_Writable_Mask = 0x32C0, 147 | const ovrHmdCap_Service_Mask = 0x22C0 148 | } 149 | ); 150 | 151 | bitflags!( 152 | #[repr(C)] 153 | #[derive(Default)] 154 | flags ovrTrackingCaps: u32 { 155 | const ovrTrackingCap_Orientation = 0x0010, 156 | const ovrTrackingCap_MagYawCorrection = 0x0020, 157 | const ovrTrackingCap_Position = 0x0040, 158 | const ovrTrackingCap_Idle = 0x0100 159 | } 160 | ); 161 | 162 | bitflags!( 163 | #[repr(C)] 164 | #[derive(Default)] 165 | flags ovrDistortionCaps: u32 { 166 | const ovrDistortionCap_TimeWarp = 0x02, 167 | const ovrDistortionCap_Vignette = 0x08, 168 | const ovrDistortionCap_NoRestore = 0x10, 169 | const ovrDistortionCap_FlipInput = 0x20, 170 | const ovrDistortionCap_SRGB = 0x40, 171 | const ovrDistortionCap_Overdrive = 0x80, 172 | const ovrDistortionCap_HqDistortion = 0x100, 173 | const ovrDistortionCap_LinuxDevFullscreen = 0x200, 174 | const ovrDistortionCap_ComputeShader = 0x400, 175 | const ovrDistortionCap_TimewarpJitDelay = 0x1000, 176 | const ovrDistortionCap_ProfileNoSpinWaits = 0x10000 177 | } 178 | ); 179 | 180 | #[repr(C)] 181 | pub struct ovrHmdStruct; 182 | 183 | #[repr(C)] 184 | pub struct ovrHmdDesc { 185 | pub Handle: *mut ovrHmdStruct, 186 | pub Type: ovrHmdType, 187 | pub ProductName: *const u8, 188 | pub Manufacturer: *const u8, 189 | pub VendorId: i16, 190 | pub ProductId: i16, 191 | pub SerialNumber: [u8; 24], 192 | pub FirmwareMajor: i16, 193 | pub FirmwareMinor: i16, 194 | pub CameraFrustumHFovInRadians: f32, 195 | pub CameraFrustumVFovInRadians: f32, 196 | pub CameraFrustumNearZInMeters: f32, 197 | pub CameraFrustumFarZInMeters: f32, 198 | pub HmdCaps: ovrHmdCaps, 199 | pub TrackingCaps: ovrTrackingCaps, 200 | pub DistortionCaps: ovrDistortionCaps, 201 | pub DefaultEyeFov: [ovrFovPort; 2], 202 | pub MaxEyeFov: [ovrFovPort; 2], 203 | pub EyeRenderOrder: [u32; 2], 204 | pub Resolution: ovrSizei, 205 | pub WindowsPos: ovrVector2i, 206 | pub DisplayDeviceName: *const i8, 207 | pub DisplayId: i32 208 | } 209 | 210 | pub type ovrRenderAPIType = u32; 211 | pub const ovrRenderAPI_None: ovrRenderAPIType = 0; 212 | pub const ovrRenderAPI_OpenGL: ovrRenderAPIType = 1; 213 | pub const ovrRenderAPI_Android_GLES: ovrRenderAPIType = 2; 214 | pub const ovrRenderAPI_D3D9: ovrRenderAPIType = 3; 215 | pub const ovrRenderAPI_D3D10: ovrRenderAPIType = 4; 216 | pub const ovrRenderAPI_D3D11: ovrRenderAPIType = 5; 217 | pub const ovrRenderAPI_Count: ovrRenderAPIType = 6; 218 | 219 | #[repr(C)] 220 | #[cfg(target_os = "linux")] 221 | pub struct _XDisplay; 222 | 223 | #[repr(C)] 224 | #[derive(Clone, Copy)] 225 | #[allow(raw_pointer_derive)] 226 | pub struct ovrGLConfig { 227 | pub API: ovrRenderAPIType, 228 | pub BackBufferSize: ovrSizei, 229 | pub Multisample: i32, 230 | 231 | #[cfg(windows)] 232 | pub Window: *const libc::c_void, 233 | #[cfg(windows)] 234 | pub HDC: *const libc::c_void, 235 | #[cfg(windows)] 236 | pub _PAD_: [usize; 6], 237 | 238 | #[cfg(target_os = "linux")] 239 | pub Disp: *const _XDisplay, 240 | #[cfg(target_os = "linux")] 241 | pub _PAD_: [usize; 7], 242 | 243 | #[cfg(all(not(windows), not(target_os = "linux")))] 244 | pub _PAD_: [usize; 8], 245 | } 246 | 247 | impl Default for ovrGLConfig { 248 | fn default() -> ovrGLConfig { 249 | unsafe { 250 | mem::zeroed() 251 | } 252 | } 253 | } 254 | 255 | // We're representing the GL-specific half of the union ovrGLTexture (specifically, 256 | // ovrGLTextureData), whose size is defined by the OVR type ovrTexture. ovrTexture contains API + 257 | // TextureSize + RenderViewport in its header, plus a ptr-sized 8-element array to pad out the rest 258 | // of the struct for rendering system-specific values. The OpenGL struct contains just one u32, so 259 | // for 32-bit builds we need to pad out the remaining 7 * 4 bytes. The 64-bit version of the native 260 | // struct ends up inheriting additional padding due to alignment. offsetof(TexId) is 28, so the 261 | // "on-books" 92 byte struct gets padded by VC to 96 bytes. If we just add 60 bytes--that is, the 262 | // 8 * 8 - 4 bytes remaining in the platform-specific data region ovr ovrTexture--Rust doesn't pad 263 | // the way VC does. So we manually add the additional 4 bytes by promoting _PAD1_ to a u64. 264 | #[repr(C)] 265 | #[derive(Clone, Copy)] 266 | pub struct ovrGLTexture { 267 | pub API: ovrRenderAPIType, 268 | pub TextureSize: ovrSizei, 269 | pub RenderViewport: ovrRecti, 270 | 271 | pub TexId: u32, 272 | 273 | // See above notes about alignment. 274 | #[cfg(target_pointer_width = "64")] 275 | pub _PAD1_: u64, 276 | 277 | pub _PAD2_: [usize; 7], 278 | } 279 | 280 | impl Default for ovrGLTexture { 281 | fn default() -> ovrGLTexture { 282 | unsafe { 283 | mem::zeroed() 284 | } 285 | } 286 | } 287 | 288 | #[repr(C)] 289 | #[derive(Default, Clone, Copy)] 290 | pub struct ovrEyeRenderDesc { 291 | pub Eye: u32, 292 | pub Fov: ovrFovPort, 293 | pub DistortedViewpoint: ovrRecti, 294 | pub PixelsPerTanAngleAtCenter: ovrVector2f, 295 | pub HmdToEyeViewOffset: ovrVector3f 296 | } 297 | 298 | #[repr(C)] 299 | #[derive(Clone, Copy)] 300 | pub struct ovrFrameTiming { 301 | pub DeltaSeconds: f32, 302 | pub Pad: f32, 303 | pub ThisFrameSeconds: f64, 304 | pub TimewarpPointSeconds: f64, 305 | pub NextFrameSeconds: f64, 306 | pub ScanoutMidpointSeconds: f64, 307 | pub EyeScanoutSeconds: [f64; 2] 308 | } 309 | 310 | macro_rules! function_table { 311 | ( $( fn $func_name:ident( $( $param_name:ident: $param_type:ty ),* ) -> $ret_type:ty ),+ ) => { 312 | #[allow(non_snake_case)] 313 | struct FunctionTablePtrs { 314 | $( 315 | $func_name: unsafe extern "C" fn($( $param_type, )*) -> $ret_type, 316 | )* 317 | } 318 | 319 | pub struct FunctionTable { 320 | ptrs: FunctionTablePtrs, 321 | lib: UnsafeDynamicLibrary 322 | } 323 | 324 | #[allow(non_snake_case)] 325 | impl FunctionTable { 326 | pub unsafe fn load(lib: UnsafeDynamicLibrary) -> Result { 327 | let ptrs = FunctionTablePtrs { 328 | $( 329 | $func_name: mem::transmute( 330 | try!(lib.symbol::<*const libc::c_void>(stringify!($func_name))) 331 | ), 332 | )* 333 | }; 334 | Ok(FunctionTable { 335 | ptrs: ptrs, 336 | lib: lib 337 | }) 338 | } 339 | 340 | $( 341 | #[inline] 342 | pub unsafe fn $func_name(&self, $( $param_name: $param_type),*) -> $ret_type { 343 | (self.ptrs.$func_name)($( $param_name, )*) 344 | } 345 | )* 346 | } 347 | }; 348 | } 349 | 350 | bitflags!( 351 | #[repr(C)] 352 | #[derive(Default)] 353 | flags ovrProjectionModifier: u32 { 354 | const ovrProjection_None = 0x00, 355 | const ovrProjection_RightHanded = 0x01, 356 | const ovrProjection_FarLessThanNear = 0x02, 357 | const ovrProjection_FarClipAtInfinity = 0x04, 358 | const ovrProjection_ClipRangeOpenGL = 0x08 359 | } 360 | ); 361 | 362 | function_table!( 363 | fn ovr_Initialize(params: *const ovrInitParams) -> ovrBool, 364 | fn ovr_Shutdown() -> (), 365 | 366 | fn ovrHmd_Create(index: i32) -> *mut ovrHmdDesc, 367 | fn ovrHmd_CreateDebug(the_type: ovrHmdType) -> *mut ovrHmdDesc, 368 | fn ovrHmd_Destroy(hmd: *mut ovrHmdDesc) -> (), 369 | 370 | fn ovrHmd_SetEnabledCaps(hmd: *mut ovrHmdDesc, hmdCaps: ovrHmdCaps) -> (), 371 | fn ovrHmd_DismissHSWDisplay(hmd: *mut ovrHmdDesc) -> ovrBool, 372 | fn ovrHmd_RecenterPose(hmd: *mut ovrHmdDesc) -> (), 373 | fn ovrHmd_ConfigureTracking(hmd: *mut ovrHmdDesc, 374 | supportedTrackingCaps: ovrTrackingCaps, 375 | requiredTrackingCaps: ovrTrackingCaps) -> ovrBool, 376 | fn ovrHmd_ConfigureRendering(hmd: *mut ovrHmdDesc, 377 | apiConfig: *const ovrGLConfig, 378 | distortionCaps: ovrDistortionCaps, 379 | eyeFovIn: *const [ovrFovPort; 2], 380 | eyeRenderDescOut: *mut [ovrEyeRenderDesc; 2]) -> ovrBool, 381 | fn ovrHmd_AttachToWindow(hmd: *mut ovrHmdDesc, 382 | window: *const libc::c_void, 383 | destMirrorRect: *const ovrRecti, 384 | sourceRenderTargetRect: *const ovrRecti) -> ovrBool, 385 | fn ovrHmd_GetFovTextureSize(hmd: *mut ovrHmdDesc, 386 | eye: i32, 387 | fov: ovrFovPort, 388 | pixelsPerDisplayPixel: f32) -> ovrSizei, 389 | 390 | fn ovrHmd_BeginFrame(hmd: *mut ovrHmdDesc, frameIndex: u32) -> ovrFrameTiming, 391 | fn ovrHmd_GetEyePoses(hmd: *mut ovrHmdDesc, 392 | frameIndex: u32, 393 | hmdToEyeViewOffset: *const [ovrVector3f; 2], 394 | outEyePoses: *mut [ovrPosef; 2], 395 | outHmdTrackingState: *mut libc::c_void) -> (), 396 | fn ovrHmd_EndFrame(hmd: *mut ovrHmdDesc, 397 | renderPose: *const [ovrPosef; 2], 398 | eyeTexture: *const [ovrGLTexture; 2]) -> (), 399 | 400 | fn ovrMatrix4f_Projection(fov: ovrFovPort, 401 | znear: f32, 402 | zfar: f32, 403 | projectionModFlags: ovrProjectionModifier) -> ovrMatrix4f 404 | ); 405 | 406 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Idiomatic Rust bindings for the Oculus SDK. Provides access to headset 2 | //! metadata and tracking information, plus helpers for attaching the headset 3 | //! to an OpenGL rendering context. 4 | 5 | #[macro_use] extern crate bitflags; 6 | extern crate libc; 7 | #[cfg(windows)] extern crate winapi; 8 | #[cfg(windows)] extern crate kernel32; 9 | 10 | #[cfg(feature = "glutin")] 11 | extern crate glutin; 12 | 13 | use std::rc::Rc; 14 | use std::fmt; 15 | 16 | mod ffi; 17 | mod shim; 18 | 19 | pub use shim::HmdDisplayId; 20 | pub use shim::HmdDisplay; 21 | 22 | pub mod render; 23 | pub mod target; 24 | 25 | /// Error produced while interacting with a wrapped Oculus device. 26 | #[derive(Clone, Debug)] 27 | pub enum OculusError { 28 | /// Error while attempting to find the Oculus runtime. This probably means a supported version 29 | /// of the runtime is not installed. 30 | OculusRuntimeError(String), 31 | 32 | /// Error while interacting directly with the Oculus SDK. The SDK doesn't provide more detailed 33 | /// error information, but the included string provides some basic context about what was 34 | /// happening at the time of failure. 35 | SdkError(&'static str), 36 | 37 | /// Only one `Context` can be active at a time per process. This error occurs when attempting to 38 | /// create a second `Context` while a `Context` is already active. 39 | DuplicateContext 40 | } 41 | 42 | impl fmt::Display for OculusError { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | match self { 45 | &OculusError::OculusRuntimeError(ref description) => f.write_str(description), 46 | &OculusError::SdkError(ref description) => f.write_str(description), 47 | &OculusError::DuplicateContext => f.write_str( 48 | "Context creation failed because another Context is already active in this process") 49 | } 50 | } 51 | } 52 | 53 | #[derive(Copy, Clone)] 54 | pub enum Eye { 55 | Left, 56 | Right 57 | } 58 | 59 | /// Oculus SDK context. Ensures the Oculus SDK has been initialized properly, and serves as a 60 | /// factory for builders that give access to the HMD. 61 | pub struct Context { 62 | shim_context: Rc 63 | } 64 | 65 | impl Context { 66 | /// Create a new Oculus SDK context. 67 | /// 68 | /// # Failure 69 | /// 70 | /// Only one `Context` can be active per process. If a `Context` is already active, this will 71 | /// fail with `Err(OculsuError::DuplicateContext)`. Note that `Hmd`s hold an internal reference 72 | /// to their associated context. 73 | pub fn new() -> Result { 74 | let shim_context = Rc::new(try!(shim::Context::new())); 75 | Ok(Context { 76 | shim_context: shim_context 77 | }) 78 | } 79 | 80 | /// Create a builder for an HMD. 81 | pub fn build_hmd(&self) -> HmdBuilder { 82 | HmdBuilder::new(self.shim_context.clone()) 83 | } 84 | } 85 | 86 | /// Options for specifying the enabled tracking capabilities of a headset. 87 | pub struct TrackingOptions { 88 | track_caps: ffi::ovrTrackingCaps 89 | } 90 | 91 | impl TrackingOptions { 92 | /// `TrackingOptions` with no tracking options enabled. 93 | pub fn new() -> TrackingOptions { 94 | TrackingOptions { 95 | track_caps: ffi::ovrTrackingCaps::empty() 96 | } 97 | } 98 | 99 | /// `TrackingOptions` with all supported tracking options enabled. 100 | pub fn with_all() -> TrackingOptions { 101 | TrackingOptions { 102 | track_caps: ffi::ovrTrackingCap_Orientation | 103 | ffi::ovrTrackingCap_MagYawCorrection | 104 | ffi::ovrTrackingCap_Position 105 | } 106 | } 107 | 108 | /// Enable tracking of head position. 109 | pub fn position<'f>(&'f mut self) -> &'f mut TrackingOptions { 110 | self.track_caps.insert(ffi::ovrTrackingCap_Position); 111 | self 112 | } 113 | 114 | /// Enable tracking of head orientation. 115 | pub fn orientation<'f>(&'f mut self) -> &'f mut TrackingOptions { 116 | self.track_caps.insert(ffi::ovrTrackingCap_Orientation); 117 | self 118 | } 119 | 120 | /// Enable yaw drift correction. 121 | pub fn mag_yaw_correct<'f>(&'f mut self) -> &'f mut TrackingOptions { 122 | self.track_caps.insert(ffi::ovrTrackingCap_MagYawCorrection); 123 | self 124 | } 125 | 126 | } 127 | 128 | /// Builder to construct an HMD. Allows the configuration of HMD settings and tracking 129 | /// capabilities. 130 | pub struct HmdBuilder { 131 | caps: ffi::ovrHmdCaps, 132 | track_caps: ffi::ovrTrackingCaps, 133 | allow_debug: bool, 134 | owning_context: Rc 135 | } 136 | 137 | impl HmdBuilder { 138 | fn new(owning_context: Rc) -> HmdBuilder { 139 | let default_caps = ffi::ovrHmdCap_LowPersistence | ffi::ovrHmdCap_DynamicPrediction; 140 | HmdBuilder { 141 | caps: default_caps, 142 | track_caps: ffi::ovrTrackingCaps::empty(), 143 | allow_debug: false, 144 | owning_context: owning_context 145 | } 146 | } 147 | 148 | /// Disables mirroring of HMD output to the attached window. This may improve 149 | /// rendering performance slightly. 150 | pub fn no_mirror<'f>(&'f mut self) -> &'f mut HmdBuilder { 151 | self.caps.insert(ffi::ovrHmdCap_NoMirrorToWindow); 152 | self 153 | } 154 | 155 | /// Turns off HMD screen and output (only if the HMD is not in Direct display 156 | /// mode). 157 | pub fn no_display<'f>(&'f mut self) -> &'f mut HmdBuilder { 158 | self.caps.insert(ffi::ovrHmdCap_DisplayOff); 159 | self 160 | } 161 | 162 | /// Disable low persistence. 163 | pub fn no_low_persistence<'f>(&'f mut self) -> &'f mut HmdBuilder { 164 | self.caps.remove(ffi::ovrHmdCap_LowPersistence); 165 | self 166 | } 167 | 168 | /// Disable dynamic adjustment of tracking prediction based on internally 169 | /// measured latency. 170 | pub fn no_dynamic_prediction<'f>(&'f mut self) -> &'f mut HmdBuilder { 171 | self.caps.remove(ffi::ovrHmdCap_DynamicPrediction); 172 | self 173 | } 174 | 175 | /// Disable VSync. 176 | pub fn no_vsync<'f>(&'f mut self) -> &'f mut HmdBuilder { 177 | self.caps.insert(ffi::ovrHmdCap_NoVSync); 178 | self 179 | } 180 | 181 | /// Enable tracking with the specified tracking options. 182 | pub fn track<'f>(&'f mut self, tracking_options: &TrackingOptions) -> &'f mut HmdBuilder { 183 | self.track_caps = tracking_options.track_caps; 184 | self 185 | } 186 | 187 | /// Allow creation of a dummy "debug" HMD if no other HMD is found. 188 | pub fn allow_debug<'f>(&'f mut self) -> &'f mut HmdBuilder { 189 | self.allow_debug = true; 190 | self 191 | } 192 | 193 | /// Build the HMD instance. This will begin tracking if tracking is enabled. 194 | pub fn build(&self) -> Result { 195 | Hmd::new(self.caps, self.track_caps, self.allow_debug, self.owning_context.clone()) 196 | } 197 | } 198 | 199 | /// A target window to bind headset rendering to. 200 | pub trait RenderTarget { 201 | /// Number of samples used for MSAA. 202 | fn get_multisample(&self) -> u32; 203 | 204 | /// The native window handle for this window. This can return null for all platforms except 205 | /// Windows. The returned handle must be valid with an effective lifetime greater than or equal 206 | /// to the lifetime of self. 207 | unsafe fn get_native_window(&self) -> *const libc::c_void; 208 | } 209 | 210 | /// An initialized HMD. 211 | pub struct Hmd { 212 | shim_hmd: shim::Hmd 213 | } 214 | 215 | impl Hmd { 216 | fn new(caps: ffi::ovrHmdCaps, 217 | track_caps: ffi::ovrTrackingCaps, 218 | allow_debug: bool, 219 | owning_context: Rc) -> Result { 220 | let mut shim_hmd = try!(shim::Hmd::new(allow_debug, owning_context)); 221 | shim_hmd.set_caps(caps); 222 | if !track_caps.is_empty() { 223 | try!(shim_hmd.configure_tracking(track_caps)); 224 | } 225 | Ok(Hmd{ shim_hmd: shim_hmd }) 226 | } 227 | 228 | /// Create a `RenderContext` for this headset. 229 | pub fn render_to<'a>(&'a self, 230 | target: &'a RenderTarget) -> Result { 231 | use shim::CreateRenderContext; 232 | render::RenderContext::new(&self.shim_hmd, target) 233 | } 234 | 235 | /// Returns a `(width, height)` pair representing the native resolution of the HMD. 236 | pub fn resolution(&self) -> (u32, u32) { 237 | self.shim_hmd.resolution() 238 | } 239 | 240 | /// Return details about the display representing this headset. 241 | pub fn get_display(&self) -> HmdDisplay { 242 | self.shim_hmd.get_display() 243 | } 244 | } 245 | 246 | -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | //! Methods for directly working with rendering. 2 | //! 3 | //! # Example 4 | //! 5 | //! End-to-end rendering looks something like this: 6 | //! 7 | //! ```no_run 8 | //! # extern crate rovr; 9 | //! # extern crate libc; 10 | //! # use rovr::{Context, TrackingOptions, Eye}; 11 | //! # use rovr::render::Frame; 12 | //! # fn main() { 13 | //! let hmd = Context::new().unwrap() 14 | //! .build_hmd() 15 | //! .track(&TrackingOptions::with_all()) 16 | //! .build().unwrap(); 17 | //! let (w, h) = hmd.resolution(); 18 | //! 19 | //! // 20 | //! // 21 | //! 22 | //! # struct EmptyRenderTarget; 23 | //! # impl rovr::RenderTarget for EmptyRenderTarget { 24 | //! # fn get_multisample(&self) -> u32 { 0 } 25 | //! # unsafe fn get_native_window(&self) -> *const libc::c_void { std::ptr::null() } 26 | //! # } 27 | //! # let render_target = EmptyRenderTarget; 28 | //! 29 | //! // This is unsafe because of the lifetime of native_window. If the window is closed before this 30 | //! // render context is destroyed, bad things may happen! 31 | //! let rc = hmd.render_to(&render_target).unwrap(); 32 | //! let (w_left, h_left) = rc.target_texture_size(&Eye::Left); 33 | //! let (w_right, h_right) = rc.target_texture_size(&Eye::Right); 34 | //! 35 | //! // 36 | //! // 37 | //! 38 | //! # let (left_tex_id, right_tex_id) = (0, 0); 39 | //! let binding = rc.create_binding(left_tex_id, right_tex_id); 40 | //! loop { 41 | //! let frame = Frame::new(&rc, &binding); 42 | //! // draw to framebuffers; frame will finish at end of loop body 43 | //! } 44 | //! # } 45 | //! ``` 46 | 47 | pub use shim::RenderContext; 48 | pub use shim::TextureBinding; 49 | pub use shim::Quaternion; 50 | pub use shim::Vector3; 51 | pub use shim::Matrix4; 52 | pub use shim::FrameEyePose; 53 | pub use shim::Frame; 54 | -------------------------------------------------------------------------------- /src/shim.rs: -------------------------------------------------------------------------------- 1 | //! Safe shim directly over the Oculus SDK. More or less directly exposes the Oculus "way" of 2 | //! interfacing with an HMD and handling rendering. 3 | 4 | use std::ptr; 5 | use std::default::Default; 6 | use ffi::UnsafeDynamicLibrary; 7 | use std::marker::PhantomData; 8 | use std::rc::Rc; 9 | use std::string::String; 10 | use std::sync::atomic; 11 | use std::vec; 12 | 13 | use libc; 14 | 15 | use ffi; 16 | use OculusError; 17 | use Eye; 18 | use RenderTarget; 19 | 20 | /// A quaternion. The first element of the tuple is the w value, and the array contains x, y, and z 21 | /// values. 22 | pub type Quaternion = (f32, [f32; 3]); 23 | 24 | /// A 3-dimensional vector, with (in order) x, y, and z components. 25 | pub type Vector3 = [f32; 3]; 26 | 27 | /// A 4x4 matrix, by convention in column-major format. 28 | pub type Matrix4 = [[f32; 4]; 4]; 29 | 30 | /// Invoke an FFI function with an ovrBool return value, yielding OculusError::SdkError with the 31 | /// supplied message on failure. 32 | macro_rules! ovr_invoke { 33 | ($x:expr) => { 34 | if $x == ffi::ovrFalse { 35 | return Err(OculusError::SdkError("$x failed")); 36 | } 37 | } 38 | } 39 | 40 | /// Invoke an FFI function with an ovrBool return value, and panic on failure. 41 | macro_rules! ovr_expect { 42 | ($x:expr) => { 43 | if $x == ffi::ovrFalse { 44 | panic!("$x failed"); 45 | } 46 | } 47 | } 48 | 49 | /// RAII wrapper for an Oculus context. Ensures only one Context is active at once in the process. 50 | pub struct Context { 51 | function_table: ffi::FunctionTable 52 | } 53 | 54 | static ACTIVE_CONTEXT: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; 55 | 56 | const PRODUCT_VERSION: &'static str = "0"; 57 | const MAJOR_VERSION: &'static str = "5"; 58 | 59 | macro_rules! try_load { 60 | ($x:expr) => { 61 | match $x { 62 | Ok(v) => v, 63 | Err(v) => return Err(OculusError::OculusRuntimeError(v)) 64 | } 65 | } 66 | } 67 | 68 | // Notes from OVR CAPI shim: 69 | // 70 | // Versioned file expectations. 71 | // 72 | // Windows: LibOVRRT__.dll 73 | // Example: LibOVRRT64_1_1.dll -- LibOVRRT 64 bit, product 1, major version 1, minor/patch/build 74 | // numbers unspecified in the name. 75 | // 76 | // Mac: LibOVRRT_.framework/Versions//LibOVRRT_ 77 | // We are not presently using the .framework bundle's Current directory to hold the version number. 78 | // This may change. 79 | // 80 | // Linux: libOVRRT_.so. 81 | // The file on disk may contain a minor version number, but a symlink is used to map this 82 | // major-only version to it. 83 | 84 | #[cfg(windows)] 85 | fn load_ovr() -> Result { 86 | let bits = if cfg!(target_pointer_width = "64") { "64" } else { "32" }; 87 | let lib_name = format!("LibOVRRT{}_{}_{}", bits, PRODUCT_VERSION, MAJOR_VERSION); 88 | Ok(try_load!(unsafe { UnsafeDynamicLibrary::open(Some(lib_name.as_ref())) })) 89 | } 90 | 91 | #[cfg(target_os = "macos")] 92 | fn load_ovr() -> Result { 93 | let lib_name = format!("LibOVRRT_{0}.framework/Versions/{1}/LibOVRRT_{0}", PRODUCT_VERSION, MAJOR_VERSION); 94 | Ok(try_load!(unsafe { UnsafeDynamicLibrary::open(Some(lib_name.as_ref())) })) 95 | } 96 | 97 | #[cfg(target_os = "linux")] 98 | fn load_ovr() -> Result { 99 | let bits = if cfg!(target_pointer_width = "64") { "64" } else { "32" }; 100 | let lib_name = format!("/usr/local/lib/libOVRRT{}_{}.so.{}", bits, PRODUCT_VERSION, MAJOR_VERSION); 101 | unsafe { 102 | Ok(try_load!(UnsafeDynamicLibrary::open(Some(lib_name.as_ref())))) 103 | } 104 | } 105 | 106 | impl Context { 107 | pub fn new() -> Result { 108 | let was_active = ACTIVE_CONTEXT.compare_and_swap(false, true, atomic::Ordering::SeqCst); 109 | if was_active { 110 | return Err(OculusError::DuplicateContext); 111 | } 112 | 113 | let lib = try!(load_ovr()); 114 | let function_table = unsafe { 115 | let function_table = try_load!(ffi::FunctionTable::load(lib)); 116 | let params: ffi::ovrInitParams = Default::default(); 117 | ovr_invoke!(function_table.ovr_Initialize(¶ms)); 118 | function_table 119 | }; 120 | Ok(Context { 121 | function_table: function_table 122 | }) 123 | } 124 | 125 | pub fn invoker(&self) -> &ffi::FunctionTable { 126 | &self.function_table 127 | } 128 | } 129 | 130 | impl Drop for Context { 131 | fn drop(&mut self) { 132 | unsafe { 133 | self.invoker().ovr_Shutdown(); 134 | let was_active = ACTIVE_CONTEXT.swap(false, atomic::Ordering::SeqCst); 135 | assert!(was_active); 136 | } 137 | } 138 | } 139 | 140 | /// Platform-specific identifier for the OS display representing an Hmd. 141 | #[allow(dead_code)] // Per-platform, only one of these enum values is used. 142 | #[derive(Debug, Eq, PartialEq)] 143 | pub enum HmdDisplayId { 144 | /// On OS X, this value is the display ID as it would be returned from 145 | /// `CGGetActiveDisplayList`. 146 | Numeric(u32), 147 | 148 | /// On Windows, this value is the device name as would be reported by `EnumDisplayDevices`. 149 | Name(String), 150 | 151 | /// On other platforms, a native identifier for this monitor is not reported by the SDK. 152 | Unavailable 153 | } 154 | 155 | /// Full details about the system display representing this Hmd. These should be used to find the 156 | /// correct monitor on which to prepare a rendering window. 157 | pub struct HmdDisplay { 158 | /// Identifier for this monitor, if available. 159 | pub id: HmdDisplayId, 160 | 161 | /// Left edge of the display region. 162 | pub x: i32, 163 | 164 | /// Top edge of the display region. 165 | pub y: i32, 166 | 167 | /// Width of the display region. 168 | pub width: u32, 169 | 170 | /// Height of the display region. 171 | pub height: u32 172 | } 173 | 174 | /// RAII wrapper for an Oculus headset. Provides safe wrappers for access to basic headset 175 | /// metadata and tracking state. 176 | pub struct Hmd { 177 | native_hmd: *mut ffi::ovrHmdDesc, 178 | context: Rc 179 | } 180 | 181 | impl Hmd { 182 | /// Create a new HMD. If `allow_debug` is true and no headset is otherwise detected, a fake 183 | /// "debug" HMD instance will be returned instead. 184 | pub fn new(allow_debug: bool, owning_context: Rc) -> Result { 185 | let hmd = { 186 | unsafe { 187 | let h = owning_context.invoker().ovrHmd_Create(0); 188 | if h.is_null() && allow_debug { 189 | owning_context.invoker().ovrHmd_CreateDebug(ffi::ovrHmd_DK2) 190 | } else { 191 | h 192 | } 193 | } 194 | }; 195 | if hmd.is_null() { 196 | Err(OculusError::SdkError("ovrHmd_Create failed")) 197 | } else { 198 | Ok(Hmd{ native_hmd: hmd, context: owning_context }) 199 | } 200 | } 201 | 202 | /// Set HMD caps. Some HMD caps cannot be set using the Oculus SDK; see the Oculus docs for 203 | /// more details. 204 | pub fn set_caps(&mut self, caps: ffi::ovrHmdCaps) { 205 | unsafe { 206 | self.context.invoker().ovrHmd_SetEnabledCaps(self.native_hmd, caps); 207 | } 208 | } 209 | 210 | /// Dismiss the Health and Safety warning automatically displayed by the Oculus runtime. This 211 | /// should only be dismissed in response to user input; see the Oculus SDK documentation for 212 | /// details on proper usage. 213 | pub fn dismiss_hsw(&self) { 214 | unsafe { 215 | // Ignore the return value; the underlying implementation is already idempotent, and 216 | // queues up the dismissal if it isn't ready yet. 217 | self.context.invoker().ovrHmd_DismissHSWDisplay(self.native_hmd); 218 | } 219 | } 220 | 221 | pub fn recenter_pose(&self) { 222 | unsafe { 223 | self.context.invoker().ovrHmd_RecenterPose(self.native_hmd); 224 | } 225 | } 226 | 227 | /// Enable tracking for this HMD with the specified capabilities. 228 | pub fn configure_tracking(&mut self, caps: ffi::ovrTrackingCaps) -> Result<(), OculusError> { 229 | unsafe { 230 | ovr_invoke!(self.context.invoker().ovrHmd_ConfigureTracking(self.native_hmd, 231 | caps, 232 | ffi::ovrTrackingCaps::empty())); 233 | } 234 | Ok(()) 235 | } 236 | 237 | /// Returns true if the HMD is configured to run in Direct mode, or false if it is in Extend 238 | /// Desktop mode. 239 | pub fn is_direct(&self) -> bool { 240 | unsafe { 241 | let h = &*self.native_hmd; 242 | !h.HmdCaps.contains(ffi::ovrHmdCap_ExtendDesktop) 243 | } 244 | } 245 | 246 | /// Native resolution of the full HMD display. 247 | pub fn resolution(&self) -> (u32, u32) { 248 | unsafe { 249 | let ref native_struct = *self.native_hmd; 250 | (native_struct.Resolution.w as u32, native_struct.Resolution.h as u32) 251 | } 252 | } 253 | 254 | /// Get the native display identifier for the monitor represented by this HMD. 255 | pub fn get_display(&self) -> HmdDisplay { 256 | unsafe { 257 | let ref native_struct = *self.native_hmd; 258 | let id = if cfg!(windows) { 259 | let s = { 260 | use std::ffi::CStr; 261 | CStr::from_ptr(native_struct.DisplayDeviceName).to_bytes() 262 | }; 263 | HmdDisplayId::Name(String::from_utf8_lossy(s).into_owned()) 264 | } else if cfg!(target_os = "macos") { 265 | HmdDisplayId::Numeric(native_struct.DisplayId as u32) 266 | } else { 267 | HmdDisplayId::Unavailable 268 | }; 269 | HmdDisplay { 270 | id: id, 271 | x: native_struct.WindowsPos.x, 272 | y: native_struct.WindowsPos.y, 273 | width: native_struct.Resolution.w as u32, 274 | height: native_struct.Resolution.h as u32 275 | } 276 | } 277 | } 278 | } 279 | 280 | impl Drop for Hmd { 281 | fn drop(&mut self) { 282 | unsafe { 283 | self.context.invoker().ovrHmd_Destroy(self.native_hmd); 284 | } 285 | } 286 | } 287 | 288 | /// An active Oculus rendering context associated with an HMD. Only OpenGL is supported. This 289 | /// provides access to the basic metadata necessary to prepare OpenGL framebuffers for drawing. 290 | /// 291 | /// See `hmd.render_to()` for details on use. 292 | pub struct RenderContext<'a> { 293 | eye_texture_sizes: [ffi::ovrSizei; 2], 294 | fovs: [ffi::ovrFovPort; 2], 295 | offsets: [ffi::ovrVector3f; 2], 296 | 297 | owning_hmd: &'a Hmd, 298 | 299 | // hold on to the render target because we need the window handle to stay alive 300 | _render_phantom: PhantomData<&'a RenderTarget> 301 | } 302 | 303 | struct GlConfigBuilder { 304 | config: ffi::ovrGLConfig 305 | } 306 | 307 | impl GlConfigBuilder { 308 | fn new(w: u32, h: u32, multisample: i32) -> GlConfigBuilder { 309 | GlConfigBuilder { 310 | config: ffi::ovrGLConfig { 311 | API: ffi::ovrRenderAPI_OpenGL, 312 | BackBufferSize: ffi::ovrSizei { w: w as i32, h: h as i32 }, 313 | Multisample: multisample, 314 | .. Default::default() 315 | } 316 | } 317 | } 318 | 319 | #[cfg(windows)] 320 | fn native_window<'a>(&'a mut self, native_window: *const libc::c_void) -> &'a mut GlConfigBuilder { 321 | self.config.Window = native_window; 322 | self 323 | } 324 | 325 | #[cfg(not(windows))] 326 | fn native_window<'a>(&'a mut self, _: *const libc::c_void) -> &'a mut GlConfigBuilder { 327 | self 328 | } 329 | 330 | fn build(&self) -> ffi::ovrGLConfig { 331 | self.config.clone() 332 | } 333 | } 334 | 335 | 336 | pub trait CreateRenderContext<'a> { 337 | fn new(owning_hmd: &'a Hmd, 338 | target: &'a RenderTarget) -> Result; 339 | } 340 | 341 | impl<'a> CreateRenderContext<'a> for RenderContext<'a> { 342 | /// Create an active Oculus rendering context. 343 | fn new(owning_hmd: &'a Hmd, 344 | target: &'a RenderTarget) -> Result, OculusError> { 345 | let (w, h) = owning_hmd.resolution(); 346 | let invoker = owning_hmd.context.invoker(); 347 | let (offsets, fovs) = unsafe { 348 | let config = GlConfigBuilder::new(w, h, target.get_multisample() as i32) 349 | .native_window(target.get_native_window()) 350 | .build(); 351 | 352 | // TODO: pull in caps as an argument 353 | let caps = 354 | ffi::ovrDistortionCap_TimeWarp | 355 | ffi::ovrDistortionCap_Overdrive; 356 | let mut eye_render_desc: [ffi::ovrEyeRenderDesc; 2] = [Default::default(); 2]; 357 | let hmd_data = &*owning_hmd.native_hmd; 358 | ovr_invoke!(invoker.ovrHmd_ConfigureRendering(owning_hmd.native_hmd, 359 | &config, 360 | caps, 361 | &hmd_data.MaxEyeFov, 362 | &mut eye_render_desc)); 363 | if owning_hmd.is_direct() { 364 | ovr_invoke!(invoker.ovrHmd_AttachToWindow(owning_hmd.native_hmd, 365 | target.get_native_window(), 366 | ptr::null(), 367 | ptr::null())); 368 | } 369 | ([eye_render_desc[0].HmdToEyeViewOffset, eye_render_desc[1].HmdToEyeViewOffset], 370 | [eye_render_desc[0].Fov, eye_render_desc[1].Fov]) 371 | }; 372 | let mut eye_texture_sizes = (0..2).map(|eye_index| { 373 | unsafe { 374 | let h = &*owning_hmd.native_hmd; 375 | invoker.ovrHmd_GetFovTextureSize(owning_hmd.native_hmd, 376 | eye_index, 377 | h.MaxEyeFov[eye_index as usize], 378 | 1f32) 379 | } 380 | }); 381 | 382 | Ok(RenderContext { 383 | eye_texture_sizes: [eye_texture_sizes.next().unwrap(), 384 | eye_texture_sizes.next().unwrap()], 385 | fovs: fovs, 386 | offsets: offsets, 387 | 388 | owning_hmd: owning_hmd, 389 | 390 | _render_phantom: PhantomData, 391 | }) 392 | } 393 | } 394 | 395 | impl<'a> RenderContext<'a> { 396 | /// Dismiss the Health and Safety warning automatically displayed by the Oculus runtime. This 397 | /// should only be dismissed in response to user input; see the Oculus SDK documentation for 398 | /// details on proper usage. 399 | pub fn dismiss_hsw(&self) { 400 | self.owning_hmd.dismiss_hsw(); 401 | } 402 | 403 | /// Recenter the headset, using the current orientation and position as the origin. 404 | pub fn recenter_pose(&self) { 405 | self.owning_hmd.recenter_pose(); 406 | } 407 | 408 | /// Return a `(width, height)` tuple containing the suggested size for a render target for the 409 | /// given eye. 410 | pub fn target_texture_size(&self, eye: &Eye) -> (u32, u32) { 411 | let ref size = match eye { 412 | &Eye::Left => self.eye_texture_sizes[0], 413 | &Eye::Right => self.eye_texture_sizes[1] 414 | }; 415 | (size.w as u32, size.h as u32) 416 | } 417 | 418 | /// Create an appropriate projection matrix for the given eye. This will properly account for 419 | /// the native field of view of the associated headset. The returned matrix is a right-handed 420 | /// projection with an OpenGL clipping range (-w to w). 421 | pub fn projection_matrix(&self, eye: &Eye, near_z: f32, far_z: f32) -> Matrix4 { 422 | let invoker = self.owning_hmd.context.invoker(); 423 | let matrix = unsafe { 424 | let ref fov = match eye { 425 | &Eye::Left => self.fovs[0], 426 | &Eye::Right => self.fovs[1] 427 | }; 428 | let flags = 429 | ffi::ovrProjection_RightHanded | 430 | ffi::ovrProjection_ClipRangeOpenGL; 431 | invoker.ovrMatrix4f_Projection(*fov, near_z, far_z, flags) 432 | }; 433 | let ref pm = matrix.M; 434 | // ovr matrices are row-major, so we must invert 435 | [[pm[0][0], pm[1][0], pm[2][0], pm[3][0]], 436 | [pm[0][1], pm[1][1], pm[2][1], pm[3][1]], 437 | [pm[0][2], pm[1][2], pm[2][2], pm[3][2]], 438 | [pm[0][3], pm[1][3], pm[2][3], pm[3][3]]] 439 | } 440 | 441 | /// Create a texture binding given a pair of OpenGL texture IDs for the left and right eye, 442 | /// respectively. The left and right textures should be of the size suggested by 443 | /// `target_texture_size`. 444 | pub fn create_binding(&self, tex_id_left: u32, tex_id_right: u32) -> TextureBinding { 445 | TextureBinding::new((self.eye_texture_sizes[0], tex_id_left), 446 | (self.eye_texture_sizes[1], tex_id_right)) 447 | } 448 | } 449 | 450 | impl<'a> Drop for RenderContext<'a> { 451 | fn drop(&mut self) { 452 | let mut eye_render_desc: [ffi::ovrEyeRenderDesc; 2] = [Default::default(); 2]; 453 | unsafe { 454 | let invoker = self.owning_hmd.context.invoker(); 455 | let hmd_data = &*self.owning_hmd.native_hmd; 456 | ovr_expect!(invoker.ovrHmd_ConfigureRendering(self.owning_hmd.native_hmd, 457 | ptr::null(), 458 | ffi::ovrDistortionCaps::empty(), 459 | &hmd_data.MaxEyeFov, 460 | &mut eye_render_desc)); 461 | } 462 | } 463 | } 464 | 465 | /// Texture binding, representing a registered pair of OpenGL textures that should serve as render 466 | /// targets for per-eye viewpoints. Create with `RenderContext::create_binding()` 467 | pub struct TextureBinding { 468 | textures: [ffi::ovrGLTexture; 2] 469 | } 470 | 471 | impl TextureBinding { 472 | fn new(left_pair: (ffi::ovrSizei, u32), right_pair: (ffi::ovrSizei, u32)) -> TextureBinding { 473 | fn texture_struct(size: ffi::ovrSizei, id: u32) -> ffi::ovrGLTexture { 474 | let viewport = ffi::ovrRecti { 475 | Pos: ffi::ovrVector2i { x: 0i32, y: 0i32 }, 476 | Size: size 477 | }; 478 | ffi::ovrGLTexture { 479 | API: ffi::ovrRenderAPI_OpenGL, 480 | TextureSize: size, 481 | RenderViewport: viewport, 482 | TexId: id, 483 | .. Default::default() 484 | } 485 | } 486 | 487 | TextureBinding { 488 | textures: [texture_struct(left_pair.0, left_pair.1), 489 | texture_struct(right_pair.0, right_pair.1)] 490 | } 491 | } 492 | 493 | } 494 | 495 | /// A single eye's pose for a frame. 496 | #[derive(Clone, Copy)] 497 | pub struct FrameEyePose { 498 | pub eye: Eye, 499 | pub orientation: Quaternion, 500 | pub position: Vector3, 501 | } 502 | 503 | /// A single frame. All OpenGL rendering to both eyes' frame buffers should happen while this 504 | /// object is alive. When going out of scope, the Oculus SDK will complete the rendering process, 505 | /// including post-processing and any necessary buffer swapping. 506 | pub struct Frame<'a> { 507 | owning_context: &'a RenderContext<'a>, 508 | textures: &'a TextureBinding, 509 | poses: [ffi::ovrPosef; 2] 510 | } 511 | 512 | impl<'a> Frame<'a> { 513 | /// Start a frame. 514 | pub fn new(owning_context: &'a RenderContext, 515 | texture_binding: &'a TextureBinding) -> Frame<'a> { 516 | let mut poses: [ffi::ovrPosef; 2] = [Default::default(); 2]; 517 | let invoker = owning_context.owning_hmd.context.invoker(); 518 | unsafe { 519 | invoker.ovrHmd_BeginFrame(owning_context.owning_hmd.native_hmd, 0); 520 | invoker.ovrHmd_GetEyePoses(owning_context.owning_hmd.native_hmd, 521 | 0, 522 | &owning_context.offsets, 523 | &mut poses, 524 | ptr::null_mut()); 525 | } 526 | 527 | Frame { 528 | owning_context: owning_context, 529 | textures: texture_binding, 530 | poses: poses 531 | } 532 | } 533 | 534 | /// Get an iterable list of eye poses that should be drawn for this frame. These are returned 535 | /// in the suggested rendering order. 536 | pub fn eye_poses(&self) -> vec::IntoIter { 537 | unsafe { 538 | let ref hmd_struct = *self.owning_context.owning_hmd.native_hmd; 539 | let mut poses = Vec::::with_capacity(2); 540 | for i in hmd_struct.EyeRenderOrder.iter() { 541 | let eye = match i { 542 | &0u32 => Eye::Left, 543 | &1u32 => Eye::Right, 544 | _ => panic!("Too many eyes!") 545 | }; 546 | let position = self.poses[*i as usize].Position; 547 | let orientation = self.poses[*i as usize].Orientation; 548 | 549 | // note that we must invert projection_matrix to column major 550 | poses.push(FrameEyePose { 551 | eye: eye, 552 | orientation: (orientation.w, [orientation.x, orientation.y, orientation.z]), 553 | position: [position.x, position.y, position.z] 554 | }); 555 | } 556 | poses.into_iter() 557 | } 558 | } 559 | } 560 | 561 | impl<'a> Drop for Frame<'a> { 562 | fn drop(&mut self) { 563 | unsafe { 564 | let invoker = self.owning_context.owning_hmd.context.invoker(); 565 | invoker.ovrHmd_EndFrame(self.owning_context.owning_hmd.native_hmd, 566 | &self.poses, 567 | &self.textures.textures); 568 | } 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /src/target.rs: -------------------------------------------------------------------------------- 1 | //! Types to ease integration of windowing libraries with rovr. 2 | 3 | #[cfg(feature = "glutin")] 4 | mod glutin_target { 5 | use glutin; 6 | use libc; 7 | 8 | use RenderTarget; 9 | use HmdDisplay; 10 | use HmdDisplayId; 11 | 12 | /// Wrapper to use a glutin window as a render target. 13 | pub struct GlutinRenderTarget<'a> { 14 | #[allow(dead_code)] // only used on windows 15 | window: &'a glutin::Window, 16 | multisample: u32 17 | } 18 | 19 | impl<'a> GlutinRenderTarget<'a> { 20 | /// Create a glutin render target from the specified window. `multisample` should match the 21 | /// multisampling level used when creating the window. 22 | pub fn new(window: &'a glutin::Window, 23 | multisample: u32) -> GlutinRenderTarget<'a> { 24 | // wish we didn't need to do this, but currently, glutin won't tell us what multisampling 25 | // was set to on creation 26 | GlutinRenderTarget { 27 | window: window, 28 | multisample: multisample 29 | } 30 | } 31 | } 32 | 33 | impl<'a> RenderTarget for GlutinRenderTarget<'a> { 34 | fn get_multisample(&self) -> u32 { 35 | self.multisample 36 | } 37 | 38 | #[cfg(windows)] 39 | unsafe fn get_native_window(&self) -> *const libc::c_void { 40 | self.window.platform_window() 41 | } 42 | 43 | // glutin currently panics for non-windows platforms if we even ask for the native window, so 44 | // don't! 45 | #[cfg(not(windows))] 46 | unsafe fn get_native_window(&self) -> *const libc::c_void { 47 | use std::ptr; 48 | ptr::null() 49 | } 50 | } 51 | 52 | impl PartialEq for HmdDisplayId { 53 | fn eq(&self, other: &glutin::NativeMonitorId) -> bool { 54 | match (self, other) { 55 | (&HmdDisplayId::Numeric(ref s), &glutin::NativeMonitorId::Numeric(ref o)) => s == o, 56 | (&HmdDisplayId::Name(ref s), &glutin::NativeMonitorId::Name(ref o)) => s == o, 57 | _ => false 58 | } 59 | } 60 | } 61 | 62 | impl PartialEq for glutin::NativeMonitorId { 63 | fn eq(&self, other: &HmdDisplayId) -> bool { 64 | other == self 65 | } 66 | } 67 | 68 | /// Find the glutin monitor that matches the HmdDisplay details. 69 | pub fn find_glutin_monitor(display: &HmdDisplay) -> Option { 70 | // TODO: this needs to also compare window position if the id type is Unavailable, but 71 | // glutin doesn't currently expose this information 72 | for mon in glutin::get_available_monitors() { 73 | if mon.get_native_identifier() == display.id { 74 | return Some(mon); 75 | } 76 | } 77 | None 78 | } 79 | } 80 | 81 | #[cfg(feature = "glutin")] 82 | pub use target::glutin_target::{GlutinRenderTarget, find_glutin_monitor}; 83 | 84 | --------------------------------------------------------------------------------