Skip to content

Commit 6964486

Browse files
committed
Avoid an explicit CATransaction commit
These are expensive, and the layer should be able to figure out the timing of when to render by itself.
1 parent 5e9ec66 commit 6964486

File tree

4 files changed

+31
-10
lines changed

4 files changed

+31
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- **Breaking:** Disable the DRM/KMS backend by default.
1414
- **Breaking:** Removed `DamageOutOfRange` error case. If the damage value is greater than the backend supports, it is instead clamped to an appropriate value.
1515
- Fixed `present_with_damage` with bounds out of range on Windows, Web and X11.
16+
- Improved performance when presenting on macOS.
1617

1718
# 0.4.7
1819

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ objc2-foundation = { version = "0.3.2", default-features = false, features = [
114114
"NSString",
115115
"NSThread",
116116
"NSValue",
117+
"NSNull",
117118
] }
118119
objc2-quartz-core = { version = "0.3.2", default-features = false, features = [
119120
"std",

src/backends/cg.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::backend_interface::*;
33
use crate::error::InitError;
44
use crate::{util, Pixel, Rect, SoftBufferError};
55
use objc2::rc::Retained;
6-
use objc2::runtime::{AnyObject, Bool};
6+
use objc2::runtime::{AnyObject, Bool, ProtocolObject};
77
use objc2::{define_class, msg_send, AllocAnyThread, DefinedClass, MainThreadMarker, Message};
88
use objc2_core_foundation::{CFRetained, CGPoint};
99
use objc2_core_graphics::{
@@ -12,10 +12,10 @@ use objc2_core_graphics::{
1212
};
1313
use objc2_foundation::{
1414
ns_string, NSDictionary, NSKeyValueChangeKey, NSKeyValueChangeNewKey,
15-
NSKeyValueObservingOptions, NSNumber, NSObject, NSObjectNSKeyValueObserverRegistration,
15+
NSKeyValueObservingOptions, NSNull, NSNumber, NSObject, NSObjectNSKeyValueObserverRegistration,
1616
NSString, NSValue,
1717
};
18-
use objc2_quartz_core::{kCAGravityTopLeft, CALayer, CATransaction};
18+
use objc2_quartz_core::{kCAGravityTopLeft, CALayer};
1919
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
2020

2121
use std::ffi::c_void;
@@ -225,6 +225,17 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for CGImpl<
225225
// resized to something that doesn't fit, see #177.
226226
layer.setContentsGravity(unsafe { kCAGravityTopLeft });
227227

228+
// The CALayer has a default action associated with a change in the layer contents, causing
229+
// a quarter second fade transition to happen every time a new buffer is applied.
230+
//
231+
// We avoid this by setting the action for the "contents" key to NULL.
232+
//
233+
// TODO(madsmtm): Do we want to do the same for bounds/contentsScale for smoother resizing?
234+
layer.setActions(Some(&NSDictionary::from_slices(
235+
&[ns_string!("contents")],
236+
&[ProtocolObject::from_ref(&*NSNull::null())],
237+
)));
238+
228239
// Initialize color space here, to reduce work later on.
229240
let color_space = CGColorSpace::new_device_rgb().unwrap();
230241

@@ -354,16 +365,9 @@ impl BufferInterface for BufferImpl<'_> {
354365
}
355366
.unwrap();
356367

357-
// The CALayer has a default action associated with a change in the layer contents, causing
358-
// a quarter second fade transition to happen every time a new buffer is applied. This can
359-
// be avoided by wrapping the operation in a transaction and disabling all actions.
360-
CATransaction::begin();
361-
CATransaction::setDisableActions(true);
362-
363368
// SAFETY: The contents is `CGImage`, which is a valid class for `contents`.
364369
unsafe { self.layer.setContents(Some(image.as_ref())) };
365370

366-
CATransaction::commit();
367371
Ok(())
368372
}
369373
}

src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,21 @@ impl Buffer<'_> {
301301
///
302302
/// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the
303303
/// Wayland compositor before calling this function.
304+
///
305+
/// ## macOS / iOS
306+
///
307+
/// On macOS/iOS/etc., this sets the [contents] of the underlying [`CALayer`], but doesn't yet
308+
/// actually commit those contents to the compositor; that is instead done automatically by
309+
/// QuartzCore at the end of the current iteration of the runloop. This synchronizes the
310+
/// contents with the rest of the window, which is important to avoid flickering when resizing.
311+
///
312+
/// If you need to send the contents to the compositor immediately (might be useful when
313+
/// rendering from a separate thread or when using Softbuffer without the standard AppKit/UIKit
314+
/// runloop), you'll want to wrap this function in a [`CATransaction`].
315+
///
316+
/// [contents]: https://developer.apple.com/documentation/quartzcore/calayer/contents?language=objc
317+
/// [`CALayer`]: https://developer.apple.com/documentation/quartzcore/calayer?language=objc
318+
/// [`CATransaction`]: https://developer.apple.com/documentation/quartzcore/catransaction?language=objc
304319
#[inline]
305320
pub fn present(self) -> Result<(), SoftBufferError> {
306321
// Damage the entire buffer.

0 commit comments

Comments
 (0)