Winit 0.30 + wgpu 0.20 init lifetime issue

Hi,

while trying to follow the wgpu tutorial using the latest versions of winit and wpug, I run into some self-referentiel struct error I can't fix.

The winit sample is as follow:

struct App {
   window: Option<Window>,
}

impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop)  {
        self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap();
    }

   fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
      todo!()
   }
}

Now the wgpu tutorial creates a render State as follow:

struct State<'a> {
    surface: wgpu::Surface<'a>,
    ....
    window: &'a Window
}

impl<'a> State<'a> {
    async fn new(window: &'a Window) -> State<'a> {
        ...
        let instance = wgpu::Instance::new(...);
        let surface = instance.create_surface(window).unwrap();
        ...

        Self {
             window,
             surface,
             ...
        }
    }
}

I was planning to call this State::new() method within a pollster::block_on() call inside the ApplicationHandler::resumed() method, something like this:

    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        let window = event_loop
                .create_window(Window::default_attributes())
                .unwrap();
        let state = pollster::block_on(State::new(&window)); // (1)
        self.window = Some(window);                                    // (2)
        self.state = Some(state);
    }

But this creates 2 related issues:

  • In (1): window does not live long enough
  • in (2): Cannot move out of 'window' because it is borrowed

I can "fix" (2) by first moving window into App and then borrowing a reference to it as follow:

    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        let window = event_loop
                .create_window(Window::default_attributes())
                .unwrap();
        self.window = Some(window);
        let state = pollster::block_on(State::new(self.window.as_ref().unwrap()));
        self.state = Some(state); // (3)
    }

But now the compiler complains in (3) that the Lifetime may not live long enough.
Although both the window and the state are owned by the App the compiler can't seem to figure this out. I miss something. Is it just a case of lifetime annotation ? And/or self-referential struct (App -> surface -> window + App -> window) ?

Any help welcome

Thanks for reading.

Brieuc

Having a

struct App<'a> {
    window: Option<Window>,
    state: Option<State<'a>>,
}

Where the State<'a> contains a borrow of the Window is indeed a self-referential struct. You could probably make the method in question compile by having &'a mut self like in the Snek example, but that would just move the borrow check errors to the call sites in all likelihood.

There may be some confusion about the meaning of lifetimes, like perhaps you think the lifetime of a field reflects the value/object liveness of the containing struct or such. If so, see this comment in another recent thread.


I don't have any specific advice on fixing your OP. The vague advice would be "only create the State<'_> for short-lived periods when you need it."

1 Like

That's generally appropriate. In the case of wgpu, the Surface needs to exist for the duration of the application, and it references the app's Window.

The usual way this is factored is by abandoning the bag-of-data App approach and putting both the Window and Surface in stack-allocated variables that are moved into the event loop's callback.

And that's exactly what the actual tutorial code does. The App abstraction as designed will not work because it is the wrong abstraction.

2 Likes

I think you misunderstood the problem. Winit 0.30 has different approach to app state handling than in the tutotial you mentioned.
Latest Winit release notes explain that you have to implement new ApplicationHandler trait and process all events there in callbacks. The only way to persist Surface is to save it in ApplicationHandler struct. Wgpu does not yet have implemented Winit 0.30 examples :frowning:

@brieuc I was able to overcome this by using create_surface_usafe. This way it does not persist window reference. But it feels ugly.

let surface = unsafe {
    let target = wgpu::SurfaceTargetUnsafe::from_window(&window).unwrap();
    instance.create_surface_unsafe(target).unwrap()
};

There is a way to set up the wgpu::Surface without any unsafe or lifetime restrictions. All you have to do is put the winit window in an Arc, which then can be cloned and passed to wgpu::Instance::create_surface(). This ensures that the window exists for as long as the surface does.

let window = Arc::new(window);
let surface = instance.create_surface(Arc::clone(&window))?;
// save and use original `window` as needed...

There is no need to contort the control-flow of your program, or use unsafe code, to satisfy wgpu.

2 Likes

They are accepting feedback on the new design: Feedback on migration to trait-based design · Issue #3626 · rust-windowing/winit · GitHub I would encourage anyone who runs into these kinds of issues to submit a comment there.

1 Like

I experimented last night with Rc but somehow manage to NOT make it works... ;(

Thanks!!!

I made a hello triangle example using winit 0.30.0 ang wgpu's latest version as of now, it's right here: sursface/examples/src/hello_triangle/main.rs at main · thinnerthinker/sursface · GitHub

Hello! I apologize in advance for reviving a solved thread, but on top of this not working for me, I have a few questions. You say that there's no need to have lifetime restrictions, however it looks to me like wgpu::Surface needs a lifetime restriction regardless, according to wgpu's documentation, at least.

Taking that into consideration, using an Arc doesn't seem to bypass the lifetime restriction problem, either. At least for me. I still get an error that window does not live long enough, although my implementation is slightly different:

My struct's implementation

#[derive(Default)]
pub struct App<'a> {
    window: Option<&'a winit::window::Window>,
    state: Option<crate::graphics::state::State<'a>>
}

My implementation of resumed:

fn resumed(&mut self, event_loop: &ActiveEventLoop) {
    let window_attributes: WindowAttributes = Window::default_attributes()
        .with_title(super::WINDOW_TITLE)
        .with_resizable(false);

    let window: Option<Window> = Some(event_loop.create_window(window_attributes)
    .unwrap_or_else(|error| panic!("Error! {:?}", error)));

    self.window = window.as_ref(); // Error here that borrowed value does not live long enough

    self.state = Some(pollster::block_on(crate::graphics::state::State::new(self.window)));
}

And here's how I use window on new , according to your solution:

let window_ref: &Window = Option::expect(window, "Window reference is null where it shouldn't be!");

// ...

let window_arc: std::sync::Arc<&Window> = std::sync::Arc::new(window_ref);
let surface: wgpu::Surface<'_> = instance.create_surface(std::sync::Arc::clone(&window_arc)).unwrap_or_else(|error| panic!("Error! {:?}", error));

I was wondering if you could elaborate a bit further on why you think your solution works, that I may find my mistake?

Also, @brieuc , you did mark that post as the solution, I was wondering if you could tell me, specifically, what you did also?

Thank you in advance.

Surface always has a lifetime parameter, but by using Arc and the 'static lifetime there, you incur no restriction from it. In general: just because a type has a lifetime parameter, doesn't mean you necessarily need to put references in it. And if it contains no references, then the lifetime (usually) can be 'static.

You need to use Arc instead of a reference, not in addition to a reference. Like this:

let window: Arc<winit::window::Window> =
    Arc::new(event_loop.create_window(window_attributes).unwrap());
...
let surface: wgpu::Surface<'static> =
    instance.create_surface(window.clone());

It's be useful if you had given the definition of State, but let me just pretend for a moment you have wgpu::Surface there directly (in reality, you'd put it in your State). Then the struct would be:

#[derive(Default)]
pub struct App {
    window: Option<Arc<winit::window::Window>>,
    surface: Option<wgpu::Surface<'static>>,
}

Notice that the struct now has no lifetime parameter. It contains no references, so it doesn't need one.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.