Skip to content

Generic Surface for !Send situations #7549

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

Open
kpreid opened this issue Apr 15, 2025 · 4 comments
Open

Generic Surface for !Send situations #7549

kpreid opened this issue Apr 15, 2025 · 4 comments
Labels
area: api Issues related to API surface area: wsi Issues with swapchain management or windowing type: enhancement New feature or request

Comments

@kpreid
Copy link
Collaborator

kpreid commented Apr 15, 2025

Is your feature request related to a problem? Please describe.
I became aware of a public example of using wgpu which does unsafe impl Send to bypass wgpu::WindowHandle's Send + Sync requirement:

https://github.com/vhspace/sdl3-rs/blob/82ff24b43044c5ebacf004cabb04339ed93e8b05/examples/raw-window-handle-with-wgpu/main.rs#L205-L209

This is not ideal, and it would be helpful if wgpu could make this unsound unsafe code unnecessary for a basic usage with a !Send windowing library. (By unsound, I mean: it is possible for code outside the unsafe module to misuse it to cause UB; specifically, by sending the resulting wgpu::Surface.)

Describe the solution you'd like
Change wgpu::Surface to, instead of type-erasing the provided window handle, be generic over the type of the handle (replacing the current lifetime parameter). The API for this could look like

impl Instance {
    // Unfortunately, to avoid ambiguous generics, `SurfaceTarget` has to go away
    // and be replaced with separate methods.

    pub fn create_surface_from_window<W: HasWindowHandle + HasDisplayHandle>(
        &self,
        window: W,
    ) -> Result<Surface<W>, CreateSurfaceError> {...}

    pub fn create_surface_from_canvas(
        &self,
        canvas: HtmlCanvasElement,
    ) -> Result<Surface<()>, CreateSurfaceError> {...}
}

// Surface just owns the window without manipulating it
struct Surface<W> {
    ...
    _window: W,
}

If a type-erased Surface is still needed (e.g. for a renderer library used on multiple platforms that wishes to avoid generics), this can be done by the application, especially if wgpu helps out with:

impl Surface<()> {
    fn with_placeholder_window<W>(self, window: W) -> Surface<W> {
        Self { ..., window: W }
    }
}

which means the application can treat window-handle surfaces and canvas surfaces uniformly just by using wgpu::Surface<Option<Box<dyn MyWindowHandle>>>, essentially recovering the current type-erased API.

Describe alternatives you've considered

  • The status quo
  • wgpu could expose a separate struct SyncSurface and struct NonSyncSurface, to not have any public generic code.

Additional context

The idea of Surface<W> was part of the original discussion on safe surface creation #1463. It was omitted from the PR #4597 apparently on the grounds that it is desirable for the underlying implementation traits to be dyn-compatible — but since the only thing wgpu::Surface does with the handle is own and drop it, the generic does not have to leak into lower layers.

@cwfitzgerald
Copy link
Member

I wonder if we could split the difference and have a Surface<NonSyncContainer> and Suface<SyncContainer> and those are aliases/wrapper types for Box<dyn Whatever> and Box<dyn Whatever + Send + Sync>.

@cwfitzgerald cwfitzgerald added type: enhancement New feature or request area: api Issues related to API surface area: wsi Issues with swapchain management or windowing labels Apr 16, 2025
@cwfitzgerald
Copy link
Member

Discussing this in next week's maintainers meeting

@leftmostcat
Copy link

leftmostcat commented Apr 17, 2025

At a glance (so I'm prepared to be very wrong here), it seems like the Send + Sync bound on wgpu::WindowHandle is required solely by the requirement that Surface not outlive the dyn wgpu::WindowHandle it's created from. Other than the code in Instance instantiating the Surface, _handle_source is never referenced (except the automatic Drop impl).

Assuming the Surface<NonSyncContainer> would not be Send + Sync, this would presumably cause problems for using that Surface with RequestAdapterOptions. If it were possible to bound the Surface such that it were always dropped before the Window Handle without having to actually retain a reference, that would make a NonSyncContainer variant unnecessary, since the WindowHandle itself would never need to leave the main thread.

I'm not sure if this actually accomplishes that, but this example creates the lifetime bound without requiring that the underlying type be Send + Sync: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1d666b3fe1363e1c34052760abfb75d8

Edit: Tweaked the playground to improve the exposed generics on LifetimeMarker.

@kpreid
Copy link
Collaborator Author

kpreid commented Apr 23, 2025

Discussion from this week's wgpu maintainers meeting:

Instead of making Surface generic, we could provide a wrapper in the style of send_wrapper for window handles, which makes any window handle able to be Send — at the price of panicking if the Surface is dropped on a thread other than the originating thread. However, send_wrapper (and anything that has the same effect) depends on std to be able to obtain the current thread ID to decide whether to panic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: api Issues related to API surface area: wsi Issues with swapchain management or windowing type: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants