Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Attempt to use IOSurface on macOS #95

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
3 changes: 2 additions & 1 deletion examples/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -89,7 +90,7 @@ fn pre_render_frames(width: usize, height: usize) -> Vec<Vec<u32>> {
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::<Vec<_>>()
};
Expand Down
17 changes: 9 additions & 8 deletions examples/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, Android needs the same!

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();
Expand Down
92 changes: 92 additions & 0 deletions src/cg/buffer.rs
Original file line number Diff line number Diff line change
@@ -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) }
}
}
46 changes: 16 additions & 30 deletions src/cg.rs → src/cg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32>);

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,
Expand Down Expand Up @@ -64,55 +58,47 @@ impl CGImpl {
}

pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
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<u32>,
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.
transaction::begin();
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();
Expand Down
15 changes: 15 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
$(
Expand Down Expand Up @@ -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
Expand Down