diff --git a/demo/common/src/renderer.rs b/demo/common/src/renderer.rs index b2205f8d..95d219c0 100644 --- a/demo/common/src/renderer.rs +++ b/demo/common/src/renderer.rs @@ -296,7 +296,9 @@ impl DemoApp where W: Window { pub fn take_raster_screenshot(&mut self, path: PathBuf) { let drawable_size = self.window_size.device_size(); let viewport = RectI::new(Vector2I::default(), drawable_size); - let pixels = match self.renderer.device.read_pixels(&RenderTarget::Default, viewport) { + let texture_data_receiver = + self.renderer.device.read_pixels(&RenderTarget::Default, viewport); + let pixels = match self.renderer.device.recv_texture_data(&texture_data_receiver) { TextureData::U8(pixels) => pixels, _ => panic!("Unexpected pixel format for default framebuffer!"), }; diff --git a/gl/src/lib.rs b/gl/src/lib.rs index 1e8f5d4a..3f4c7885 100644 --- a/gl/src/lib.rs +++ b/gl/src/lib.rs @@ -13,13 +13,14 @@ #[macro_use] extern crate log; -use gl::types::{GLboolean, GLchar, GLenum, GLfloat, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid}; +use gl::types::{GLboolean, GLchar, GLenum, GLfloat, GLint, GLsizei, GLsizeiptr, GLsync}; +use gl::types::{GLuint, GLvoid}; use half::f16; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::vector::Vector2I; use pathfinder_gpu::resources::ResourceLoader; -use pathfinder_gpu::{RenderTarget, BlendFunc, BlendOp, BufferData, BufferTarget, BufferUploadMode}; -use pathfinder_gpu::{ClearOps, DepthFunc, Device, Primitive, RenderOptions, RenderState}; +use pathfinder_gpu::{BlendFunc, BlendOp, BufferData, BufferTarget, BufferUploadMode, ClearOps}; +use pathfinder_gpu::{DepthFunc, Device, Primitive, RenderOptions, RenderState, RenderTarget}; use pathfinder_gpu::{ShaderKind, StencilFunc, TextureData, TextureDataRef, TextureFormat}; use pathfinder_gpu::{UniformData, VertexAttrClass, VertexAttrDescriptor, VertexAttrType}; use pathfinder_simd::default::F32x4; @@ -230,6 +231,7 @@ impl Device for GLDevice { type Program = GLProgram; type Shader = GLShader; type Texture = GLTexture; + type TextureDataReceiver = GLTextureDataReceiver; type TimerQuery = GLTimerQuery; type Uniform = GLUniform; type VertexArray = GLVertexArray; @@ -508,58 +510,33 @@ impl Device for GLDevice { self.set_texture_parameters(texture); } - fn read_pixels(&self, render_target: &RenderTarget, viewport: RectI) -> TextureData { + fn read_pixels(&self, render_target: &RenderTarget, viewport: RectI) + -> GLTextureDataReceiver { let (origin, size) = (viewport.origin(), viewport.size()); let format = self.render_target_format(render_target); self.bind_render_target(render_target); + let byte_size = size.x() as usize * size.y() as usize * format.bytes_per_pixel() as usize; - match format { - TextureFormat::R8 | TextureFormat::RGBA8 => { - let channels = format.channels(); - let mut pixels = vec![0; size.x() as usize * size.y() as usize * channels]; - unsafe { - gl::ReadPixels(origin.x(), - origin.y(), - size.x() as GLsizei, - size.y() as GLsizei, - format.gl_format(), - format.gl_type(), - pixels.as_mut_ptr() as *mut GLvoid); ck(); - } - flip_y(&mut pixels, size, channels); - TextureData::U8(pixels) - } - TextureFormat::R16F | TextureFormat::RGBA16F => { - let channels = format.channels(); - let mut pixels = - vec![f16::default(); size.x() as usize * size.y() as usize * channels]; - unsafe { - gl::ReadPixels(origin.x(), - origin.y(), - size.x() as GLsizei, - size.y() as GLsizei, - format.gl_format(), - format.gl_type(), - pixels.as_mut_ptr() as *mut GLvoid); ck(); - } - flip_y(&mut pixels, size, channels); - TextureData::F16(pixels) - } - TextureFormat::RGBA32F => { - let channels = format.channels(); - let mut pixels = vec![0.0; size.x() as usize * size.y() as usize * channels]; - unsafe { - gl::ReadPixels(origin.x(), - origin.y(), - size.x() as GLsizei, - size.y() as GLsizei, - format.gl_format(), - format.gl_type(), - pixels.as_mut_ptr() as *mut GLvoid); ck(); - } - flip_y(&mut pixels, size, channels); - TextureData::F32(pixels) - } + unsafe { + let mut gl_pixel_buffer = 0; + gl::GenBuffers(1, &mut gl_pixel_buffer); ck(); + gl::BindBuffer(gl::PIXEL_PACK_BUFFER, gl_pixel_buffer); ck(); + gl::BufferData(gl::PIXEL_PACK_BUFFER, + byte_size as GLsizeiptr, + ptr::null(), + gl::STATIC_READ); ck(); + + gl::ReadPixels(origin.x(), + origin.y(), + size.x() as GLsizei, + size.y() as GLsizei, + format.gl_format(), + format.gl_type(), + 0 as *mut GLvoid); ck(); + + let gl_sync = gl::FenceSync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0); + + GLTextureDataReceiver { gl_pixel_buffer, gl_sync, size, format } } } @@ -631,17 +608,46 @@ impl Device for GLDevice { } } - #[inline] - fn get_timer_query(&self, query: &Self::TimerQuery) -> Option { + fn try_recv_timer_query(&self, query: &Self::TimerQuery) -> Option { unsafe { let mut result = 0; gl::GetQueryObjectiv(query.gl_query, gl::QUERY_RESULT_AVAILABLE, &mut result); ck(); if result == gl::FALSE as GLint { - return None; + None + } else { + Some(self.recv_timer_query(query)) } + } + } + + fn recv_timer_query(&self, query: &Self::TimerQuery) -> Duration { + unsafe { let mut result = 0; gl::GetQueryObjectui64v(query.gl_query, gl::QUERY_RESULT, &mut result); ck(); - Some(Duration::from_nanos(result)) + Duration::from_nanos(result) + } + } + + fn try_recv_texture_data(&self, receiver: &Self::TextureDataReceiver) -> Option { + unsafe { + let result = gl::ClientWaitSync(receiver.gl_sync, + gl::SYNC_FLUSH_COMMANDS_BIT, + 0); ck(); + if result == gl::TIMEOUT_EXPIRED || result == gl::WAIT_FAILED { + None + } else { + Some(self.get_texture_data(receiver)) + } + } + } + + fn recv_texture_data(&self, receiver: &Self::TextureDataReceiver) -> TextureData { + unsafe { + let result = gl::ClientWaitSync(receiver.gl_sync, + gl::SYNC_FLUSH_COMMANDS_BIT, + !0); ck(); + debug_assert!(result != gl::TIMEOUT_EXPIRED && result != gl::WAIT_FAILED); + self.get_texture_data(receiver) } } @@ -782,6 +788,52 @@ impl GLDevice { } } } + + fn get_texture_data(&self, receiver: &GLTextureDataReceiver) -> TextureData { + unsafe { + let (format, size) = (receiver.format, receiver.size); + let channels = format.channels(); + let (mut texture_data, texture_data_ptr, texture_data_len); + match format { + TextureFormat::R8 | TextureFormat::RGBA8 => { + let mut pixels: Vec = + vec![0; size.x() as usize * size.y() as usize * channels]; + texture_data_ptr = pixels.as_mut_ptr(); + texture_data_len = pixels.len() * mem::size_of::(); + texture_data = TextureData::U8(pixels); + } + TextureFormat::R16F | TextureFormat::RGBA16F => { + let mut pixels: Vec = + vec![f16::default(); size.x() as usize * size.y() as usize * channels]; + texture_data_ptr = pixels.as_mut_ptr() as *mut u8; + texture_data_len = pixels.len() * mem::size_of::(); + texture_data = TextureData::F16(pixels); + } + TextureFormat::RGBA32F => { + let mut pixels = vec![0.0; size.x() as usize * size.y() as usize * channels]; + texture_data_ptr = pixels.as_mut_ptr() as *mut u8; + texture_data_len = pixels.len() * mem::size_of::(); + texture_data = TextureData::F32(pixels); + } + } + + gl::BindBuffer(gl::PIXEL_PACK_BUFFER, receiver.gl_pixel_buffer); ck(); + gl::GetBufferSubData(gl::PIXEL_PACK_BUFFER, + 0, + texture_data_len as GLsizeiptr, + texture_data_ptr as *mut GLvoid); ck(); + gl::BindBuffer(gl::PIXEL_PACK_BUFFER, 0); ck(); + + match texture_data { + TextureData::U8(ref mut pixels) => flip_y(pixels, size, channels), + TextureData::U16(ref mut pixels) => flip_y(pixels, size, channels), + TextureData::F16(ref mut pixels) => flip_y(pixels, size, channels), + TextureData::F32(ref mut pixels) => flip_y(pixels, size, channels), + } + + texture_data + } + } } pub struct GLVertexArray { @@ -1030,6 +1082,22 @@ impl VertexAttrTypeExt for VertexAttrType { } } +pub struct GLTextureDataReceiver { + gl_pixel_buffer: GLuint, + gl_sync: GLsync, + size: Vector2I, + format: TextureFormat, +} + +impl Drop for GLTextureDataReceiver { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &mut self.gl_pixel_buffer); ck(); + gl::DeleteSync(self.gl_sync); ck(); + } + } +} + /// The version/dialect of OpenGL we should render with. #[derive(Clone, Copy)] #[repr(u32)] diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index b791c07c..b0c42665 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -29,6 +29,7 @@ pub trait Device: Sized { type Program; type Shader; type Texture; + type TextureDataReceiver; type TimerQuery; type Uniform; type VertexArray; @@ -71,7 +72,8 @@ pub trait Device: Sized { fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture; fn texture_size(&self, texture: &Self::Texture) -> Vector2I; fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef); - fn read_pixels(&self, target: &RenderTarget, viewport: RectI) -> TextureData; + fn read_pixels(&self, target: &RenderTarget, viewport: RectI) + -> Self::TextureDataReceiver; fn begin_commands(&self); fn end_commands(&self); fn draw_arrays(&self, index_count: u32, render_state: &RenderState); @@ -83,7 +85,10 @@ pub trait Device: Sized { fn create_timer_query(&self) -> Self::TimerQuery; fn begin_timer_query(&self, query: &Self::TimerQuery); fn end_timer_query(&self, query: &Self::TimerQuery); - fn get_timer_query(&self, query: &Self::TimerQuery) -> Option; + fn try_recv_timer_query(&self, query: &Self::TimerQuery) -> Option; + fn recv_timer_query(&self, query: &Self::TimerQuery) -> Duration; + fn try_recv_texture_data(&self, receiver: &Self::TextureDataReceiver) -> Option; + fn recv_texture_data(&self, receiver: &Self::TextureDataReceiver) -> TextureData; fn create_texture_from_png(&self, resources: &dyn ResourceLoader, name: &str) -> Self::Texture { let data = resources.slurp(&format!("textures/{}.png", name)).unwrap(); diff --git a/metal/src/lib.rs b/metal/src/lib.rs index 6124bcbd..2d82cf01 100644 --- a/metal/src/lib.rs +++ b/metal/src/lib.rs @@ -54,7 +54,7 @@ use std::mem; use std::ptr; use std::rc::Rc; use std::slice; -use std::sync::Arc; +use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use std::time::{Duration, Instant}; const FIRST_VERTEX_BUFFER_INDEX: u64 = 1; @@ -148,10 +148,34 @@ pub struct MetalTexture { dirty: Cell, } -pub struct MetalTimerQuery { +#[derive(Clone)] +pub struct MetalTextureDataReceiver(Arc); + +struct MetalTextureDataReceiverInfo { + mutex: Mutex, + cond: Condvar, + texture: Texture, + viewport: RectI, +} + +enum MetalTextureDataReceiverState { + Pending, + Downloaded(TextureData), + Finished, +} + +#[derive(Clone)] +pub struct MetalTimerQuery(Arc); + +struct MetalTimerQueryInfo { + mutex: Mutex, + cond: Condvar, event_value: u64, - start_time: Cell>, - end_time: Cell>, +} + +struct MetalTimerQueryData { + start_time: Option, + end_time: Option, } #[derive(Clone)] @@ -184,7 +208,8 @@ impl Device for MetalDevice { type Program = MetalProgram; type Shader = MetalShader; type Texture = MetalTexture; - type TimerQuery = Arc; + type TextureDataReceiver = MetalTextureDataReceiver; + type TimerQuery = MetalTimerQuery; type Uniform = MetalUniform; type VertexArray = MetalVertexArray; type VertexAttr = VertexAttribute; @@ -439,46 +464,24 @@ impl Device for MetalDevice { texture.dirty.set(true); } - fn read_pixels(&self, target: &RenderTarget, viewport: RectI) -> TextureData { + fn read_pixels(&self, target: &RenderTarget, viewport: RectI) + -> MetalTextureDataReceiver { let texture = self.render_target_color_texture(target); - self.synchronize_texture(&texture); + let texture_data_receiver = + MetalTextureDataReceiver(Arc::new(MetalTextureDataReceiverInfo { + mutex: Mutex::new(MetalTextureDataReceiverState::Pending), + cond: Condvar::new(), + texture, + viewport, + })); - let (origin, size) = (viewport.origin(), viewport.size()); - let metal_origin = MTLOrigin { x: origin.x() as u64, y: origin.y() as u64, z: 0 }; - let metal_size = MTLSize { width: size.x() as u64, height: size.y() as u64, depth: 1 }; - let metal_region = MTLRegion { origin: metal_origin, size: metal_size }; + let texture_data_receiver_for_block = texture_data_receiver.clone(); + let block = ConcreteBlock::new(move |_| { + texture_data_receiver_for_block.download(); + }); - let format = self.texture_format(&texture) - .expect("Unexpected framebuffer texture format!"); - match format { - TextureFormat::R8 | TextureFormat::RGBA8 => { - let channels = format.channels(); - let stride = size.x() as usize * channels; - let mut pixels = vec![0; stride * size.y() as usize]; - texture.get_bytes(pixels.as_mut_ptr() as *mut _, metal_region, 0, stride as u64); - TextureData::U8(pixels) - } - TextureFormat::R16F | TextureFormat::RGBA16F => { - let channels = format.channels(); - let stride = size.x() as usize * channels; - let mut pixels = vec![f16::default(); stride * size.y() as usize]; - texture.get_bytes(pixels.as_mut_ptr() as *mut _, - metal_region, - 0, - stride as u64 * 2); - TextureData::F16(pixels) - } - TextureFormat::RGBA32F => { - let channels = format.channels(); - let stride = size.x() as usize * channels; - let mut pixels = vec![0.0; stride * size.y() as usize]; - texture.get_bytes(pixels.as_mut_ptr() as *mut _, - metal_region, - 0, - stride as u64 * 4); - TextureData::F32(pixels) - } - } + self.synchronize_texture(&texture_data_receiver.0.texture, block.copy()); + texture_data_receiver } fn begin_commands(&self) { @@ -534,23 +537,26 @@ impl Device for MetalDevice { encoder.end_encoding(); } - fn create_timer_query(&self) -> Arc { + fn create_timer_query(&self) -> MetalTimerQuery { let event_value = self.next_timer_query_event_value.get(); self.next_timer_query_event_value.set(event_value + 2); - let query = Arc::new(MetalTimerQuery { + let query = MetalTimerQuery(Arc::new(MetalTimerQueryInfo { event_value, - start_time: Cell::new(None), - end_time: Cell::new(None), - }); + mutex: Mutex::new(MetalTimerQueryData { start_time: None, end_time: None }), + cond: Condvar::new(), + })); let captured_query = query.clone(); let start_block = ConcreteBlock::new(move |_: *mut Object, _: u64| { - captured_query.start_time.set(Some(Instant::now())) + let mut guard = captured_query.0.mutex.lock().unwrap(); + guard.start_time = Some(Instant::now()); }); let captured_query = query.clone(); let end_block = ConcreteBlock::new(move |_: *mut Object, _: u64| { - captured_query.end_time.set(Some(Instant::now())) + let mut guard = captured_query.0.mutex.lock().unwrap(); + guard.end_time = Some(Instant::now()); + captured_query.0.cond.notify_all(); }); self.shared_event.notify_listener_at_value(&self.shared_event_listener, event_value, @@ -562,26 +568,49 @@ impl Device for MetalDevice { query } - fn begin_timer_query(&self, query: &Arc) { + fn begin_timer_query(&self, query: &MetalTimerQuery) { self.command_buffers .borrow_mut() .last() .unwrap() - .encode_signal_event(&self.shared_event, query.event_value); + .encode_signal_event(&self.shared_event, query.0.event_value); } - fn end_timer_query(&self, query: &Arc) { + fn end_timer_query(&self, query: &MetalTimerQuery) { self.command_buffers .borrow_mut() .last() .unwrap() - .encode_signal_event(&self.shared_event, query.event_value + 1); + .encode_signal_event(&self.shared_event, query.0.event_value + 1); } - fn get_timer_query(&self, query: &Arc) -> Option { - match (query.start_time.get(), query.end_time.get()) { - (Some(start_time), Some(end_time)) => Some(end_time - start_time), - _ => None, + fn try_recv_timer_query(&self, query: &MetalTimerQuery) -> Option { + try_recv_timer_query_with_guard(&mut query.0.mutex.lock().unwrap()) + } + + fn recv_timer_query(&self, query: &MetalTimerQuery) -> Duration { + let mut guard = query.0.mutex.lock().unwrap(); + loop { + let duration = try_recv_timer_query_with_guard(&mut guard); + if let Some(duration) = duration { + return duration + } + guard = query.0.cond.wait(guard).unwrap(); + } + } + + fn try_recv_texture_data(&self, receiver: &MetalTextureDataReceiver) -> Option { + try_recv_texture_data_with_guard(&mut receiver.0.mutex.lock().unwrap()) + } + + fn recv_texture_data(&self, receiver: &MetalTextureDataReceiver) -> TextureData { + let mut guard = receiver.0.mutex.lock().unwrap(); + loop { + let texture_data = try_recv_texture_data_with_guard(&mut guard); + if let Some(texture_data) = texture_data { + return texture_data + } + guard = receiver.0.cond.wait(guard).unwrap(); } } @@ -1096,12 +1125,7 @@ impl MetalDevice { } fn texture_format(&self, texture: &Texture) -> Option { - match texture.pixel_format() { - MTLPixelFormat::R8Unorm => Some(TextureFormat::R8), - MTLPixelFormat::R16Float => Some(TextureFormat::R16F), - MTLPixelFormat::RGBA8Unorm => Some(TextureFormat::RGBA8), - _ => None, - } + TextureFormat::from_metal_pixel_format(texture.pixel_format()) } fn set_viewport(&self, encoder: &RenderCommandEncoderRef, viewport: &RectI) { @@ -1115,11 +1139,13 @@ impl MetalDevice { }) } - fn synchronize_texture(&self, texture: &Texture) { - { + fn synchronize_texture(&self, texture: &Texture, block: RcBlock<(*mut Object,), ()>) { + unsafe { let command_buffers = self.command_buffers.borrow(); - let encoder = command_buffers.last().unwrap().new_blit_command_encoder(); + let command_buffer = command_buffers.last().unwrap(); + let encoder = command_buffer.new_blit_command_encoder(); encoder.synchronize_resource(&texture); + let () = msg_send![*command_buffer, addCompletedHandler:&*block]; encoder.end_encoding(); } @@ -1218,6 +1244,98 @@ impl UniformDataExt for UniformData { } } +trait TextureFormatExt: Sized { + fn from_metal_pixel_format(metal_pixel_format: MTLPixelFormat) -> Option; +} + +impl TextureFormatExt for TextureFormat { + fn from_metal_pixel_format(metal_pixel_format: MTLPixelFormat) -> Option { + match metal_pixel_format { + MTLPixelFormat::R8Unorm => Some(TextureFormat::R8), + MTLPixelFormat::R16Float => Some(TextureFormat::R16F), + MTLPixelFormat::RGBA8Unorm => Some(TextureFormat::RGBA8), + MTLPixelFormat::BGRA8Unorm => { + // FIXME(pcwalton): This is wrong! But it prevents a crash for now. + Some(TextureFormat::RGBA8) + } + _ => None, + } + } +} + +// Synchronization helpers + +fn try_recv_timer_query_with_guard(guard: &mut MutexGuard) + -> Option { + match (guard.start_time, guard.end_time) { + (Some(start_time), Some(end_time)) => Some(end_time - start_time), + _ => None, + } +} + +impl MetalTextureDataReceiver { + fn download(&self) { + let (origin, size) = (self.0.viewport.origin(), self.0.viewport.size()); + let metal_origin = MTLOrigin { x: origin.x() as u64, y: origin.y() as u64, z: 0 }; + let metal_size = MTLSize { width: size.x() as u64, height: size.y() as u64, depth: 1 }; + let metal_region = MTLRegion { origin: metal_origin, size: metal_size }; + + let format = TextureFormat::from_metal_pixel_format(self.0.texture.pixel_format()); + let format = format.expect("Unexpected framebuffer texture format!"); + + let texture_data = match format { + TextureFormat::R8 | TextureFormat::RGBA8 => { + let channels = format.channels(); + let stride = size.x() as usize * channels; + let mut pixels = vec![0; stride * size.y() as usize]; + self.0.texture.get_bytes(pixels.as_mut_ptr() as *mut _, + metal_region, + 0, + stride as u64); + TextureData::U8(pixels) + } + TextureFormat::R16F | TextureFormat::RGBA16F => { + let channels = format.channels(); + let stride = size.x() as usize * channels; + let mut pixels = vec![f16::default(); stride * size.y() as usize]; + self.0.texture.get_bytes(pixels.as_mut_ptr() as *mut _, + metal_region, + 0, + stride as u64 * 2); + TextureData::F16(pixels) + } + TextureFormat::RGBA32F => { + let channels = format.channels(); + let stride = size.x() as usize * channels; + let mut pixels = vec![0.0; stride * size.y() as usize]; + self.0.texture.get_bytes(pixels.as_mut_ptr() as *mut _, + metal_region, + 0, + stride as u64 * 4); + TextureData::F32(pixels) + } + }; + + let mut guard = self.0.mutex.lock().unwrap(); + *guard = MetalTextureDataReceiverState::Downloaded(texture_data); + self.0.cond.notify_all(); + } +} + +fn try_recv_texture_data_with_guard(guard: &mut MutexGuard) + -> Option { + match **guard { + MetalTextureDataReceiverState::Pending | MetalTextureDataReceiverState::Finished => { + return None + } + MetalTextureDataReceiverState::Downloaded(_) => {} + } + match mem::replace(&mut **guard, MetalTextureDataReceiverState::Finished) { + MetalTextureDataReceiverState::Downloaded(texture_data) => Some(texture_data), + _ => unreachable!(), + } +} + // Extra structs missing from `metal-rs` bitflags! { diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 2d4a0513..7965c5aa 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -296,7 +296,7 @@ where // Accumulate stage-0 time. let mut total_stage_0_time = Duration::new(0, 0); for timer_query in &timers.stage_0 { - match self.device.get_timer_query(timer_query) { + match self.device.try_recv_timer_query(timer_query) { None => return None, Some(stage_0_time) => total_stage_0_time += stage_0_time, } @@ -305,7 +305,7 @@ where // Get stage-1 time. let stage_1_time = { let stage_1_timer_query = timers.stage_1.as_ref().unwrap(); - match self.device.get_timer_query(stage_1_timer_query) { + match self.device.try_recv_timer_query(stage_1_timer_query) { None => return None, Some(query) => query, }