// pathfinder/demo/magicleap/magicleap.rs // // Copyright © 2019 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use crate::c_api::MLGraphicsBeginFrame; use crate::c_api::MLGraphicsCreateClientGL; use crate::c_api::MLGraphicsDestroyClient; use crate::c_api::MLGraphicsEndFrame; use crate::c_api::MLGraphicsGetRenderTargets; use crate::c_api::MLGraphicsInitFrameParams; use crate::c_api::MLGraphicsOptions; use crate::c_api::MLGraphicsSignalSyncObjectGL; use crate::c_api::MLGraphicsVirtualCameraInfoArray; use crate::c_api::MLHandle; use crate::c_api::MLHeadTrackingCreate; use crate::c_api::MLLifecycleSetReadyIndication; use crate::c_api::MLLogLevel; use crate::c_api::MLLoggingLog; use crate::c_api::MLMat4f; use crate::c_api::MLQuaternionf; use crate::c_api::MLRectf; use crate::c_api::MLTransform; use crate::c_api::MLVec3f; use crate::c_api::ML_HANDLE_INVALID; use crate::c_api::ML_RESULT_TIMEOUT; use crate::c_api::ML_VIRTUAL_CAMERA_COUNT; use egl; use egl::EGL_NO_SURFACE; use egl::EGLContext; use egl::EGLDisplay; use gl; use gl::types::GLuint; use log; use log::debug; use log::info; use log::warn; use pathfinder_demo::window::CameraTransform; use pathfinder_demo::window::Event; use pathfinder_demo::window::Mode; use pathfinder_demo::window::Window; use pathfinder_demo::window::WindowSize; use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::point::Point2DF32; use pathfinder_geometry::basic::rect::RectF32; use pathfinder_geometry::basic::rect::RectI32; use pathfinder_geometry::basic::transform3d::Perspective; use pathfinder_geometry::basic::transform3d::Transform3DF32; use pathfinder_geometry::distortion::BarrelDistortionCoefficients; use pathfinder_gl::GLVersion; use pathfinder_gpu::resources::FilesystemResourceLoader; use pathfinder_gpu::resources::ResourceLoader; use pathfinder_simd::default::F32x4; use smallvec::SmallVec; use std::ffi::CString; use std::io::Write; use std::mem; use std::os::raw::c_void; use std::path::PathBuf; use std::thread; use std::time::Duration; pub struct MagicLeapWindow { framebuffer_id: GLuint, graphics_client: MLHandle, size: Point2DI32, virtual_camera_array: MLGraphicsVirtualCameraInfoArray, initial_camera_transforms: Option>, frame_handle: MLHandle, resource_loader: FilesystemResourceLoader, pose_event: Option>, running: bool, in_frame: bool, } impl Window for MagicLeapWindow { fn resource_loader(&self) -> &dyn ResourceLoader { &self.resource_loader } fn gl_version(&self) -> GLVersion { GLVersion::GL3 } fn gl_default_framebuffer(&self) -> GLuint { self.framebuffer_id } fn mouse_position(&self) -> Point2DI32 { Point2DI32::new(0, 0) } fn create_user_event_id (&self) -> u32 { 0 } fn push_user_event(_: u32, _: u32) { } fn present_open_svg_dialog(&mut self) { } fn run_save_dialog(&self, _: &str) -> Result { Err(()) } fn view_box_size(&self, _mode: Mode) -> Point2DI32 { self.size } fn barrel_distortion_coefficients(&self) -> BarrelDistortionCoefficients { BarrelDistortionCoefficients { k0: 0.0, k1: 0.0 } } fn make_current(&mut self, _mode: Mode, eye: Option) -> RectI32 { self.begin_frame(); let eye = match eye { Some(eye) if (eye as usize) < ML_VIRTUAL_CAMERA_COUNT => eye as usize, _ => { warn!("Asked for eye out of range {:?}", eye); 0 } }; debug!("Making {} current.", eye); let viewport = self.virtual_camera_array.viewport; let color_id = self.virtual_camera_array.color_id.as_gl_uint(); let depth_id = self.virtual_camera_array.depth_id.as_gl_uint(); let virtual_camera = self.virtual_camera_array.virtual_cameras[eye]; let layer_id = virtual_camera.virtual_camera_name as i32; unsafe { gl::FramebufferTextureLayer(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, color_id, 0, layer_id); gl::FramebufferTextureLayer(gl::FRAMEBUFFER, gl::DEPTH_ATTACHMENT, depth_id, 0, layer_id); gl::Viewport(viewport.x as i32, viewport.y as i32, viewport.w as i32, viewport.h as i32); } debug!("Made {} current.", eye); RectI32::new(Point2DI32::new(0, 0), self.size) } fn present(&mut self) { self.end_frame(); self.begin_frame(); } } fn get_proc_address(s: &str) -> *const c_void { egl::get_proc_address(s) as *const c_void } impl MagicLeapWindow { pub fn new(egl_display: EGLDisplay, egl_context: EGLContext) -> MagicLeapWindow { debug!("Creating MagicLeapWindow"); let mut framebuffer_id = 0; let graphics_options = MLGraphicsOptions::default(); let mut graphics_client = unsafe { mem::zeroed() }; let mut head_tracker = unsafe { mem::zeroed() }; let mut targets = unsafe { mem::zeroed() }; let virtual_camera_array = unsafe { mem::zeroed() }; let handle = MLHandle::from(egl_context); unsafe { egl::make_current(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context); gl::load_with(get_proc_address); gl::GenFramebuffers(1, &mut framebuffer_id); MLGraphicsCreateClientGL(&graphics_options, handle, &mut graphics_client).unwrap(); MLLifecycleSetReadyIndication().unwrap(); MLHeadTrackingCreate(&mut head_tracker).unwrap(); MLGraphicsGetRenderTargets(graphics_client, &mut targets).unwrap(); } let (max_width, max_height) = targets.buffers.iter().map(|buffer| buffer.color) .chain(targets.buffers.iter().map(|buffer| buffer.depth)) .map(|target| (target.width as i32, target.height as i32)) .max() .unwrap_or_default(); let resource_loader = FilesystemResourceLoader::locate(); debug!("Created MagicLeapWindow"); MagicLeapWindow { framebuffer_id, graphics_client, size: Point2DI32::new(max_width, max_height), frame_handle: ML_HANDLE_INVALID, virtual_camera_array, initial_camera_transforms: None, resource_loader, pose_event: None, running: true, in_frame: false, } } pub fn size(&self) -> WindowSize { WindowSize { logical_size: self.size, backing_scale_factor: 1.0, } } pub fn running(&self) -> bool { self.running } pub fn try_get_event(&mut self) -> Option { self.pose_event.take().map(Event::CameraTransforms) } fn begin_frame(&mut self) { if !self.in_frame { debug!("PF beginning frame"); let mut params = unsafe { mem::zeroed() }; unsafe { gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer_id); MLGraphicsInitFrameParams(&mut params).unwrap(); let mut result = MLGraphicsBeginFrame(self.graphics_client, ¶ms, &mut self.frame_handle, &mut self.virtual_camera_array); if result == ML_RESULT_TIMEOUT { info!("PF frame timeout"); let mut sleep = Duration::from_millis(1); let max_sleep = Duration::from_secs(5); while result == ML_RESULT_TIMEOUT { sleep = (sleep * 2).min(max_sleep); info!("PF exponential backoff {}ms", sleep.as_millis()); thread::sleep(sleep); result = MLGraphicsBeginFrame(self.graphics_client, ¶ms, &mut self.frame_handle, &mut self.virtual_camera_array); } info!("PF frame finished timeout"); } result.unwrap(); } let virtual_camera_array = &self.virtual_camera_array; let initial_cameras = self.initial_camera_transforms.get_or_insert_with(|| (0..virtual_camera_array.num_virtual_cameras) .map(|i| &virtual_camera_array.virtual_cameras[i as usize]) .map(|camera| Transform3DF32::from(camera.transform).post_mul(&Transform3DF32::from_uniform_scale(0.5))) .collect() ); let camera_transforms = (0..virtual_camera_array.num_virtual_cameras) .map(|i| &virtual_camera_array.virtual_cameras[i as usize]) .zip(initial_cameras) .map(|(camera, initial_camera)| CameraTransform { perspective: Perspective::new( &Transform3DF32::from(camera.projection), RectI32::from(virtual_camera_array.viewport).size(), ), view: Transform3DF32::from(camera.transform).inverse().post_mul(initial_camera), }).collect(); self.in_frame = true; self.pose_event = Some(camera_transforms); debug!("PF begun frame"); } } fn end_frame(&mut self) { if self.in_frame { debug!("PF ending frame"); unsafe { gl::BindFramebuffer(gl::FRAMEBUFFER, 0); for i in 0..self.virtual_camera_array.num_virtual_cameras { let virtual_camera = &self.virtual_camera_array.virtual_cameras[i as usize]; MLGraphicsSignalSyncObjectGL(self.graphics_client, virtual_camera.sync_object).unwrap(); } MLGraphicsEndFrame(self.graphics_client, self.frame_handle).unwrap(); } self.in_frame = false; debug!("PF ended frame"); } } } impl Drop for MagicLeapWindow { fn drop(&mut self) { self.end_frame(); unsafe { gl::DeleteFramebuffers(1, &self.framebuffer_id); MLGraphicsDestroyClient(&mut self.graphics_client); } } } // Logging pub struct MagicLeapLogger { tag: CString, level_filter: log::LevelFilter, } impl log::Log for MagicLeapLogger { fn enabled(&self, metadata: &log::Metadata) -> bool { metadata.level() <= self.level_filter } fn log(&self, record: &log::Record) { let lvl = match record.level() { log::Level::Error => MLLogLevel::Error, log::Level::Warn => MLLogLevel::Warning, log::Level::Info => MLLogLevel::Info, log::Level::Debug => MLLogLevel::Debug, log::Level::Trace => MLLogLevel::Verbose, }; let mut msg = SmallVec::<[u8; 128]>::new(); write!(msg, "{}\0", record.args()).unwrap(); unsafe { MLLoggingLog(lvl, self.tag.as_ptr(), &msg[0] as *const _ as _); } } fn flush(&self) {} } impl MagicLeapLogger { pub fn new(tag: CString, level_filter: log::LevelFilter) -> Self { MagicLeapLogger { tag, level_filter } } } // Impl pathfinder traits for c-api types impl From for Transform3DF32 { fn from(mat: MLTransform) -> Self { Transform3DF32::from(mat.rotation) .pre_mul(&Transform3DF32::from(mat.position)) } } impl From for Transform3DF32 { fn from(v: MLVec3f) -> Self { Transform3DF32::from_translation(v.x, v.y, v.z) } } impl From for RectF32 { fn from(r: MLRectf) -> Self { RectF32::new(Point2DF32::new(r.x, r.y), Point2DF32::new(r.w, r.h)) } } impl From for RectI32 { fn from(r: MLRectf) -> Self { RectF32::from(r).to_i32() } } impl From for Transform3DF32 { fn from(q: MLQuaternionf) -> Self { Transform3DF32::from_rotation_quaternion(F32x4::new(q.x, q.y, q.z, q.w)) } } impl From for Transform3DF32 { fn from(mat: MLMat4f) -> Self { let a = mat.matrix_colmajor; Transform3DF32::row_major(a[0], a[4], a[8], a[12], a[1], a[5], a[9], a[13], a[2], a[6], a[10], a[14], a[3], a[7], a[11], a[15]) } }