From 1f9ed1148c883178809bf7616a210dd11219fee4 Mon Sep 17 00:00:00 2001 From: Timo Sonnenschein Date: Sat, 9 May 2026 16:42:28 +0200 Subject: [PATCH] chore(deps): upgrade wgpu to 29.0.3 --- templates/apps/wgpu/Cargo.toml.hbs | 15 +- templates/apps/wgpu/README.md | 12 +- templates/apps/wgpu/src/lib.rs.hbs | 382 +++++++++++++++++------------ 3 files changed, 238 insertions(+), 171 deletions(-) diff --git a/templates/apps/wgpu/Cargo.toml.hbs b/templates/apps/wgpu/Cargo.toml.hbs index 5061cede..fd807202 100644 --- a/templates/apps/wgpu/Cargo.toml.hbs +++ b/templates/apps/wgpu/Cargo.toml.hbs @@ -12,15 +12,14 @@ name = "{{app.name}}-desktop" path = "gen/bin/desktop.rs" [dependencies] -futures = "0.3.8" -mobile-entry-point = "0.1.0" -wgpu = "0.6.0" -winit = "0.23.0" +log = "0.4.29" +pollster = "0.4.0" +wgpu = { version = "29.0.3", features = ["spirv"] } [target.'cfg(target_os = "android")'.dependencies] -android_logger = "0.9.0" -log = "0.4.11" -ndk-glue = "0.2.1" +winit = { version = "0.30.13", features = ["android-native-activity"] } +android_logger = "0.15.1" [target.'cfg(not(target_os = "android"))'.dependencies] -wgpu-subscriber = "0.1.0" +winit = "0.30.13" +simple_logger = "5.2.0" diff --git a/templates/apps/wgpu/README.md b/templates/apps/wgpu/README.md index 36fb8c17..4b1bcd62 100644 --- a/templates/apps/wgpu/README.md +++ b/templates/apps/wgpu/README.md @@ -2,11 +2,11 @@ This is just the [`wgpu-rs` triangle example](https://github.com/gfx-rs/wgpu-rs/blob/v0.6/examples/hello-triangle/main.rs) with a handful of small changes, which I'll outline below: -- Annotated `main` with `#[mobile_entry_point]`, which generates all the `extern` functions we need for mobile. Note that the name of the function doesn't actually matter for this. -- Changes conditionally compiled on Android: - - Use `android_logger` instead of `wgpu-subscriber` - - Use `Rgba8UnormSrgb` instead of `Bgra8UnormSrgb` (ideally, the supported format would be detected dynamically instead) - - Use `std::thread::sleep` to shoddily workaround [`raw_window_handle` requirements](https://github.com/rust-windowing/winit/issues/1588) - - Render directly upon `MainEventsCleared` instead of calling `request_redraw`, since [winit doesn't implement that method on Android yet](https://github.com/rust-windowing/winit/issues/1723) +- Render a red triangle instead of just clearing the screen green. +- Add the entry points `android_main` for android +- Annotate `start_app` as extern, so that i can be used via FFI on iOS +- Add logging via [`simple_logger`](https://crates.io/crates/simple_logger) and [`android_logger`](https://crates.io/crates/android_logger) +- Reduce the limit `max_inter_stage_shader_variables` from 16 to 15 as a tempory fix for [this issue](https://github.com/gfx-rs/wgpu/issues/9386 is fixed +) To run this on desktop, just do `cargo run` like normal! For mobile, use `cargo android run` and `cargo apple run` respectively (or use `cargo android open`/`cargo apple open` to open in Android Studio and Xcode respectively). diff --git a/templates/apps/wgpu/src/lib.rs.hbs b/templates/apps/wgpu/src/lib.rs.hbs index bd563888..4bb6b37f 100644 --- a/templates/apps/wgpu/src/lib.rs.hbs +++ b/templates/apps/wgpu/src/lib.rs.hbs @@ -1,178 +1,246 @@ -use mobile_entry_point::mobile_entry_point; +use std::sync::Arc; + use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::{Window, WindowBuilder}, + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}, + window::{Window, WindowId}, }; -// TODO: how can we detect supported formats dynamically? -#[cfg(target_os = "android")] -const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb; -#[cfg(not(target_os = "android"))] -const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; - -async fn run(event_loop: EventLoop<()>, window: Window, swapchain_format: wgpu::TextureFormat) { - let size = window.inner_size(); - let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); - let surface = unsafe { instance.create_surface(&window) }; - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::Default, - // Request an adapter which can render to our surface - compatible_surface: Some(&surface), - }) - .await - .expect("Failed to find an appropiate adapter"); - - // Create the logical device and command queue - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - shader_validation: true, +struct State { + instance: wgpu::Instance, + window: Arc, + device: wgpu::Device, + queue: wgpu::Queue, + size: winit::dpi::PhysicalSize, + surface: wgpu::Surface<'static>, + surface_format: wgpu::TextureFormat, + render_pipeline: wgpu::RenderPipeline, +} + +impl State { + async fn new(display: OwnedDisplayHandle, window: Arc) -> State { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle( + Box::new(display), + )); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .expect("Failed to find an appropiate adapter"); + // FIXME(iOS): change to `wgpu::DeviceDescriptor::default()` when https://github.com/gfx-rs/wgpu/issues/9386 is fixed + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + required_limits: wgpu::Limits { + max_inter_stage_shader_variables: 15, + ..Default::default() + }, + ..Default::default() + }) + .await + .expect("Failed to create device"); + + let size = window.inner_size(); + + let surface = instance.create_surface(window.clone()).unwrap(); + let cap = surface.get_capabilities(&adapter); + let surface_format = cap.formats[0]; + + // renderer + let vs_module = device.create_shader_module(wgpu::include_spirv!("shader.vert.spv")); + let fs_module = device.create_shader_module(wgpu::include_spirv!("shader.frag.spv")); + + let pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor::default()); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &vs_module, + entry_point: Some("main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + buffers: &[], }, - None, - ) - .await - .expect("Failed to create device"); - - // Load the shaders from disk - let vs_module = device.create_shader_module(wgpu::include_spirv!("shader.vert.spv")); - let fs_module = device.create_shader_module(wgpu::include_spirv!("shader.frag.spv")); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - vertex_stage: wgpu::ProgrammableStageDescriptor { - module: &vs_module, - entry_point: "main", - }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { - module: &fs_module, - entry_point: "main", - }), - // Use the default rasterizer state: no culling, no depth bias - rasterization_state: None, - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[swapchain_format.into()], - depth_stencil_state: None, - vertex_state: wgpu::VertexStateDescriptor { - index_format: wgpu::IndexFormat::Uint16, - vertex_buffers: &[], - }, - sample_count: 1, - sample_mask: !0, - alpha_to_coverage_enabled: false, - }); - - let mut sc_desc = wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, - format: swapchain_format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Mailbox, - }; - - let mut swap_chain = device.create_swap_chain(&surface, &sc_desc); - - event_loop.run(move |event, _, control_flow| { - // Have the closure take ownership of the resources. - // `event_loop.run` never returns, therefore we must do this to ensure - // the resources are properly cleaned up. - let _ = ( - &instance, - &adapter, - &vs_module, - &fs_module, - &pipeline_layout, - ); - - let mut render = || { - let frame = swap_chain - .get_current_frame() - .expect("Failed to acquire next swap chain texture") - .output; - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { - attachment: &frame.view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, - }, - }], - depth_stencil_attachment: None, - }); - rpass.set_pipeline(&render_pipeline); - rpass.draw(0..3, 0..1); - } + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment: Some(wgpu::FragmentState { + module: &fs_module, + entry_point: Some("main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: &[Some(wgpu::ColorTargetState { + format: surface_format, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview_mask: None, + cache: None, + }); - queue.submit(Some(encoder.finish())); + let state = State { + instance, + window, + device, + queue, + size, + surface, + surface_format, + render_pipeline, + }; + + state.configure_surface(); + + state + } + + fn get_window(&self) -> &Window { + &self.window + } + + fn configure_surface(&self) { + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: self.surface_format, + view_formats: vec![self.surface_format.add_srgb_suffix()], + alpha_mode: wgpu::CompositeAlphaMode::Auto, + width: self.size.width, + height: self.size.height, + desired_maximum_frame_latency: 2, + present_mode: wgpu::PresentMode::AutoVsync, + }; + self.surface.configure(&self.device, &surface_config); + } + + fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + self.size = new_size; + + self.configure_surface(); + } + + fn render(&mut self) { + let surface_texture = match self.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(texture) => texture, + wgpu::CurrentSurfaceTexture::Occluded | wgpu::CurrentSurfaceTexture::Timeout => return, + wgpu::CurrentSurfaceTexture::Suboptimal(texture) => { + drop(texture); + self.configure_surface(); + return; + } + wgpu::CurrentSurfaceTexture::Outdated => { + self.configure_surface(); + return; + } + wgpu::CurrentSurfaceTexture::Validation => { + unreachable!("No error scope registered, so validation errors will panic") + } + wgpu::CurrentSurfaceTexture::Lost => { + self.surface = self.instance.create_surface(self.window.clone()).unwrap(); + self.configure_surface(); + return; + } }; - *control_flow = ControlFlow::Poll; + let texture_view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor { + format: Some(self.surface_format.add_srgb_suffix()), + ..Default::default() + }); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + { + let mut renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &texture_view, + depth_slice: None, + 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, + multiview_mask: None, + }); + renderpass.set_pipeline(&self.render_pipeline); + renderpass.draw(0..3, 0..1); + } + + self.queue.submit([encoder.finish()]); + self.window.pre_present_notify(); + + surface_texture.present(); + + } +} + +#[derive(Default)] +struct App { + state: Option, +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let attributes = Window::default_attributes().with_title("{{app.stylized-name}}"); + + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let attributes = attributes.with_inner_size(winit::dpi::LogicalSize::new(1280, 720)); + let window = Arc::new(event_loop.create_window(attributes).unwrap()); + + let state = pollster::block_on(State::new( + event_loop.owned_display_handle(), + window.clone(), + )); + self.state = Some(state); + + window.request_redraw(); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + let state = self.state.as_mut().unwrap(); match event { - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - // Recreate the swap chain with the new size - sc_desc.width = size.width; - sc_desc.height = size.height; - swap_chain = device.create_swap_chain(&surface, &sc_desc); + WindowEvent::CloseRequested => { + event_loop.exit(); } - Event::MainEventsCleared => { - if cfg!(target_os = "android") { - // TODO: `request_redraw` isn't impld in winit's Android backend - // https://github.com/rust-windowing/winit/issues/1723 - render(); - } + WindowEvent::RedrawRequested => { + state.render(); + state.get_window().request_redraw(); } - Event::RedrawRequested(_) => render(), - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - _ => {} + WindowEvent::Resized(size) => { + state.resize(size); + } + _ => (), } - }); + } +} + +#[cfg(not(target_os = "android"))] +#[unsafe(no_mangle)] +#[inline(never)] +pub extern "C" fn start_app() { + simple_logger::SimpleLogger::new().init().unwrap(); + + let event_loop = EventLoop::new().unwrap(); + event_loop.run_app(&mut App::default()).unwrap(); } #[cfg(target_os = "android")] -fn init_logging() { +#[unsafe(no_mangle)] +fn android_main(app: winit::platform::android::activity::AndroidApp) { android_logger::init_once( android_logger::Config::default() - .with_min_level(log::Level::Info) + .with_max_level(log::LevelFilter::Trace) .with_tag("{{app.name}}"), ); -} -#[cfg(not(target_os = "android"))] -fn init_logging() { - wgpu_subscriber::initialize_default_subscriber(None); -} + use winit::platform::android::EventLoopBuilderExtAndroid; -#[mobile_entry_point] -fn main() { - init_logging(); - let event_loop = EventLoop::new(); - let window = WindowBuilder::new() - .with_title("{{app.stylized-name}}") - .build(&event_loop) - .unwrap(); - // TODO: actually handle this correctly - // https://github.com/rust-windowing/winit/issues/1588 - #[cfg(target_os = "android")] - std::thread::sleep_ms(2000); - futures::executor::block_on(run(event_loop, window, FORMAT)); + let event_loop = EventLoop::builder().with_android_app(app).build().unwrap(); + event_loop.run_app(&mut App::default()).unwrap(); }