From 0fa3b5446cbbaeb93744a122d70eef3f152ed98c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 10 Apr 2023 15:12:56 -0700 Subject: [PATCH 1/2] WIP: Attempt to use `IOSurface` on macOS It looks like `BytesPerRow` (the stride) has some kind of alignment requirement (testing on M1), that doesn't let us just use the width. So for this to work, softbuffer needs to expose the stride, and the user needs to deal with that... This doesn't seem to be showing the right thing even when width matches stride. Not sure why. --- Cargo.toml | 2 + src/cg/buffer.rs | 93 ++++++++++++++++++++++++++++++++++++++++ src/{cg.rs => cg/mod.rs} | 43 ++++++------------- 3 files changed, 107 insertions(+), 31 deletions(-) create mode 100644 src/cg/buffer.rs rename src/{cg.rs => cg/mod.rs} (75%) 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/src/cg/buffer.rs b/src/cg/buffer.rs new file mode 100644 index 00000000..4949bdd3 --- /dev/null +++ b/src/cg/buffer.rs @@ -0,0 +1,93 @@ +use core_foundation::{ + base::TCFType, 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 u8, + pixels: 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(), + ), + // TODO: Can we always use stride = width? Is it efficient? + /* + ( + CFString::wrap_under_get_rule(kIOSurfaceBytesPerRow), + CFNumber::from(width).as_CFType(), + ), + */ + ( + CFString::wrap_under_get_rule(kIOSurfacePixelFormat), + CFNumber::from(i32::from_be_bytes([b'B', b'G', b'R', b'A'])).as_CFType(), + ), + ]) + }; + let io_surface = io_surface::new(&properties); + let ptr = unsafe { IOSurfaceGetBaseAddress(io_surface.obj) }; + dbg!(width); + dbg!(unsafe { IOSurfaceGetBytesPerRow(io_surface.obj) } / 4); + let pixels = width as usize * height as usize; + Self { + io_surface, + ptr, + pixels, + } + } + + pub fn as_ptr(&self) -> IOSurfaceRef { + self.io_surface.obj + } + + 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 as *mut u32, self.pixels) } + } + + #[inline] + pub unsafe fn pixels_mut(&self) -> &mut [u32] { + unsafe { slice::from_raw_parts_mut(self.ptr as *mut u32, self.pixels) } + } +} 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..2e455090 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,30 @@ 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, - ); - + 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 +89,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(); From 8c1ee39210278325d78a1518f02d0b408c8c4822 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 10 Apr 2023 17:18:17 -0700 Subject: [PATCH 2/2] Make winit example work with IOSurface, using alpha 255, stride --- examples/animation.rs | 3 ++- examples/winit.rs | 17 +++++++++-------- src/cg/buffer.rs | 35 +++++++++++++++++------------------ src/cg/mod.rs | 5 +++++ src/lib.rs | 15 +++++++++++++++ 5 files changed, 48 insertions(+), 27 deletions(-) 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 index 4949bdd3..85427ccb 100644 --- a/src/cg/buffer.rs +++ b/src/cg/buffer.rs @@ -1,5 +1,5 @@ use core_foundation::{ - base::TCFType, dictionary::CFDictionary, number::CFNumber, string::CFString, + base::TCFType, boolean::CFBoolean, dictionary::CFDictionary, number::CFNumber, string::CFString, }; use io_surface::{ kIOSurfaceBytesPerElement, kIOSurfaceBytesPerRow, kIOSurfaceHeight, kIOSurfacePixelFormat, @@ -17,8 +17,9 @@ extern "C" { pub struct Buffer { io_surface: IOSurface, - ptr: *mut u8, - pixels: usize, + ptr: *mut u32, + stride: usize, + len: usize, } impl Buffer { @@ -37,28 +38,21 @@ impl Buffer { CFString::wrap_under_get_rule(kIOSurfaceBytesPerElement), CFNumber::from(4).as_CFType(), ), - // TODO: Can we always use stride = width? Is it efficient? - /* - ( - CFString::wrap_under_get_rule(kIOSurfaceBytesPerRow), - CFNumber::from(width).as_CFType(), - ), - */ ( CFString::wrap_under_get_rule(kIOSurfacePixelFormat), - CFNumber::from(i32::from_be_bytes([b'B', b'G', b'R', b'A'])).as_CFType(), + CFNumber::from(i32::from_be_bytes(*b"BGRA")).as_CFType(), ), ]) }; let io_surface = io_surface::new(&properties); - let ptr = unsafe { IOSurfaceGetBaseAddress(io_surface.obj) }; - dbg!(width); - dbg!(unsafe { IOSurfaceGetBytesPerRow(io_surface.obj) } / 4); - let pixels = width as usize * height as usize; + 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, - pixels, + stride, + len, } } @@ -66,6 +60,11 @@ impl Buffer { self.io_surface.obj } + #[inline] + pub fn stride(&self) -> usize { + self.stride + } + pub unsafe fn lock(&mut self) { let mut seed = 0; unsafe { @@ -83,11 +82,11 @@ impl Buffer { // TODO: We can assume alignment, right? #[inline] pub unsafe fn pixels_ref(&self) -> &[u32] { - unsafe { slice::from_raw_parts(self.ptr as *mut u32, self.pixels) } + 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 as *mut u32, self.pixels) } + unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } } } diff --git a/src/cg/mod.rs b/src/cg/mod.rs index 2e455090..9cc4afbb 100644 --- a/src/cg/mod.rs +++ b/src/cg/mod.rs @@ -81,6 +81,11 @@ impl<'a> BufferImpl<'a> { unsafe { self.buffer.pixels_mut() } } + #[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 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