winit icon indicating copy to clipboard operation
winit copied to clipboard

Move `can_create_surfaces()`/`destroy_surfaces()` from `ApplicationHandler` to `WindowEvent`

Open MarijnS95 opened this issue 1 month ago • 8 comments

It has been brought up in many issues over the years, that winit to this day doesn't support multiple Activitys on Android. I've always envisioned them to be analogous to a system-created window (i.e. needing a new ApplicationHandler::window_created(new_window: Window) callback of sorts) that the user could possibly launch themselves asynchronously with https://github.com/rust-mobile/android-intent.

As brought up again in https://github.com/rust-windowing/winit/pull/4419 this requires the surface events to be moved to WindowEvent as they're strictly related to a single Activity (thus winit Window) for which the underlying presentation surface (a layer in the composition stack/tree of sorts) is created or destroyed.

Looking through the current examples, it completely eludes me why can_create_surfaces() has been the selected place to create new windows now, as that is entirely counter to what the original Event::Resumed used/intended to represent. On winit's Android backend, a Window is anyway a bogus concept that doesn't really represent anything right now.

Is that something we can move and resolve before winit 0.31 is finalized and released, allowing Android to possibly "catch up" without breaking core/API releases - and if anything to demonstrate the right semantics in at least our examples/documentation/guidelines?

Related downstream confusion

  • https://github.com/ash-rs/ash/pull/1008#discussion_r2423072441

MarijnS95 avatar Nov 23 '25 21:11 MarijnS95

It has been brought up in many issues over the years, that winit to this day doesn't support multiple Activitys on Android. I've always envisioned them to be analogous to a system-created window (i.e. needing a new ApplicationHandler::window_created(new_window: Window) callback of sorts) that the user could possibly launch themselves asynchronously with https://github.com/rust-mobile/android-intent.

This is the end design I want to kind of have as well. Like we try to solve an issue that Window is not ready, in this case we should indeed, move the can_create_surfaces to be a WindowEvent, since window is ready before, but at this point, why can't we just delay the entire window itself, since it's not useful (As I was suggesting)? We have similar issue with Wayland, but it's solved there internally.

kchibisov avatar Nov 24 '25 06:11 kchibisov

I fully agree that can_create_surfaces()/destroy_surfaces() should be window events.

I actually tend to think that they should be WindowEvent::Created / WindowEvent::Destroyed, since that is what happens on Android IIUC? The entire window is torn down and re-created (with state restoration) whenever you do something as simple as rotate your phone (though you can opt out of it in some cases by modifying the manifest).

madsmtm avatar Nov 24 '25 10:11 madsmtm

I actually tend to think that they should be WindowEvent::Created / WindowEvent::Destroyed

I think we can start with something like that actually, and then move to proper event that sends you the window.

kchibisov avatar Nov 24 '25 10:11 kchibisov

I actually tend to think that they should be WindowEvent::Created / WindowEvent::Destroyed, since that is what happens on Android IIUC? The entire window is torn down and re-created (with state restoration) whenever you do something as simple as rotate your phone (though you can opt out of it in some cases by modifying the manifest).

"Unfortunately" not, you're conflating two things here.

When an app doesn't opt into any so-called configuration changes, yes your Activity (which I want to become analogous to a Window) is entirely torn down and recreated, see onCreate and onDestroy in the lifecycle: https://developer.android.com/guide/components/activities/activity-lifecycle.

But that's very inefficient, which is why Android allows configuring to which configChanges the app can respond which are delivered via simple callbacks.


And both of those are "entirely" decoupled from Surface lifetimes. While these definitely occur when the Activity is (re)started or resized, they also happen when the user navigates away from the Activity or locks their phone where the compositor will "immediately" remove the render surface even if the Activity or Window as it were are still alive (only paused/stopped or resumed/started).


TL;DR: On Android Activity (Window) lifetime != Surface lifetime. We can't tie can_create/destroy_surfaces() to Window::Created/Destroyed events, they're separate things.

MarijnS95 avatar Nov 24 '25 10:11 MarijnS95

I've been upgrading Blitz to Winit 0.31, and wanted to add my support for this change. This event doesn't make much sense to me where it is but seems like a natural fit for a WindowEvent (and would actually clarify when I should be creating/destoying surfaces which was never entirely clear to me in the 0.30 API).

Would we be able to maintain the invariant that CanCreateSurfaces/DestroySurfaces event pairs always occur between Created/Destroyed event pairs (across all platforms)?

nicoburns avatar Dec 03 '25 15:12 nicoburns

@nicoburns yes, I think that invariant should hold (if/how winit should validate it - possibly in debug mode - is not known to me). Otherwise receiving either of those events for an inexistent WindowId (before Created or after Destroyed), like any other WindowEvent, seems completely borked.

EDIT: It seems @madsmtm was writing such validation in https://github.com/rust-windowing/winit/pull/3710

Related: we already have https://github.com/rust-windowing/winit/issues/3206 / https://github.com/rust-windowing/winit/pull/4419 to ensure DestroySurfaces will also be raised on all platforms, as right now that is only true for CanCreateSurfaces.

MarijnS95 avatar Dec 03 '25 15:12 MarijnS95

@MarijnS95 Follow-up question. I've just realised that can_create_surfaces is now called instead of resume (which is no longer called at application startup (at least on macOS)). I am currently deferring creation of windows until can_create_surfaces (used to be resume) is called. That obviously wouldn't work if can_create_surfaces was a WindowEvent as a window to receieve the event would never be created!

Does this mean that it is now ok to create windows immediately in my main() function. Or is there is there some other event I should be waiting for to create windows?

nicoburns avatar Dec 03 '25 16:12 nicoburns

@nicoburns good point, see my original issue description: I was also very confused by seeing window creation inside can_create_surfaces(), which - for the purposes of Android - is a feature "on" a window.

It's unclear to me where these now belong though. In Android, those suspend/resume events are also part of an Activity, which I wanted to make analogous to a Window, so that won't work.

Personally, perhaps it should be async? Have an (Active)EventLoop and call create_window() for which later WindowEvent::Created is raised?

MarijnS95 avatar Dec 03 '25 16:12 MarijnS95