From 209013642720b97b0f6385479a2d9be437170e96 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 2 Dec 2024 00:38:32 +0100 Subject: [PATCH 1/2] Update objc2 to v0.6, and use new objc2-core-graphics crate --- .github/workflows/ci.yml | 8 +-- CHANGELOG.md | 3 + Cargo.toml | 82 +++++++++++++++++----- README.md | 2 +- src/backends/cg.rs | 143 +++++++++++++++++++++------------------ 5 files changed, 147 insertions(+), 91 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99b1ef4..c516648 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: strategy: fail-fast: false matrix: - rust_version: ['1.70.0', stable, nightly] + rust_version: ['1.71.0', stable, nightly] platform: - { target: x86_64-pc-windows-msvc, os: windows-latest, } - { target: i686-pc-windows-msvc, os: windows-latest, } @@ -51,7 +51,7 @@ jobs: - { target: wasm32-unknown-unknown, os: ubuntu-latest, } exclude: # Orbital doesn't follow MSRV - - rust_version: '1.70.0' + - rust_version: '1.71.0' platform: { target: x86_64-unknown-redox, os: ubuntu-latest } include: - rust_version: nightly @@ -96,11 +96,9 @@ jobs: run: sudo apt-get install gcc-multilib - name: Pin deps that break MSRV - if: matrix.rust_version == '1.70.0' + if: matrix.rust_version == '1.71.0' run: | - cargo update -p ahash --precise 0.8.7 cargo update -p bumpalo --precise 3.14.0 - cargo update -p objc2-encode --precise 4.0.3 - name: Build crate shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e45763..8669f2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +- Update to `objc2` 0.6.0. +- Bump MSRV to Rust 1.71. + # 0.4.6 - Added support for iOS, tvOS, watchOS and visionOS (UIKit). diff --git a/Cargo.toml b/Cargo.toml index 49cd067..f90cbba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/rust-windowing/softbuffer" keywords = ["framebuffer", "windowing"] categories = ["game-development", "graphics", "gui", "multimedia", "rendering"] exclude = ["examples"] -rust-version = "1.70.0" +rust-version = "1.71.0" [[bench]] name = "buffer_mut" @@ -19,13 +19,29 @@ harness = false [features] default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"] kms = ["bytemuck", "drm", "rustix"] -wayland = ["wayland-backend", "wayland-client", "wayland-sys", "memmap2", "rustix", "fastrand"] +wayland = [ + "wayland-backend", + "wayland-client", + "wayland-sys", + "memmap2", + "rustix", + "fastrand", +] wayland-dlopen = ["wayland-sys/dlopen"] -x11 = ["as-raw-xcb-connection", "bytemuck", "fastrand", "rustix", "tiny-xlib", "x11rb"] +x11 = [ + "as-raw-xcb-connection", + "bytemuck", + "fastrand", + "rustix", + "tiny-xlib", + "x11rb", +] x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"] [dependencies] -raw_window_handle = { package = "raw-window-handle", version = "0.6", features = ["std"] } +raw_window_handle = { package = "raw-window-handle", version = "0.6", features = [ + "std", +] } tracing = { version = "0.1.41", default-features = false } [target.'cfg(target_os = "android")'.dependencies] @@ -38,23 +54,48 @@ bytemuck = { version = "1.12.3", optional = true } drm = { version = "0.14.1", default-features = false, optional = true } fastrand = { version = "2.0.0", optional = true } memmap2 = { version = "0.9.0", optional = true } -rustix = { version = "0.38.19", features = ["fs", "mm", "shm", "std"], default-features = false, optional = true } +rustix = { version = "0.38.19", features = [ + "fs", + "mm", + "shm", + "std", +], default-features = false, optional = true } tiny-xlib = { version = "0.2.1", optional = true } -wayland-backend = { version = "0.3.0", features = ["client_system"], optional = true } +wayland-backend = { version = "0.3.0", features = [ + "client_system", +], optional = true } wayland-client = { version = "0.31.0", optional = true } wayland-sys = { version = "0.31.0", optional = true } -x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "shm"], optional = true } +x11rb = { version = "0.13.0", features = [ + "allow-unsafe-code", + "shm", +], optional = true } [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = "0.59.0" -features = ["Win32_Graphics_Gdi", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_Foundation"] +features = [ + "Win32_Graphics_Gdi", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", + "Win32_Foundation", +] [target.'cfg(target_vendor = "apple")'.dependencies] -bytemuck = { version = "1.12.3", features = ["extern_crate_alloc"] } -core-graphics = "0.24.0" -foreign-types = "0.5.0" -objc2 = "0.5.2" -objc2-foundation = { version = "0.2.2", features = [ +objc2-core-graphics = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2", + "CGColorSpace", + "CGDataProvider", + "CGImage", +] } +objc2 = "0.6.0" +objc2-core-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "CFCGTypes", +] } +objc2-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-foundation", "NSDictionary", "NSGeometry", "NSKeyValueObserving", @@ -62,7 +103,12 @@ objc2-foundation = { version = "0.2.2", features = [ "NSThread", "NSValue", ] } -objc2-quartz-core = { version = "0.2.2", features = ["CALayer", "CATransaction"] } +objc2-quartz-core = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-foundation", + "CALayer", + "CATransaction", +] } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3.63" @@ -89,7 +135,9 @@ cfg_aliases = "0.2.0" [dev-dependencies] colorous = "1.0.12" -criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } +criterion = { version = "0.4.0", default-features = false, features = [ + "cargo_bench_support", +] } web-time = "1.0.0" winit = "0.30.0" @@ -114,9 +162,7 @@ wasm-bindgen-test = "0.3" rustix = { version = "0.38.8", features = ["event"] } [workspace] -members = [ - "run-wasm", -] +members = ["run-wasm"] [[example]] # Run with `cargo apk r --example winit_android` diff --git a/README.md b/README.md index 39cd0db..c17e01f 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ fn main() { ## MSRV Policy -This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to +This crate's Minimum Supported Rust Version (MSRV) is **1.71**. Changes to the MSRV will be accompanied by a minor version bump. As a **tentative** policy, the upper bound of the MSRV is given by the following diff --git a/src/backends/cg.rs b/src/backends/cg.rs index 85bba70..b4eba17 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -1,55 +1,38 @@ use crate::backend_interface::*; use crate::error::InitError; use crate::{Rect, 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 foreign_types::ForeignType; use objc2::rc::Retained; use objc2::runtime::{AnyObject, Bool}; -use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass}; +use objc2::{define_class, msg_send, AllocAnyThread, DefinedClass, MainThreadMarker, Message}; +use objc2_core_foundation::{CFRetained, CGPoint}; +use objc2_core_graphics::{ + CGBitmapInfo, CGColorRenderingIntent, CGColorSpace, CGColorSpaceCreateDeviceRGB, + CGDataProviderCreateWithData, CGImageAlphaInfo, CGImageCreate, +}; use objc2_foundation::{ - ns_string, CGPoint, MainThreadMarker, NSDictionary, NSKeyValueChangeKey, - NSKeyValueChangeNewKey, NSKeyValueObservingOptions, NSNumber, NSObject, - NSObjectNSKeyValueObserverRegistration, NSString, NSValue, + ns_string, NSDictionary, NSKeyValueChangeKey, NSKeyValueChangeNewKey, + NSKeyValueObservingOptions, NSNumber, NSObject, NSObjectNSKeyValueObserverRegistration, + NSString, NSValue, }; use objc2_quartz_core::{kCAGravityTopLeft, CALayer, CATransaction}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::ffi::c_void; use std::marker::PhantomData; +use std::mem::size_of; use std::num::NonZeroU32; use std::ops::Deref; -use std::ptr; -use std::sync::Arc; - -struct Buffer(Vec); - -impl AsRef<[u8]> for Buffer { - fn as_ref(&self) -> &[u8] { - bytemuck::cast_slice(&self.0) - } -} +use std::ptr::{self, slice_from_raw_parts_mut, NonNull}; -declare_class!( +define_class!( + #[unsafe(super(NSObject))] + #[name = "SoftbufferObserver"] + #[ivars = Retained] struct Observer; - unsafe impl ClassType for Observer { - type Super = NSObject; - type Mutability = mutability::InteriorMutable; - const NAME: &'static str = "SoftbufferObserver"; - } - - impl DeclaredClass for Observer { - type Ivars = Retained; - } - - // NSKeyValueObserving - unsafe impl Observer { - #[method(observeValueForKeyPath:ofObject:change:context:)] + /// NSKeyValueObserving + impl Observer { + #[unsafe(method(observeValueForKeyPath:ofObject:change:context:))] fn observe_value( &self, key_path: Option<&NSString>, @@ -69,7 +52,7 @@ unsafe impl Sync for Observer {} impl Observer { fn new(layer: &CALayer) -> Retained { let this = Self::alloc().set_ivars(layer.retain()); - unsafe { msg_send_id![super(this), init] } + unsafe { msg_send![super(this), init] } } fn update( @@ -81,9 +64,11 @@ impl Observer { let change = change.expect("requested a change dictionary in `addObserver`, but none was provided"); - let new = change - .get(unsafe { NSKeyValueChangeNewKey }) - .expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`"); + let new = unsafe { + change + .objectForKey(NSKeyValueChangeNewKey) + .expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`") + }; // NOTE: Setting these values usually causes a quarter second animation to occur, which is // undesirable. @@ -92,14 +77,14 @@ impl Observer { // ongoing, and as such we don't need to wrap this in a `CATransaction` ourselves. if key_path == Some(ns_string!("contentsScale")) { - let new = unsafe { &*(new as *const AnyObject as *const NSNumber) }; + let new = new.downcast::().unwrap(); let scale_factor = new.as_cgfloat(); // Set the scale factor of the layer to match the root layer when it changes (e.g. if // moved to a different monitor, or monitor settings changed). layer.setContentsScale(scale_factor); } else if key_path == Some(ns_string!("bounds")) { - let new = unsafe { &*(new as *const AnyObject as *const NSValue) }; + let new = new.downcast::().unwrap(); let bounds = new.get_rect().expect("new bounds value was not CGRect"); // Set `bounds` and `position` so that the new layer is inside the superlayer. @@ -168,7 +153,7 @@ impl SurfaceInterface for CGImpl< let _: () = unsafe { msg_send![view, setWantsLayer: Bool::YES] }; // SAFETY: `-[NSView layer]` returns an optional `CALayer` - let layer: Option> = unsafe { msg_send_id![view, layer] }; + let layer: Option> = unsafe { msg_send![view, layer] }; layer.expect("failed making the view layer-backed") } RawWindowHandle::UiKit(handle) => { @@ -179,7 +164,7 @@ impl SurfaceInterface for CGImpl< let view: &NSObject = unsafe { handle.ui_view.cast().as_ref() }; // SAFETY: `-[UIView layer]` returns `CALayer` - let layer: Retained = unsafe { msg_send_id![view, layer] }; + let layer: Retained = unsafe { msg_send![view, layer] }; layer } _ => return Err(InitError::Unsupported(window_src)), @@ -225,15 +210,13 @@ impl SurfaceInterface for CGImpl< root_layer.addObserver_forKeyPath_options_context( &observer, ns_string!("contentsScale"), - NSKeyValueObservingOptions::NSKeyValueObservingOptionNew - | NSKeyValueObservingOptions::NSKeyValueObservingOptionInitial, + NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial, ptr::null_mut(), ); root_layer.addObserver_forKeyPath_options_context( &observer, ns_string!("bounds"), - NSKeyValueObservingOptions::NSKeyValueObservingOptionNew - | NSKeyValueObservingOptions::NSKeyValueObservingOptionInitial, + NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial, ptr::null_mut(), ); } @@ -246,7 +229,7 @@ impl SurfaceInterface for CGImpl< layer.setContentsGravity(unsafe { kCAGravityTopLeft }); // Initialize color space here, to reduce work later on. - let color_space = CGColorSpace::create_device_rgb(); + let color_space = unsafe { CGColorSpaceCreateDeviceRGB() }.unwrap(); // Grab initial width and height from the layer (whose properties have just been initialized // by the observer using `NSKeyValueObservingOptionInitial`). @@ -280,7 +263,7 @@ impl SurfaceInterface for CGImpl< fn buffer_mut(&mut self) -> Result, SoftBufferError> { Ok(BufferImpl { - buffer: vec![0; self.width * self.height], + buffer: vec![0; self.width * self.height].into(), imp: self, }) } @@ -288,7 +271,7 @@ impl SurfaceInterface for CGImpl< pub struct BufferImpl<'a, D, W> { imp: &'a mut CGImpl, - buffer: Vec, + buffer: Box<[u32]>, } impl BufferInterface for BufferImpl<'_, D, W> { @@ -307,21 +290,47 @@ impl BufferInterface for BufferImpl<'_, } fn present(self) -> Result<(), SoftBufferError> { - let data_provider = CGDataProvider::from_buffer(Arc::new(Buffer(self.buffer))); - - let image = CGImage::new( - self.imp.width, - self.imp.height, - 8, - 32, - self.imp.width * 4, - &self.imp.color_space.0, - kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, - &data_provider, - false, - kCGRenderingIntentDefault, - ); - let contents = unsafe { (image.as_ptr() as *mut AnyObject).as_ref() }; + unsafe extern "C-unwind" fn release( + _info: *mut c_void, + data: NonNull, + size: usize, + ) { + let data = data.cast::(); + let slice = slice_from_raw_parts_mut(data.as_ptr(), size / size_of::()); + // SAFETY: This is the same slice that we + drop(unsafe { Box::from_raw(slice) }) + } + + let data_provider = { + let len = self.buffer.len() * size_of::(); + let buffer: *mut [u32] = Box::into_raw(self.buffer); + // Convert slice pointer to thin pointer. + let data_ptr = buffer.cast::(); + + // SAFETY: The data pointer and length are valid. + // The info pointer can safely be NULL, we don't use it in the `release` callback. + unsafe { + CGDataProviderCreateWithData(ptr::null_mut(), data_ptr, len, Some(release)).unwrap() + } + }; + + let image = unsafe { + CGImageCreate( + self.imp.width, + self.imp.height, + 8, + 32, + self.imp.width * 4, + Some(&self.imp.color_space.0), + // TODO: This looks incorrect! + CGBitmapInfo::ByteOrder32Little | CGBitmapInfo(CGImageAlphaInfo::NoneSkipFirst.0), + Some(&data_provider), + ptr::null(), + false, + CGColorRenderingIntent::RenderingIntentDefault, + ) + } + .unwrap(); // 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 @@ -330,7 +339,7 @@ impl BufferInterface for BufferImpl<'_, CATransaction::setDisableActions(true); // SAFETY: The contents is `CGImage`, which is a valid class for `contents`. - unsafe { self.imp.layer.setContents(contents) }; + unsafe { self.imp.layer.setContents(Some(image.as_ref())) }; CATransaction::commit(); Ok(()) @@ -341,7 +350,7 @@ impl BufferInterface for BufferImpl<'_, } } -struct SendCGColorSpace(CGColorSpace); +struct SendCGColorSpace(CFRetained); // SAFETY: `CGColorSpace` is immutable, and can freely be shared between threads. unsafe impl Send for SendCGColorSpace {} unsafe impl Sync for SendCGColorSpace {} From 3158984f0b82453f3a3fb45120e6a6dbfcba1ee8 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 5 Feb 2025 08:11:48 +0100 Subject: [PATCH 2/2] Finish comment --- src/backends/cg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/cg.rs b/src/backends/cg.rs index b4eba17..3e3cbe4 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -297,7 +297,7 @@ impl BufferInterface for BufferImpl<'_, ) { let data = data.cast::(); let slice = slice_from_raw_parts_mut(data.as_ptr(), size / size_of::()); - // SAFETY: This is the same slice that we + // SAFETY: This is the same slice that we passed to `Box::into_raw` below. drop(unsafe { Box::from_raw(slice) }) }