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

Update winit to 0.30 and refactor examples #5709

Draft
wants to merge 4 commits into
base: trunk
Choose a base branch
from

Conversation

anima-libera
Copy link

Description
Attempted to update winit to 0.30 and to refactor the examples accordingly. It does not work for the wasm32 target (sorry, I had no idea what I was doing, trying to block in web browsers, not used to that); but for non-wasm32 targets it seems to work (to the extent that I could test) so it may be useful in the end.

About the player crate, it is left untouched (so does not compile), but it can be ignored by changing this line into

winit = { version = "0.29", features = ["android-native-activity"], optional = true }

(which may be a bad idea, since all the other dependencies seem in sync in the workspace) in order to focus on the examples or run xtask now.

There are 5 modules to partially refactor:

  • the example framework
  • the hello_triangle, hello_windows and uniform_values examples (they do not use the framework to handle the creation of their window(s) and event loop)
  • the player crate

Note: I made the hello_triangle and hello_windows examples let the WGPU_ADAPTER_NAME env variable have a say in their choice of adapter (as the example framework does for all the examples that use it). (I did that to be able to run the examples that would no work with the default adapter on my machine, the issue there is most probably on my end and most probably not a concern.)

Note: There were mysterious numbers in the uniform_values example that could be labels referenced from some documentation that I could not find, they are mostly still above what they were labeling but since the code have moved around it may require whatever is referencing these labels to be partially rewritten to not rot.

Testing
This was tested on one machine (X11, Nvidia GPU) and works for the native target (some examples that use the framework were run, the three other examples were also run and they all seemed to work fine). Does not compile for wasm32. Even with player's winit version set back down to 0.29 (to run xtask), xtask's output seems negative (and some part of that negativity is probably about wasm32 target not even compiling in the example crate, hard to tell).

Checklist

  • Run cargo fmt.
  • Run cargo clippy. If applicable, add:
    • --target wasm32-unknown-unknown
    • --target wasm32-unknown-emscripten
  • Run cargo xtask test to run tests.
  • Add change to CHANGELOG.md. See simple instructions inside file.

@anima-libera
Copy link
Author

If it worked it could fix #5214

@anima-libera
Copy link
Author

The problem with wasm32 is that:

  • blocking on async is not allowed, so no trivial calling of async functions from sync functions
  • ApplicationHandler has no async methods
  • initializing the wgpu stuff requires the window, which can only be created in running event_loop, and we can't trivially do that in a method of ApplicationHandler since some of the wgpu stuff initialization is async

cwfitzgerald suggested that

[we] either need a custom event, or in redraw_requested, check to see if the spawn_local is finished, and if it is, take the state

@AustinMReppert
Copy link

Perhaps something like this for wasm

use std::{borrow::Cow, sync::Arc};
use winit::{
    application::ApplicationHandler,
    event::WindowEvent,
    event_loop::{ActiveEventLoop, EventLoop},
    window::{Window, WindowId},
};
use crate::framework;

#[cfg(target_arch = "wasm32")]
use web_time as time;

#[cfg(target_arch = "wasm32")]
use std::cell::RefCell;

#[cfg(target_arch = "wasm32")]
use winit::event_loop::ControlFlow;

#[cfg(target_arch = "wasm32")]
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);

#[cfg(target_arch = "wasm32")]
enum Message {
    Initialized(InitializedLoopState),
}

#[cfg(target_arch = "wasm32")]
thread_local! {
    pub static MESSAGE_QUEUE: RefCell<Vec<Message>> = RefCell::new(Vec::new());
}

struct LoopState {
    // See https://docs.rs/winit/latest/winit/changelog/v0_30/index.html#removed
    // for the recommended pratcice regarding Window creation (from which everything depends)
    // in winit >= 0.30.0.
    // The actual state is in an Option because its initialization is now delayed to after
    // the even loop starts running.
    state: Option<InitializedLoopState>
}

impl LoopState {
    fn new() -> LoopState {
        LoopState { state: None }
    }
}

struct InitializedLoopState {
    window: Arc<Window>,
    _instance: wgpu::Instance,
    surface: wgpu::Surface<'static>,
    _adapter: wgpu::Adapter,
    device: wgpu::Device,
    queue: wgpu::Queue,
    _shader: wgpu::ShaderModule,
    _pipeline_layout: wgpu::PipelineLayout,
    render_pipeline: wgpu::RenderPipeline,
    config: wgpu::SurfaceConfiguration,
}

impl InitializedLoopState {
    async fn new(window: Arc<Window>) -> InitializedLoopState {
        let mut size = window.inner_size();
        size.width = size.width.max(1);
        size.height = size.height.max(1);

        let instance = wgpu::Instance::default();

        let surface = instance.create_surface(window.clone()).unwrap();
        let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, Some(&surface))
            .await
            .expect("Failed to find an appropriate adapter");

        // Create the logical device and command queue
        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    label: None,
                    required_features: wgpu::Features::empty(),
                    // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
                    required_limits: wgpu::Limits::downlevel_webgl2_defaults()
                        .using_resolution(adapter.limits()),
                },
                None,
            )
            .await
            .expect("Failed to create device");

        // Load the shaders from disk
        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: None,
            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
        });

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: None,
            bind_group_layouts: &[],
            push_constant_ranges: &[],
        });

        let swapchain_capabilities = surface.get_capabilities(&adapter);
        let swapchain_format = swapchain_capabilities.formats[0];

        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: None,
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: "vs_main",
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: "fs_main",
                compilation_options: Default::default(),
                targets: &[Some(swapchain_format.into())],
            }),
            primitive: wgpu::PrimitiveState::default(),
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
        });

        let config = surface
            .get_default_config(&adapter, size.width, size.height)
            .unwrap();
        surface.configure(&device, &config);

        InitializedLoopState {
            window,
            _instance: instance,
            surface,
            _adapter: adapter,
            device,
            queue,
            _shader: shader,
            _pipeline_layout: pipeline_layout,
            render_pipeline,
            config,
        }
    }
}

impl ApplicationHandler for LoopState {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        if self.state.is_none() {
            #[allow(unused_mut)]
                let mut attributes = Window::default_attributes();
            #[cfg(target_arch = "wasm32")]
            {
                log::info!("Creating canvas element for wasm32 target");
                use wasm_bindgen::JsCast;
                use winit::platform::web::WindowAttributesExtWebSys;
                let canvas = web_sys::window()
                    .unwrap()
                    .document()
                    .unwrap()
                    .get_element_by_id("canvas")
                    .unwrap()
                    .dyn_into::<web_sys::HtmlCanvasElement>()
                    .unwrap();
                attributes = attributes.with_canvas(Some(canvas));
            }

            // Move this into the state struct?
            let window = Arc::new(event_loop.create_window(attributes).unwrap());
            #[cfg(not(target_arch = "wasm32"))]
            {
                self.state = Some(pollster::block_on(InitializedLoopState::new(window)));
            }
            #[cfg(target_arch = "wasm32")]
            {
                wasm_bindgen_futures::spawn_local(async move {
                    let initialized_state = InitializedLoopState::new(window).await;
                    MESSAGE_QUEUE.with_borrow_mut(|queue| {
                        queue.push(Message::Initialized(initialized_state))
                    });
                });

            }
        }
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        #[cfg(target_arch = "wasm32")]
        {
            MESSAGE_QUEUE.with_borrow_mut(|queue| {
                for message in queue.drain(..) {
                    match message {
                        Message::Initialized(state) => {
                            self.state = Some(state);
                        }
                    }
                }
            });
            event_loop.set_control_flow(ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME));
        }
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        _window_id: WindowId,
        event: WindowEvent,
    ) {
        if let Some(state) = self.state.as_mut().as_mut() {
            match event {
                WindowEvent::Resized(new_size) => {
                    // Reconfigure the surface with the new size
                    state.config.width = new_size.width.max(1);
                    state.config.height = new_size.height.max(1);
                    state.surface.configure(&state.device, &state.config);
                    // On macos the window needs to be redrawn manually after resizing
                    state.window.request_redraw();
                }
                WindowEvent::RedrawRequested => {
                    let frame = state
                        .surface
                        .get_current_texture()
                        .expect("Failed to acquire next swap chain texture");
                    let view = frame
                        .texture
                        .create_view(&wgpu::TextureViewDescriptor::default());
                    let mut encoder = state
                        .device
                        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
                    {
                        let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                            label: None,
                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                                view: &view,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
                                    store: wgpu::StoreOp::Store,
                                },
                            })],
                            depth_stencil_attachment: None,
                            timestamp_writes: None,
                            occlusion_query_set: None,
                        });
                        rpass.set_pipeline(&state.render_pipeline);
                        rpass.draw(0..3, 0..1);
                    }

                    state.queue.submit(Some(encoder.finish()));
                    frame.present();
                }
                WindowEvent::CloseRequested => event_loop.exit(),
                _ => {}
            }
        }
    }
}

pub fn main() {
    framework::init_logger();

    log::info!("Initializing...");

    let mut loop_state = LoopState::new();
    let event_loop = EventLoop::new().unwrap();

    log::info!("Entering event loop...");
    event_loop.run_app(&mut loop_state).unwrap();
}

@MrFastDie
Copy link

Perhaps something like this for wasm

use std::{borrow::Cow, sync::Arc};
use winit::{
    application::ApplicationHandler,
    event::WindowEvent,
    event_loop::{ActiveEventLoop, EventLoop},
    window::{Window, WindowId},
};
use crate::framework;

#[cfg(target_arch = "wasm32")]
use web_time as time;

#[cfg(target_arch = "wasm32")]
use std::cell::RefCell;

#[cfg(target_arch = "wasm32")]
use winit::event_loop::ControlFlow;

#[cfg(target_arch = "wasm32")]
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);

#[cfg(target_arch = "wasm32")]
enum Message {
    Initialized(InitializedLoopState),
}

#[cfg(target_arch = "wasm32")]
thread_local! {
    pub static MESSAGE_QUEUE: RefCell<Vec<Message>> = RefCell::new(Vec::new());
}

struct LoopState {
    // See https://docs.rs/winit/latest/winit/changelog/v0_30/index.html#removed
    // for the recommended pratcice regarding Window creation (from which everything depends)
    // in winit >= 0.30.0.
    // The actual state is in an Option because its initialization is now delayed to after
    // the even loop starts running.
    state: Option<InitializedLoopState>
}

impl LoopState {
    fn new() -> LoopState {
        LoopState { state: None }
    }
}

struct InitializedLoopState {
    window: Arc<Window>,
    _instance: wgpu::Instance,
    surface: wgpu::Surface<'static>,
    _adapter: wgpu::Adapter,
    device: wgpu::Device,
    queue: wgpu::Queue,
    _shader: wgpu::ShaderModule,
    _pipeline_layout: wgpu::PipelineLayout,
    render_pipeline: wgpu::RenderPipeline,
    config: wgpu::SurfaceConfiguration,
}

impl InitializedLoopState {
    async fn new(window: Arc<Window>) -> InitializedLoopState {
        let mut size = window.inner_size();
        size.width = size.width.max(1);
        size.height = size.height.max(1);

        let instance = wgpu::Instance::default();

        let surface = instance.create_surface(window.clone()).unwrap();
        let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, Some(&surface))
            .await
            .expect("Failed to find an appropriate adapter");

        // Create the logical device and command queue
        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    label: None,
                    required_features: wgpu::Features::empty(),
                    // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
                    required_limits: wgpu::Limits::downlevel_webgl2_defaults()
                        .using_resolution(adapter.limits()),
                },
                None,
            )
            .await
            .expect("Failed to create device");

        // Load the shaders from disk
        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: None,
            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
        });

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: None,
            bind_group_layouts: &[],
            push_constant_ranges: &[],
        });

        let swapchain_capabilities = surface.get_capabilities(&adapter);
        let swapchain_format = swapchain_capabilities.formats[0];

        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: None,
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: "vs_main",
                buffers: &[],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: "fs_main",
                compilation_options: Default::default(),
                targets: &[Some(swapchain_format.into())],
            }),
            primitive: wgpu::PrimitiveState::default(),
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
        });

        let config = surface
            .get_default_config(&adapter, size.width, size.height)
            .unwrap();
        surface.configure(&device, &config);

        InitializedLoopState {
            window,
            _instance: instance,
            surface,
            _adapter: adapter,
            device,
            queue,
            _shader: shader,
            _pipeline_layout: pipeline_layout,
            render_pipeline,
            config,
        }
    }
}

impl ApplicationHandler for LoopState {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        if self.state.is_none() {
            #[allow(unused_mut)]
                let mut attributes = Window::default_attributes();
            #[cfg(target_arch = "wasm32")]
            {
                log::info!("Creating canvas element for wasm32 target");
                use wasm_bindgen::JsCast;
                use winit::platform::web::WindowAttributesExtWebSys;
                let canvas = web_sys::window()
                    .unwrap()
                    .document()
                    .unwrap()
                    .get_element_by_id("canvas")
                    .unwrap()
                    .dyn_into::<web_sys::HtmlCanvasElement>()
                    .unwrap();
                attributes = attributes.with_canvas(Some(canvas));
            }

            // Move this into the state struct?
            let window = Arc::new(event_loop.create_window(attributes).unwrap());
            #[cfg(not(target_arch = "wasm32"))]
            {
                self.state = Some(pollster::block_on(InitializedLoopState::new(window)));
            }
            #[cfg(target_arch = "wasm32")]
            {
                wasm_bindgen_futures::spawn_local(async move {
                    let initialized_state = InitializedLoopState::new(window).await;
                    MESSAGE_QUEUE.with_borrow_mut(|queue| {
                        queue.push(Message::Initialized(initialized_state))
                    });
                });

            }
        }
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        #[cfg(target_arch = "wasm32")]
        {
            MESSAGE_QUEUE.with_borrow_mut(|queue| {
                for message in queue.drain(..) {
                    match message {
                        Message::Initialized(state) => {
                            self.state = Some(state);
                        }
                    }
                }
            });
            event_loop.set_control_flow(ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME));
        }
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        _window_id: WindowId,
        event: WindowEvent,
    ) {
        if let Some(state) = self.state.as_mut().as_mut() {
            match event {
                WindowEvent::Resized(new_size) => {
                    // Reconfigure the surface with the new size
                    state.config.width = new_size.width.max(1);
                    state.config.height = new_size.height.max(1);
                    state.surface.configure(&state.device, &state.config);
                    // On macos the window needs to be redrawn manually after resizing
                    state.window.request_redraw();
                }
                WindowEvent::RedrawRequested => {
                    let frame = state
                        .surface
                        .get_current_texture()
                        .expect("Failed to acquire next swap chain texture");
                    let view = frame
                        .texture
                        .create_view(&wgpu::TextureViewDescriptor::default());
                    let mut encoder = state
                        .device
                        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
                    {
                        let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                            label: None,
                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                                view: &view,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
                                    store: wgpu::StoreOp::Store,
                                },
                            })],
                            depth_stencil_attachment: None,
                            timestamp_writes: None,
                            occlusion_query_set: None,
                        });
                        rpass.set_pipeline(&state.render_pipeline);
                        rpass.draw(0..3, 0..1);
                    }

                    state.queue.submit(Some(encoder.finish()));
                    frame.present();
                }
                WindowEvent::CloseRequested => event_loop.exit(),
                _ => {}
            }
        }
    }
}

pub fn main() {
    framework::init_logger();

    log::info!("Initializing...");

    let mut loop_state = LoopState::new();
    let event_loop = EventLoop::new().unwrap();

    log::info!("Entering event loop...");
    event_loop.run_app(&mut loop_state).unwrap();
}

Does this actually work? I can't run it with the latest versions.

I came up with a different approach as winit says the following about run_app:

Web applications are recommended to use ^1 instead of run_app() to avoid the need for the Javascript exception trick, and to make it clearer that the event loop runs asynchronously (via the browser's own, internal, event loop) and doesn't block the current thread of execution like it does on other platforms.
This function won't be available with target_feature = "exception-handling".
^1: EventLoopExtWebSys::spawn_app() is only available on Web.
use std::borrow::Cow;
use std::panic;
use std::sync::Arc;
use std::sync::Mutex;

use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use web_sys::console;
use wgpu::Device;
use wgpu::Instance;
use wgpu::RenderPipeline;
use wgpu::Surface;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::event_loop::ControlFlow;
use winit::event_loop::EventLoop;
use winit::event_loop::EventLoopProxy;
use winit::platform::web::EventLoopExtWebSys;
use winit::platform::web::WindowAttributesExtWebSys;
use winit::window::Window;
use winit::window::WindowId;

#[wasm_bindgen]
#[allow(dead_code)]
struct State {
    proxy: EventLoopProxy<UserEvent>,
}

#[derive(Debug)]
pub enum UserEvent {
    WakeUp,
    Initialized(
        Surface<'static>,
        Device,
        wgpu::Queue,
        wgpu::SurfaceConfiguration,
        RenderPipeline,
    ),
}

#[wasm_bindgen]
impl State {
    #[wasm_bindgen(constructor)]
    pub async fn new(canvas: web_sys::HtmlCanvasElement) -> Result<State, JsValue> {
        panic::set_hook(Box::new(console_error_panic_hook::hook));

        let event_loop = EventLoop::<UserEvent>::with_user_event()
            .build()
            .expect("Failed to create event loop");
        event_loop.set_control_flow(ControlFlow::Poll); // TODO wait

        let event_loop_proxy = event_loop.create_proxy();

        let app = App::create::<UserEvent>(canvas, Arc::new(event_loop_proxy.clone())).await;

        event_loop.spawn_app(app);
    
        Ok(State {
            proxy: event_loop_proxy,
        })
    }
}

struct App {
    window: Option<Arc<Window>>,
    surface: Option<Surface<'static>>,
    device: Option<Device>,
    queue: Option<Arc<Mutex<wgpu::Queue>>>,
    config: Option<wgpu::SurfaceConfiguration>,
    render_pipeline: Option<RenderPipeline>,
    canvas: web_sys::HtmlCanvasElement,
    proxy: Arc<EventLoopProxy<UserEvent>>,
    initialized: bool,
}

impl App {
    pub async fn create<T>(
        canvas: web_sys::HtmlCanvasElement,
        proxy: Arc<EventLoopProxy<UserEvent>>,
    ) -> Self {
        App {
            canvas,
            window: None,
            surface: None,
            device: None,
            queue: None,
            config: None,
            render_pipeline: None,
            proxy,
            initialized: false,
        }
    }
}

async fn init(
    //app: &App,
    w: Arc<Window>,
) -> (
    Surface<'static>,
    Device,
    wgpu::Queue,
    wgpu::SurfaceConfiguration,
    RenderPipeline,
) {
    let mut size = w.inner_size();
    size.width = size.width.max(1);
    size.height = size.height.max(1);

    let instance = Instance::default();
    let surface = instance
        .create_surface(w)
        .map_err(|e| console::error_1(&JsValue::from_str(&format!("CreateSurfaceError: {:?}", e))))
        .unwrap();

    let adapter = instance
        .request_adapter(&wgpu::RequestAdapterOptions {
            power_preference: wgpu::PowerPreference::default(),
            force_fallback_adapter: false,
            compatible_surface: Some(&surface),
        })
        .await
        .expect("Failed to find an appropriate adapter");

    // Create the logical device and command queue
    let (device, queue) = adapter
        .request_device(
            &wgpu::DeviceDescriptor {
                label: None,
                required_features: wgpu::Features::empty(),
                // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
                required_limits: wgpu::Limits::downlevel_webgl2_defaults()
                    .using_resolution(adapter.limits()),
            },
            None,
        )
        .await
        .expect("Failed to create device");

    // Load the shaders from disk
    let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
        label: None,
        source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
    });

    let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
        label: None,
        bind_group_layouts: &[],
        push_constant_ranges: &[],
    });

    let swapchain_capabilities = surface.get_capabilities(&adapter);
    let swapchain_format = swapchain_capabilities.formats[0];

    let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
        label: None,
        layout: Some(&pipeline_layout),
        vertex: wgpu::VertexState {
            module: &shader,
            entry_point: "vs_main",
            buffers: &[],
            compilation_options: Default::default(),
        },
        fragment: Some(wgpu::FragmentState {
            module: &shader,
            entry_point: "fs_main",
            compilation_options: Default::default(),
            targets: &[Some(swapchain_format.into())],
        }),
        primitive: wgpu::PrimitiveState::default(),
        depth_stencil: None,
        multisample: wgpu::MultisampleState::default(),
        multiview: None,
    });

    let config = surface
        .get_default_config(&adapter, size.width, size.height)
        .unwrap();
    surface.configure(&device, &config);

    return (surface, device, queue, config, render_pipeline);
}

impl ApplicationHandler<UserEvent> for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        console::debug_1(&JsValue::from_str("Resumed"));

        if self.initialized {
            return;
        }

        let window_attributes = WindowAttributesExtWebSys::with_canvas(
            Window::default_attributes(),
            Some(self.canvas.clone()),
        )
        .with_append(true);

        let window = event_loop.create_window(window_attributes.clone()).unwrap();

        // This seems to need to be done before creating a surface.
        window.request_redraw();
        self.window = Some(Arc::new(window));

        let window_clone = self.window.clone().unwrap();
        let proxy_clone = self.proxy.clone();

        wasm_bindgen_futures::spawn_local(async move {
            let x = init(window_clone).await;
            proxy_clone
                .send_event(UserEvent::Initialized(x.0, x.1, x.2, x.3, x.4))
                .expect("Initialized web");
        });
    }
    fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
        console::debug_1(&JsValue::from_str(
            format!("User event: {:?}", event).as_str(),
        ));

        match event {
            UserEvent::WakeUp => {
                console::debug_1(&JsValue::from_str("WakeUp"));
            }
            UserEvent::Initialized(surface, device, queue, config, render_pipeline) => {
                console::debug_1(&JsValue::from_str("Initialized"));

                self.surface = Some(surface);
                self.device = Some(device);
                self.queue = Some(Arc::new(Mutex::new(queue)));
                self.config = Some(config);
                self.render_pipeline = Some(render_pipeline);
                self.initialized = true;
            }
        }
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
        if !self.initialized {
            console::error_1(&JsValue::from_str("App is not initialized, exiting"));
            return;
        }

        console::debug_1(&JsValue::from_str("EventReceived"));
        match event {
            WindowEvent::CloseRequested => {
                event_loop.exit();
            }
            WindowEvent::Resized(new_size) => {
                console::debug_1(&JsValue::from_str("Resizing"));

                let mut config = self.config.clone().unwrap();
                config.width = new_size.width.max(1);
                config.height = new_size.height.max(1);

                self.surface
                    .as_ref()
                    .unwrap()
                    .configure(self.device.as_ref().unwrap(), &config);

                self.config = Some(config);

                // On macos the window needs to be redrawn manually after resizing
                self.window.as_ref().unwrap().request_redraw();
            }
            WindowEvent::RedrawRequested => {
                console::debug_1(&JsValue::from_str("RedrawRequested"));
                let frame = self
                    .surface
                    .as_ref()
                    .unwrap()
                    .get_current_texture()
                    .expect("Failed to acquire next swap chain texture");

                let view = frame
                    .texture
                    .create_view(&wgpu::TextureViewDescriptor::default());

                let mut encoder = self
                    .device
                    .as_ref()
                    .unwrap()
                    .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
                {
                    let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: None,
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: &view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
                                store: wgpu::StoreOp::Store,
                            },
                        })],
                        depth_stencil_attachment: None,
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    });
                    rpass.set_pipeline(self.render_pipeline.as_ref().unwrap());
                    rpass.draw(0..3, 0..1);
                }

                console::debug_1(&JsValue::from_str("Adding to queue"));
                self.queue
                    .as_mut()
                    .unwrap()
                    .lock()
                    .unwrap()
                    .submit(Some(encoder.finish()));
                console::debug_1(&JsValue::from_str("Submitted queue"));
                frame.present();
            }
            _ => (),
        }
    }
}

However I get an error from winit:

[Error] panicked at /Users/daniel/.cargo/registry/src/index.crates.io-6f17d22bba15001f/winit-0.30.3/src/platform_impl/web/event_loop/runner.rs:627:30:
already borrowed: BorrowMutError

Stack:

@http://localhost:5173/src/wasm/rusty_cube_wasm.js:353:30
<?>.wasm-function[1285]@[wasm code]
<?>.wasm-function[515]@[wasm code]
<?>.wasm-function[921]@[wasm code]
<?>.wasm-function[790]@[wasm code]
<?>.wasm-function[335]@[wasm code]
<?>.wasm-function[409]@[wasm code]
<?>.wasm-function[1001]@[wasm code]
<?>.wasm-function[1086]@[wasm code]
__wbg_adapter_26@http://localhost:5173/src/wasm/rusty_cube_wasm.js:215:132
real@http://localhost:5173/src/wasm/rusty_cube_wasm.js:200:21


	(anonyme Funktion) (rusty_cube_wasm.js:369)
	<?>.wasm-function[1285]
	<?>.wasm-function[515]
	<?>.wasm-function[921]
	<?>.wasm-function[790]
	<?>.wasm-function[335]
	<?>.wasm-function[409]
	<?>.wasm-function[1001]
	<?>.wasm-function[1086]
	__wbg_adapter_26 (rusty_cube_wasm.js:215:133)
	real (rusty_cube_wasm.js:200)

That seems to occur when I'm trying to push the triangle to the queue:

self.queue
                    .as_mut()
                    .unwrap()
                    .lock()
                    .unwrap()
                    .submit(Some(encoder.finish()));

Someone probably has an idea? I can't really do anything with my weak rust knowledge here...

@MrFastDie
Copy link

Ok, after further investigations this seems to be related to macOS. I tried redrawing the window after creating including a sleep but it does not fix the queue error.

On linux it seems to work but sometimes I do get an error related to Vulkan:

[4152:4311:0623/183230.498284:ERROR:vulkan_swap_chain.cc(431)] vkAcquireNextImageKHR() hangs.
[4152:4152:0623/183230.498716:ERROR:gpu_service_impl.cc(1125)] Exiting GPU process because some drivers can't recover from errors. GPU process will restart shortly.
[4115:4115:0623/183230.524352:ERROR:gpu_process_host.cc(1002)] GPU process exited unexpectedly: exit_code=8704

Could be when I am using i3 and I start the browser in fullscreen. When its not on full size it sometimes does render but sadly not always...

@MarijnS95 MarijnS95 mentioned this pull request Aug 23, 2024
6 tasks
@lexi-the-cute
Copy link

In reply to: #5709 (comment)

The issue is not winit. WGPU on WASM is still broken regardless of winit version as long as WGPU is >=22.0.0. WGPU=0.20.1 works just fine on WASM with any version of winit>=0.29.15. I have not tested before 0.29.15, but have tested both winit=0.29.15 and winit=0.30.5 with both WGPU=0.20.1 and WGPU>=22.0.0. Something changed with WGPU between WGPU=0.20.1 and WGPU=22.0.0 with how the adapter works. WASM does not work on WGPU>=22.0.0 both with and without the webgl feature

@Jelmerta
Copy link

Jelmerta commented Nov 4, 2024

inspired by this pr i did get wgpu on wasm working with wgpu 22.1 and winit 30.5 in my game:
https://github.com/Jelmerta/Kloenk/blob/main/src/application.rs

@PWhiddy
Copy link
Contributor

PWhiddy commented Dec 27, 2024

Where does this fall on the list of priorities?
Are we waiting for more breaking changes on winit first?

@cwfitzgerald
Copy link
Member

We'd love to have this land, it seems like we need someone to champion getting this over the line.

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

Successfully merging this pull request may close these issues.

8 participants