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

Redrawing on macOS will not work with future versions of Winit #209

Open
madsmtm opened this issue Nov 25, 2024 · 2 comments
Open

Redrawing on macOS will not work with future versions of Winit #209

madsmtm opened this issue Nov 25, 2024 · 2 comments

Comments

@madsmtm
Copy link

madsmtm commented Nov 25, 2024

Winit currently implements redrawing in a weird ad-hoc fashion on macOS. This causes a bunch of issues, see rust-windowing/winit#2640 for some of it, which I intend to fix by emitting RedrawRequested in the built-in scheduled draw events (-[NSView drawRect:] / -[NSView updateLayer]) instead.

However, this won't work when the "consumer" of the raw-window-handle that Winit provides (i.e. blade) is overriding the layer on NSView, as is currently done, since that makes the view "layer-hosting", see -[NSView wantsLayer].

The code in question is around here:

pub unsafe fn from_view(view: *mut Object) -> Self {
let main_layer: *mut Object = msg_send![view, layer];
let class = class!(CAMetalLayer);
let is_valid_layer: BOOL = msg_send![main_layer, isKindOfClass: class];
let raw_layer = if is_valid_layer == YES {
main_layer
} else {
// If the main layer is not a CAMetalLayer, we create a CAMetalLayer and use it.
let new_layer: *mut Object = msg_send![class, new];
let frame: CGRect = msg_send![main_layer, bounds];
let () = msg_send![new_layer, setFrame: frame];
#[cfg(target_os = "ios")]
{
// Unlike NSView, UIView does not allow to replace main layer.
let () = msg_send![main_layer, addSublayer: new_layer];
let () = msg_send![main_layer, setAutoresizingMask: 0x1Fu64];
let screen: *mut Object = msg_send![class!(UIScreen), mainScreen];
let scale_factor: CGFloat = msg_send![screen, nativeScale];
let () = msg_send![view, setContentScaleFactor: scale_factor];
};
#[cfg(target_os = "macos")]
{
let () = msg_send![view, setLayer: new_layer];
let () = msg_send![view, setWantsLayer: YES];
let () = msg_send![new_layer, setContentsGravity: kCAGravityTopLeft];
let window: *mut Object = msg_send![view, window];
if !window.is_null() {
let scale_factor: CGFloat = msg_send![window, backingScaleFactor];
let () = msg_send![new_layer, setContentsScale: scale_factor];
}
}
new_layer
};
Self {
view: msg_send![view, retain],
render_layer: mem::transmute::<_, &metal::MetalLayerRef>(raw_layer).to_owned(),
info: crate::SurfaceInfo {
format: crate::TextureFormat::Rgba8Unorm,
alpha: crate::AlphaMode::Ignored,
},
}
}

To fix this, blade should be using -[CALayer addSublayer:] like on iOS instead of -[CALayer setLayer:]. The logic to get resizing to work automatically after this though is a bit cumbersome (you'll need to use observers), so I've extracted it into a crate: raw-window-metal.

I can submit a PR for it if you want, though note that raw-window-metal depends on objc2. If you don't want to use that, you could try to use a similar approach as wgpu currently does, though beware that that still has issues, observers are really the best way to do this (that I've found at least).

@kvark
Copy link
Owner

kvark commented Nov 27, 2024

Interesting, thank you for bringing this up! So you are saying we shouldn't be replacing the layer, and we can add a sub-layer. If there is already a layer though, would it be fine to just use it directly (instead of a sublayer)?

@madsmtm
Copy link
Author

madsmtm commented Nov 27, 2024

Interesting, thank you for bringing this up! So you are saying we shouldn't be replacing the layer, and we can add a sub-layer. If there is already a layer though, would it be fine to just use it directly (instead of a sublayer)?

Yeah, if the view is already configured with a CAMetalLayer, you either have MTKView, and then resizing is going to work in a different way anyhow, or the user has overwritten -[UIView layerClass] on iOS / called setLayer: on macOS, in which case they're responsible for the view now being layer-hosting (so they're also responsible for letting blade know when to resize and redraw).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants