diff --git a/Cargo.toml b/Cargo.toml index c6da900a..73e596b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,9 @@ 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..933f3329 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -78,10 +78,11 @@ fn pre_render_frames(width: usize, height: usize) -> Vec> { let render = |frame_id| { let elapsed = ((frame_id as f64) / (60.0)) * 2.0 * PI; - (0..(width * height)) - .map(|index| { - let y = ((index / width) as f64) / (height as f64); - let x = ((index % width) as f64) / (width as f64); + let coords = (0..height).flat_map(|x| (0..width).map(move |y| (x, y))); + coords + .map(|(x, y)| { + let y = (y as f64) / (height as f64); + let x = (x as f64) / (width as f64); let red = ((((y + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); let green = diff --git a/examples/rectangle.rs b/examples/rectangle.rs index 5443d3b9..4940ed3b 100644 --- a/examples/rectangle.rs +++ b/examples/rectangle.rs @@ -4,17 +4,17 @@ use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; fn redraw(buffer: &mut [u32], width: usize, height: usize, flag: bool) { - for (index, color) in buffer.iter_mut().enumerate() { - let y = index / width; - let x = index % width; - - if flag && x >= 100 && x < width - 100 && y >= 100 && y < height - 100 { - *color = 0x00ffffff; - } else { - let red = (x & 0xff) ^ (y & 0xff); - let green = (x & 0x7f) ^ (y & 0x7f); - let blue = (x & 0x3f) ^ (y & 0x3f); - *color = (blue | (green << 8) | (red << 16)) as u32; + for y in 0..height { + for x in 0..width { + let value = if flag && x >= 100 && x < width - 100 && y >= 100 && y < height - 100 { + 0x00ffffff + } else { + let red = (x & 0xff) ^ (y & 0xff); + let green = (x & 0x7f) ^ (y & 0x7f); + let blue = (x & 0x3f) ^ (y & 0x3f); + (blue | (green << 8) | (red << 16)) as u32 + }; + buffer[y * width + x] = value; } } } diff --git a/examples/winit.rs b/examples/winit.rs index 935ef3f8..fe5a82b8 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -42,14 +42,14 @@ 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); + 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 * width as usize + x as usize; + buffer[index] = blue | (green << 8) | (red << 16); + } } buffer.present().unwrap(); diff --git a/src/cg/buffer.rs b/src/cg/buffer.rs new file mode 100644 index 00000000..da83408e --- /dev/null +++ b/src/cg/buffer.rs @@ -0,0 +1,92 @@ +use core_foundation::{ + base::TCFType, dictionary::CFDictionary, number::CFNumber, string::CFString, +}; +use io_surface::{ + kIOSurfaceBytesPerElement, 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: u32, height: u32) -> Self { + let properties = unsafe { + CFDictionary::from_CFType_pairs(&[ + ( + CFString::wrap_under_get_rule(kIOSurfaceWidth), + CFNumber::from(i64::from(width)).as_CFType(), + ), + ( + CFString::wrap_under_get_rule(kIOSurfaceHeight), + CFNumber::from(i64::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); + } + } + + #[allow(dead_code)] + #[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 63% rename from src/cg.rs rename to src/cg/mod.rs index fce628d6..9f832044 100644 --- a/src/cg.rs +++ b/src/cg/mod.rs @@ -1,34 +1,23 @@ use crate::SoftBufferError; -use core_graphics::base::{ - kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault, -}; -use core_graphics::color_space::CGColorSpace; -use core_graphics::data_provider::CGDataProvider; -use core_graphics::image::CGImage; use raw_window_handle::AppKitWindowHandle; use cocoa::appkit::{NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow}; use cocoa::base::{id, nil}; use cocoa::quartzcore::{transaction, CALayer, ContentsGravity}; -use foreign_types::ForeignType; use std::num::NonZeroU32; -use std::sync::Arc; +use std::ptr; -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, window: id, - color_space: CGColorSpace, width: u32, height: u32, + data: Vec, + buffer: Option, } impl CGImpl { @@ -47,72 +36,81 @@ impl CGImpl { view.addSubview_(subview); // retains subview (+1) = 2 let _: () = msg_send![subview, release]; // releases subview (-1) = 1 } - let color_space = CGColorSpace::create_device_rgb(); Ok(Self { layer, window, - color_space, width: 0, height: 0, + data: Vec::new(), + buffer: None, }) } pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { - self.width = width.get(); - self.height = height.get(); + let width = width.get(); + let height = height.get(); + if width != self.width || height != self.height { + self.width = width; + self.height = height; + self.data.resize(width as usize * height as usize, 0); + self.buffer = Some(Buffer::new(width, height)); + } Ok(()) } pub fn buffer_mut(&mut self) -> Result { - Ok(BufferImpl { - buffer: vec![0; self.width as usize * self.height as usize], - imp: self, - }) + if self.buffer.is_none() { + panic!("Must set size of surface before calling `buffer_mut()`"); + } + + Ok(BufferImpl { imp: self }) } } pub struct BufferImpl<'a> { imp: &'a mut CGImpl, - buffer: Vec, } impl<'a> BufferImpl<'a> { #[inline] pub fn pixels(&self) -> &[u32] { - &self.buffer + &self.imp.data } #[inline] pub fn pixels_mut(&mut self) -> &mut [u32] { - &mut self.buffer + &mut self.imp.data } 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, - ); - // 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. transaction::begin(); transaction::set_disable_actions(true); + let buffer = self.imp.buffer.as_mut().unwrap(); unsafe { + // Copy pixels into `IOSurface` buffer, with right stride and + // alpha + buffer.lock(); + let stride = buffer.stride(); + let pixels = buffer.pixels_mut(); + let width = self.imp.width as usize; + for y in 0..self.imp.height as usize { + for x in 0..width { + // Set alpha to 255 + let value = self.imp.data[y * width + x] | (255 << 24); + pixels[y * stride + x] = value; + } + } + 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(ptr::null_mut()); + self.imp.layer.set_contents(buffer.as_ptr() as id); }; transaction::commit();