1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! Glitch-free macOS `CAMetalLayer` window live resize.
//!
//! Provides the minimum configuration primitives to prevent content
//! distortion during window resize on macOS Metal apps.
//!
//! # The problem
//!
//! During live window resize on macOS, the compositor lags behind the
//! app's rendering by one or more frames. By default, the compositor
//! stretches the previous drawable contents to fill the new window
//! bounds (`contentsGravity = kCAGravityResize`), which shows up as
//! visible wobble or distortion until the next frame is presented.
//!
//! # The fix
//!
//! Two one-time configuration calls on the `CAMetalLayer`:
//!
//! 1. **`contentsGravity = kCAGravityTopLeft`** — pins stale frames to
//! the top-left corner instead of scaling them, so the "wrong" area
//! is clipped rather than stretched.
//! 2. **`contentsScale = NSWindow.backingScaleFactor`** — ensures
//! drawable pixels map 1:1 to screen pixels on Retina displays,
//! without which topLeft gravity puts content at the wrong position.
//!
//! And one per-frame discipline:
//!
//! 3. **Read drawable texture dimensions at render time**, never cached
//! layer `width`/`height`. During resize, the drawable's texture
//! may not yet match the cached size and rendering at the wrong
//! size causes visible distortion.
//!
//! # What NOT to do
//!
//! The `presentsWithTransaction = true` + `commandBuffer.waitUntilScheduled()`
//! approach is sometimes suggested as an alternative. It has been tested
//! and **breaks frame delivery** — AppKit events fire (hit tests pass,
//! state updates) but nothing renders to the screen. The
//! `contentsGravity + contentsScale` approach alone is sufficient and
//! does not block the event loop.
//!
//! # Reference
//!
//! Pattern first documented by Tristan Hume, 2019:
//! <https://thume.ca/2019/06/19/glitchless-metal-window-resizing/>
//!
//! # Example
//!
//! ```ignore
//! // macOS-only; the crate exposes no items on other platforms.
//! use metal_live_resize::configure_for_live_resize;
//! use core::ffi::c_void;
//!
//! unsafe fn example(layer: *mut c_void, view: *mut c_void) {
//! // after attaching your CAMetalLayer to an NSView:
//! unsafe { configure_for_live_resize(layer, view) };
//!
//! // per-frame, read the actual drawable size rather than cached w/h:
//! if let Some((w, h)) = unsafe { metal_live_resize::drawable_texture_size(layer) } {
//! let _ = (w, h); // render at (w, h)
//! }
//! }
//! ```
use ;
use AnyObject;
use ;
/// Apply both live-resize fixes: sets `contentsGravity = kCAGravityTopLeft`
/// and `contentsScale = view.window.backingScaleFactor` on the layer.
///
/// Convenience wrapper around [`set_contents_gravity_top_left`] and
/// [`set_contents_scale`] with scale read via [`view_backing_scale`].
/// If the view has no attached window yet, only the gravity is set.
///
/// Call once after attaching the `CAMetalLayer` to the `NSView`.
///
/// # Safety
/// - `layer` must be a valid pointer to a `CAMetalLayer`.
/// - `view` must be a valid pointer to an `NSView` that owns the layer.
/// - Must be called on the main thread (AppKit requirement).
pub unsafe
/// Sets `contentsGravity = kCAGravityTopLeft` on the layer.
///
/// Prevents the compositor from scaling old drawable contents during
/// live resize. Without this, each resize tick visibly stretches the
/// previous frame until the next `nextDrawable` completes.
///
/// # Safety
/// - `layer` must be a valid pointer to a `CALayer` (or subclass such
/// as `CAMetalLayer`).
/// - Must be called on the main thread.
pub unsafe
/// Sets `contentsScale` on the layer.
///
/// Pass the window's `backingScaleFactor` (2.0 on standard Retina
/// displays, 1.0 on 1x displays, 3.0 on iPhone-class displays).
/// Without this, drawable pixels are mismatched from screen pixels
/// and top-left gravity puts content at a scaled position.
///
/// # Safety
/// - `layer` must be a valid pointer to a `CALayer` (or subclass).
/// - Must be called on the main thread.
pub unsafe
/// Reads the `backingScaleFactor` from an `NSView`'s window.
///
/// Returns `None` if the view is detached from any window (i.e.
/// `[view window]` returns `nil`). In that case fall back to 1.0 or
/// query the main screen directly.
///
/// # Safety
/// - `view` must be a valid pointer to an `NSView`.
/// - Must be called on the main thread.
pub unsafe
/// Gets the actual pixel dimensions of the drawable's current texture.
///
/// Obtains `nextDrawable` from the layer and reads the `width`/`height`
/// of its bound texture. During live resize, the drawable may not yet
/// match the window's reported size, and rendering at the cached size
/// causes visible distortion.
///
/// Returns `None` if `nextDrawable` returns `nil` (drawable pool
/// exhausted — caller should skip the frame).
///
/// **This consumes the next drawable from the layer's pool.** If you
/// need the drawable itself for rendering, obtain it via `nextDrawable`
/// yourself and read `drawable.texture.width/height` directly.
///
/// # Safety
/// - `layer` must be a valid pointer to a `CAMetalLayer` with a Metal
/// device attached.
/// - Must be called on the main thread.
pub unsafe