diff --git a/Cargo.toml b/Cargo.toml index c6da900a..38a77a0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,10 @@ features = ["Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging", "Win32_Foundat [target.'cfg(target_os = "macos")'.dependencies] bytemuck = { version = "1.12.3", features = ["extern_crate_alloc"] } cocoa = "0.24.0" +core-foundation = "0.9.3" core-graphics = "0.22.3" foreign-types = "0.3.0" +io-surface = "0.15.1" objc = "0.2.7" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/animation.rs b/examples/animation.rs index f729a395..20746ab6 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -29,6 +29,7 @@ fn main() { let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); let mut old_size = (0, 0); + // TODO: need to pre-render with right stride? let mut frames = pre_render_frames(0, 0); let start = Instant::now(); @@ -89,7 +90,7 @@ fn pre_render_frames(width: usize, height: usize) -> Vec> { let blue = ((((y - elapsed).cos() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); - blue | (green << 8) | (red << 16) + blue | (green << 8) | (red << 16) | (255 << 24) }) .collect::>() }; diff --git a/examples/winit.rs b/examples/winit.rs index 935ef3f8..d145de49 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -42,14 +42,15 @@ fn main() { .unwrap(); let mut buffer = surface.buffer_mut().unwrap(); - for index in 0..(width * height) { - let y = index / width; - let x = index % width; - let red = x % 255; - let green = y % 255; - let blue = (x * y) % 255; - - buffer[index as usize] = blue | (green << 8) | (red << 16); + let stride = buffer.stride(); + for y in 0..height { + for x in 0..width { + let red = x % 255; + let green = y % 255; + let blue = (x * y) % 255; + let index = y as usize * stride + x as usize; + buffer[index] = blue | (green << 8) | (red << 16) | (255 << 24); + } } buffer.present().unwrap(); diff --git a/src/cg/buffer.rs b/src/cg/buffer.rs new file mode 100644 index 00000000..85427ccb --- /dev/null +++ b/src/cg/buffer.rs @@ -0,0 +1,92 @@ +use core_foundation::{ + base::TCFType, boolean::CFBoolean, dictionary::CFDictionary, number::CFNumber, string::CFString, +}; +use io_surface::{ + kIOSurfaceBytesPerElement, kIOSurfaceBytesPerRow, kIOSurfaceHeight, kIOSurfacePixelFormat, + kIOSurfaceWidth, IOSurface, IOSurfaceRef, +}; +use std::{ffi::c_int, slice}; + +#[link(name = "IOSurface", kind = "framework")] +extern "C" { + fn IOSurfaceGetBaseAddress(buffer: IOSurfaceRef) -> *mut u8; + fn IOSurfaceGetBytesPerRow(buffer: IOSurfaceRef) -> usize; + fn IOSurfaceLock(buffer: IOSurfaceRef, options: u32, seed: *mut u32) -> c_int; + fn IOSurfaceUnlock(buffer: IOSurfaceRef, options: u32, seed: *mut u32) -> c_int; +} + +pub struct Buffer { + io_surface: IOSurface, + ptr: *mut u32, + stride: usize, + len: usize, +} + +impl Buffer { + pub fn new(width: i32, height: i32) -> Self { + let properties = unsafe { + CFDictionary::from_CFType_pairs(&[ + ( + CFString::wrap_under_get_rule(kIOSurfaceWidth), + CFNumber::from(width).as_CFType(), + ), + ( + CFString::wrap_under_get_rule(kIOSurfaceHeight), + CFNumber::from(height).as_CFType(), + ), + ( + CFString::wrap_under_get_rule(kIOSurfaceBytesPerElement), + CFNumber::from(4).as_CFType(), + ), + ( + CFString::wrap_under_get_rule(kIOSurfacePixelFormat), + CFNumber::from(i32::from_be_bytes(*b"BGRA")).as_CFType(), + ), + ]) + }; + let io_surface = io_surface::new(&properties); + let ptr = unsafe { IOSurfaceGetBaseAddress(io_surface.obj) } as *mut u32; + let stride = unsafe { IOSurfaceGetBytesPerRow(io_surface.obj) } / 4; + let len = stride * height as usize; + Self { + io_surface, + ptr, + stride, + len, + } + } + + pub fn as_ptr(&self) -> IOSurfaceRef { + self.io_surface.obj + } + + #[inline] + pub fn stride(&self) -> usize { + self.stride + } + + pub unsafe fn lock(&mut self) { + let mut seed = 0; + unsafe { + IOSurfaceLock(self.io_surface.obj, 0, &mut seed); + } + } + + pub unsafe fn unlock(&mut self) { + let mut seed = 0; + unsafe { + IOSurfaceUnlock(self.io_surface.obj, 0, &mut seed); + } + } + + // TODO: We can assume alignment, right? + #[inline] + pub unsafe fn pixels_ref(&self) -> &[u32] { + unsafe { slice::from_raw_parts(self.ptr, self.len) } + } + + #[inline] + pub unsafe fn pixels_mut(&self) -> &mut [u32] { + unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } + } +} diff --git a/src/cg.rs b/src/cg/mod.rs similarity index 75% rename from src/cg.rs rename to src/cg/mod.rs index fce628d6..9cc4afbb 100644 --- a/src/cg.rs +++ b/src/cg/mod.rs @@ -13,15 +13,9 @@ use cocoa::quartzcore::{transaction, CALayer, ContentsGravity}; use foreign_types::ForeignType; use std::num::NonZeroU32; -use std::sync::Arc; -struct Buffer(Vec); - -impl AsRef<[u8]> for Buffer { - fn as_ref(&self) -> &[u8] { - bytemuck::cast_slice(&self.0) - } -} +mod buffer; +use buffer::Buffer; pub struct CGImpl { layer: CALayer, @@ -64,44 +58,35 @@ impl CGImpl { } pub fn buffer_mut(&mut self) -> Result { - Ok(BufferImpl { - buffer: vec![0; self.width as usize * self.height as usize], - imp: self, - }) + // TODO conversion + let mut buffer = Buffer::new(self.width as i32, self.height as i32); + unsafe { buffer.lock() }; + Ok(BufferImpl { buffer, imp: self }) } } pub struct BufferImpl<'a> { imp: &'a mut CGImpl, - buffer: Vec, + buffer: Buffer, } impl<'a> BufferImpl<'a> { #[inline] pub fn pixels(&self) -> &[u32] { - &self.buffer + unsafe { self.buffer.pixels_ref() } } #[inline] pub fn pixels_mut(&mut self) -> &mut [u32] { - &mut self.buffer + unsafe { self.buffer.pixels_mut() } } - pub fn present(self) -> Result<(), SoftBufferError> { - let data_provider = CGDataProvider::from_buffer(Arc::new(Buffer(self.buffer))); - let image = CGImage::new( - self.imp.width as usize, - self.imp.height as usize, - 8, - 32, - (self.imp.width * 4) as usize, - &self.imp.color_space, - kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, - &data_provider, - false, - kCGRenderingIntentDefault, - ); + #[inline] + pub fn stride(&self) -> usize { + self.buffer.stride() + } + pub fn present(mut self) -> Result<(), SoftBufferError> { // The CALayer has a default action associated with a change in the layer contents, causing // a quarter second fade transition to happen every time a new buffer is applied. This can // be mitigated by wrapping the operation in a transaction and disabling all actions. @@ -109,10 +94,11 @@ impl<'a> BufferImpl<'a> { transaction::set_disable_actions(true); unsafe { + self.buffer.unlock(); self.imp .layer .set_contents_scale(self.imp.window.backingScaleFactor()); - self.imp.layer.set_contents(image.as_ptr() as id); + self.imp.layer.set_contents(self.buffer.as_ptr() as id); }; transaction::commit(); diff --git a/src/lib.rs b/src/lib.rs index f7dadf63..5387ba9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,6 +124,16 @@ macro_rules! make_dispatch { } } + #[inline] + pub fn stride(&self) -> usize { + match self { + $( + $(#[$attr])* + Self::$name(inner) => inner.stride(), + )* + } + } + pub fn present(self) -> Result<(), SoftBufferError> { match self { $( @@ -355,6 +365,11 @@ pub struct Buffer<'a> { } impl<'a> Buffer<'a> { + #[inline] + pub fn stride(&self) -> usize { + self.buffer_impl.stride() + } + /// Presents buffer to the window. /// /// # Platform dependent behavior