diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e605c92..eb59be2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - **Breaking:** Disable the DRM/KMS backend by default. - **Breaking:** Removed `DamageOutOfRange` error case. If the damage value is greater than the backend supports, it is instead clamped to an appropriate value. - Fixed `present_with_damage` with bounds out of range on Windows, Web and X11. +- Reduced flickering when presenting while resizing on macOS. # 0.4.7 diff --git a/Cargo.toml b/Cargo.toml index 144b6b4a..4844fa46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,7 @@ objc2-foundation = { version = "0.3.2", default-features = false, features = [ "NSString", "NSThread", "NSValue", + "NSNull", ] } objc2-quartz-core = { version = "0.3.2", default-features = false, features = [ "std", diff --git a/src/backends/cg.rs b/src/backends/cg.rs index 889fe374..0f1d2ef7 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -3,7 +3,7 @@ use crate::backend_interface::*; use crate::error::InitError; use crate::{util, Pixel, Rect, SoftBufferError}; use objc2::rc::Retained; -use objc2::runtime::{AnyObject, Bool}; +use objc2::runtime::{AnyObject, Bool, ProtocolObject}; use objc2::{define_class, msg_send, AllocAnyThread, DefinedClass, MainThreadMarker, Message}; use objc2_core_foundation::{CFRetained, CGPoint}; use objc2_core_graphics::{ @@ -12,10 +12,10 @@ use objc2_core_graphics::{ }; use objc2_foundation::{ ns_string, NSDictionary, NSKeyValueChangeKey, NSKeyValueChangeNewKey, - NSKeyValueObservingOptions, NSNumber, NSObject, NSObjectNSKeyValueObserverRegistration, + NSKeyValueObservingOptions, NSNull, NSNumber, NSObject, NSObjectNSKeyValueObserverRegistration, NSString, NSValue, }; -use objc2_quartz_core::{kCAGravityTopLeft, CALayer, CATransaction}; +use objc2_quartz_core::{kCAGravityTopLeft, CALayer}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::ffi::c_void; @@ -225,6 +225,17 @@ impl SurfaceInterface for CGImpl< // resized to something that doesn't fit, see #177. layer.setContentsGravity(unsafe { kCAGravityTopLeft }); + // 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. + // + // We avoid this by setting the action for the "contents" key to NULL. + // + // TODO(madsmtm): Do we want to do the same for bounds/contentsScale for smoother resizing? + layer.setActions(Some(&NSDictionary::from_slices( + &[ns_string!("contents")], + &[ProtocolObject::from_ref(&*NSNull::null())], + ))); + // Initialize color space here, to reduce work later on. let color_space = CGColorSpace::new_device_rgb().unwrap(); @@ -354,16 +365,9 @@ impl BufferInterface for BufferImpl<'_> { } .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 - // be avoided by wrapping the operation in a transaction and disabling all actions. - CATransaction::begin(); - CATransaction::setDisableActions(true); - // SAFETY: The contents is `CGImage`, which is a valid class for `contents`. unsafe { self.layer.setContents(Some(image.as_ref())) }; - CATransaction::commit(); Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index cdbb7f58..97bd962f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -301,6 +301,21 @@ impl Buffer<'_> { /// /// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the /// Wayland compositor before calling this function. + /// + /// ## macOS / iOS + /// + /// On macOS/iOS/etc., this sets the [contents] of the underlying [`CALayer`], but doesn't yet + /// actually commit those contents to the compositor; that is instead done automatically by + /// QuartzCore at the end of the current iteration of the runloop. This synchronizes the + /// contents with the rest of the window, which is important to avoid flickering when resizing. + /// + /// If you need to send the contents to the compositor immediately (might be useful when + /// rendering from a separate thread or when using Softbuffer without the standard AppKit/UIKit + /// runloop), you'll want to wrap this function in a [`CATransaction`]. + /// + /// [contents]: https://developer.apple.com/documentation/quartzcore/calayer/contents?language=objc + /// [`CALayer`]: https://developer.apple.com/documentation/quartzcore/calayer?language=objc + /// [`CATransaction`]: https://developer.apple.com/documentation/quartzcore/catransaction?language=objc #[inline] pub fn present(self) -> Result<(), SoftBufferError> { // Damage the entire buffer.