From 11c3af686f5b790dcabd3870b9385285dc58ab45 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 14 Aug 2022 12:01:37 +0100 Subject: [PATCH] examples: winit simplifications * Since we can rely on `Resumed` events for all platforms with Winit 0.27 we can remove the alternative `NewEvents` path for initializing state. * Removes some unnecessary abstraction for the app state. * Bumps agdk-egui to egui 0.19 (Winit 0.27) --- examples/agdk-egui/Cargo.toml | 14 +- examples/agdk-egui/src/lib.rs | 20 +- examples/agdk-winit-wgpu/src/lib.rs | 351 ++++++++++++--------------- examples/na-winit-wgpu/src/lib.rs | 358 +++++++++++++--------------- 4 files changed, 329 insertions(+), 414 deletions(-) diff --git a/examples/agdk-egui/Cargo.toml b/examples/agdk-egui/Cargo.toml index 31ebc15..e9a6b8d 100644 --- a/examples/agdk-egui/Cargo.toml +++ b/examples/agdk-egui/Cargo.toml @@ -11,21 +11,15 @@ log = "0.4" winit = "0.27" wgpu = "0.13.0" pollster = "0.2" -egui = "0.18" -egui-wgpu = { version = "0.18", features = [ "winit" ] } -egui-winit = { version = "0.18", default-features = false } -egui_demo_lib = "0.18" +egui = "0.19" +egui-wgpu = { version = "0.19", features = [ "winit" ] } +egui-winit = { version = "0.19", default-features = false } +egui_demo_lib = "0.19" [patch.crates-io] winit = { git = "https://github.com/rib/winit", branch = "android-activity-0.27" } #winit = { path="../../../winit" } -egui = { git = "https://github.com/budde25/egui", branch = "bump-winit" } -egui-wgpu = { git = "https://github.com/budde25/egui", branch = "bump-winit" } -egui-winit = { git = "https://github.com/budde25/egui", branch = "bump-winit" } -egui_extras = { git = "https://github.com/budde25/egui", branch = "bump-winit" } -egui_demo_lib = { git = "https://github.com/budde25/egui", branch = "bump-winit" } - #egui = { git = "https://github.com/emilk/egui" } #egui-wgpu = { git = "https://github.com/emilk/egui" } #egui-winit = { git = "https://github.com/emilk/egui" } diff --git a/examples/agdk-egui/src/lib.rs b/examples/agdk-egui/src/lib.rs index dc21248..92a9f41 100644 --- a/examples/agdk-egui/src/lib.rs +++ b/examples/agdk-egui/src/lib.rs @@ -1,10 +1,7 @@ -use log::Level; use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget}; #[cfg(target_os = "android")] use android_activity::AndroidApp; -#[cfg(target_os = "android")] -use winit::platform::android::EventLoopBuilderExtAndroid; use winit::event_loop::ControlFlow; @@ -87,15 +84,7 @@ fn _main(event_loop: EventLoop) { let mut window: Option = None; let mut egui_demo_windows = egui_demo_lib::DemoWindows::default(); - // On most platforms we can immediately create a winit window. On Android we manage - // window + surface state according to Resumed/Paused events. - #[cfg(not(target_os = "android"))] - { - window = Some(create_window(&event_loop, &mut state, &mut painter)); - } - event_loop.run(move |event, event_loop, control_flow| match event { - #[cfg(target_os = "android")] Resumed => match window { None => { window = Some(create_window(event_loop, &mut state, &mut painter)); @@ -105,12 +94,9 @@ fn _main(event_loop: EventLoop) { window.request_redraw(); } }, - - #[cfg(target_os = "android")] Suspended => { window = None; } - RedrawRequested(..) => { if let Some(window) = window.as_ref() { let raw_input = state.take_egui_input(window); @@ -154,10 +140,13 @@ fn _main(event_loop: EventLoop) { }); } +#[allow(dead_code)] #[cfg(target_os = "android")] #[no_mangle] fn android_main(app: AndroidApp) { - android_logger::init_once(android_logger::Config::default().with_min_level(Level::Trace)); + use winit::platform::android::EventLoopBuilderExtAndroid; + + android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace)); let event_loop = EventLoopBuilder::with_user_event() .with_android_app(app) @@ -165,6 +154,7 @@ fn android_main(app: AndroidApp) { _main(event_loop); } +#[allow(dead_code)] #[cfg(not(target_os = "android"))] fn main() { env_logger::builder() diff --git a/examples/agdk-winit-wgpu/src/lib.rs b/examples/agdk-winit-wgpu/src/lib.rs index e882e21..f8bb09b 100644 --- a/examples/agdk-winit-wgpu/src/lib.rs +++ b/examples/agdk-winit-wgpu/src/lib.rs @@ -1,9 +1,6 @@ use std::borrow::Cow; -use std::ops::Deref; -use std::sync::{Arc, RwLock}; use log::trace; -use log::Level; use wgpu::TextureFormat; use wgpu::{Adapter, Device, Instance, PipelineLayout, Queue, RenderPipeline, ShaderModule}; @@ -15,8 +12,6 @@ use winit::{ #[cfg(target_os = "android")] use android_activity::AndroidApp; -#[cfg(target_os = "android")] -use winit::platform::android::EventLoopBuilderExtAndroid; struct RenderState { device: Device, @@ -32,228 +27,202 @@ struct SurfaceState { surface: wgpu::Surface, } -struct AppInner { +struct App { instance: Instance, adapter: Option, surface_state: Option, render_state: Option, } -struct App { - inner: Arc>, -} - impl App { fn new(instance: Instance) -> Self { Self { - inner: Arc::new(RwLock::new(AppInner { - instance, - adapter: None, - surface_state: None, - render_state: None, - })), + instance, + adapter: None, + surface_state: None, + render_state: None, } } } -impl Deref for App { - type Target = Arc>; - fn deref(&self) -> &Self::Target { - &self.inner + +impl App { + fn create_surface(&mut self, event_loop: &EventLoopWindowTarget) { + let window = winit::window::Window::new(&event_loop).unwrap(); + trace!("WGPU: creating surface for native window"); + let surface = unsafe { self.instance.create_surface(&window) }; + self.surface_state = Some(SurfaceState { window, surface }); } -} -async fn init_render_state(adapter: &Adapter, target_format: TextureFormat) -> RenderState { - trace!("Initializing render state"); + async fn init_render_state(adapter: &Adapter, target_format: TextureFormat) -> RenderState { + trace!("Initializing render state"); - trace!("WGPU: requesting device"); - // Create the logical device and command queue - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - 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. - limits: wgpu::Limits::downlevel_webgl2_defaults() - .using_resolution(adapter.limits()), - }, - None, - ) - .await - .expect("Failed to create device"); - - trace!("WGPU: loading shader"); - // 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"))), - }); - - trace!("WGPU: creating pipeline layout"); - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - trace!("WGPU: creating render pipeline"); - 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: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(target_format.into())], - }), - primitive: wgpu::PrimitiveState::default(), - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - }); - - RenderState { - device, - queue, - _shader: shader, - target_format, - _pipeline_layout: pipeline_layout, - render_pipeline, - } -} - -fn configure_surface_swapchain(render_state: &RenderState, surface_state: &SurfaceState) { - let swapchain_format = render_state.target_format; - let size = surface_state.window.inner_size(); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: swapchain_format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Mailbox, - //present_mode: wgpu::PresentMode::Fifo, - }; - - trace!("WGPU: Configuring surface swapchain: format = {swapchain_format:?}, size = {size:?}"); - surface_state - .surface - .configure(&render_state.device, &config); -} - -// We want to defer the initialization of our render state until -// we have a surface so we can take its format into account. -// -// After we've initialized our render state once though we -// expect all future surfaces will have the same format and we -// so this stat will remain valid. -async fn ensure_render_state_for_surface(app: &App, new_surface_state: &SurfaceState) { - let mut app_guard = app.inner.write().unwrap(); - - if app_guard.adapter.is_none() { - trace!("WGPU: requesting a suitable adapter (compatible with our surface)"); - let adapter = app_guard - .instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - force_fallback_adapter: false, - // Request an adapter which can render to our surface - compatible_surface: Some(&new_surface_state.surface), - }) + trace!("WGPU: requesting device"); + // Create the logical device and command queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + 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. + limits: wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()), + }, + None, + ) .await - .expect("Failed to find an appropriate adapter"); + .expect("Failed to create device"); - app_guard.adapter = Some(adapter); + trace!("WGPU: loading shader"); + // 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"))), + }); + + trace!("WGPU: creating pipeline layout"); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + trace!("WGPU: creating render pipeline"); + 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: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(target_format.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + RenderState { + device, + queue, + _shader: shader, + target_format, + _pipeline_layout: pipeline_layout, + render_pipeline, + } } - let adapter = app_guard.adapter.as_ref().unwrap(); - if app_guard.render_state.is_none() { - trace!("WGPU: finding supported swapchain format"); - let swapchain_format = new_surface_state.surface.get_supported_formats(&adapter)[0]; + // We want to defer the initialization of our render state until + // we have a surface so we can take its format into account. + // + // After we've initialized our render state once though we + // expect all future surfaces will have the same format and we + // so this stat will remain valid. + async fn ensure_render_state_for_surface(&mut self) { + if let Some(surface_state) = &self.surface_state { + if self.adapter.is_none() { + trace!("WGPU: requesting a suitable adapter (compatible with our surface)"); + let adapter = self + .instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + force_fallback_adapter: false, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface_state.surface), + }) + .await + .expect("Failed to find an appropriate adapter"); - let rs = init_render_state(adapter, swapchain_format).await; - app_guard.render_state = Some(rs); + self.adapter = Some(adapter); + } + let adapter = self.adapter.as_ref().unwrap(); + + if self.render_state.is_none() { + trace!("WGPU: finding supported swapchain format"); + let swapchain_format = surface_state.surface.get_supported_formats(&adapter)[0]; + + let rs = Self::init_render_state(adapter, swapchain_format).await; + self.render_state = Some(rs); + } + } + } + + fn configure_surface_swapchain(&mut self) { + if let (Some(render_state), Some(surface_state)) = (&self.render_state, &self.surface_state) { + let swapchain_format = render_state.target_format; + let size = surface_state.window.inner_size(); + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + //present_mode: wgpu::PresentMode::Fifo, + }; + + trace!("WGPU: Configuring surface swapchain: format = {swapchain_format:?}, size = {size:?}"); + surface_state + .surface + .configure(&render_state.device, &config); + } + } + + fn queue_redraw(&self) { + if let Some(surface_state) = &self.surface_state { + trace!("Making Redraw Request"); + surface_state.window.request_redraw(); + } + } + + fn resume(&mut self, event_loop: &EventLoopWindowTarget) { + trace!("Resumed, creating render state..."); + self.create_surface(event_loop); + pollster::block_on(self.ensure_render_state_for_surface()); + self.configure_surface_swapchain(); + self.queue_redraw(); } } -fn create_surface(app: &App, event_loop: &EventLoopWindowTarget) -> SurfaceState { - let window = winit::window::Window::new(&event_loop).unwrap(); - - trace!("WGPU: creating surface for native window"); - let guard = app.inner.read().unwrap(); - let surface = unsafe { guard.instance.create_surface(&window) }; - - SurfaceState { window, surface } -} - -fn resume(app: &App, event_loop: &EventLoopWindowTarget) { - trace!("Resumed, creating render state..."); - - let new_surface = create_surface(&app, event_loop); - - pollster::block_on(ensure_render_state_for_surface(&app, &new_surface)); - - app.write().unwrap().surface_state = Some(new_surface); - - let guard = app.read().unwrap(); - let render_state = guard.render_state.as_ref().unwrap(); - let surface_state = guard.surface_state.as_ref().unwrap(); - configure_surface_swapchain(render_state, surface_state); - - trace!("Making Redraw Request"); - surface_state.window.request_redraw(); -} - -fn run(event_loop: EventLoop<()>, app: App) { - //let mut running = false; - +fn run(event_loop: EventLoop<()>) { trace!("Running mainloop..."); + // doesn't need to be re-considered later + let instance = wgpu::Instance::new(wgpu::Backends::all()); + //let instance = wgpu::Instance::new(wgpu::Backends::VULKAN); + //let instance = wgpu::Instance::new(wgpu::Backends::GL); + + let mut app = App::new(instance); event_loop.run(move |event, event_loop, control_flow| { trace!("Received Winit event: {event:?}"); *control_flow = ControlFlow::Wait; match event { - Event::NewEvents(winit::event::StartCause::Init) => { - // Note: that because Winit doesn't currently support lifecycle events consistently - // across platforms then we effectively issue a fake 'resume' on non-android - // platforms... - #[cfg(not(target_os = "android"))] - resume(&app, event_loop) - } Event::Resumed => { - resume(&app, event_loop); + app.resume(event_loop); } Event::Suspended => { trace!("Suspended, dropping render state..."); - let mut guard = app.write().unwrap(); - //guard.running = false; - guard.render_state = None; + app.render_state = None; } Event::WindowEvent { event: WindowEvent::Resized(_size), .. } => { - let guard = app.read().unwrap(); - if let Some(ref surface_state) = guard.surface_state { - if let Some(ref render_state) = guard.render_state { - configure_surface_swapchain(render_state, surface_state); - - // Winit: doesn't currently implicitly request a redraw - // for a resize which may be required on some platforms... - surface_state.window.request_redraw(); - } - } + app.configure_surface_swapchain(); + // Winit: doesn't currently implicitly request a redraw + // for a resize which may be required on some platforms... + app.queue_redraw(); } Event::RedrawRequested(_) => { trace!("Handling Redraw Request"); - let guard = app.read().unwrap(); - if let Some(ref surface_state) = guard.surface_state { - if let Some(ref rs) = guard.render_state { + if let Some(ref surface_state) = app.surface_state { + if let Some(ref rs) = app.render_state { let frame = surface_state .surface .get_current_texture() @@ -300,26 +269,24 @@ fn run(event_loop: EventLoop<()>, app: App) { } fn _main(event_loop: EventLoop<()>) { - // We can decide on our graphics API / backend up-front and that - // doesn't need to be re-considered later - let instance = wgpu::Instance::new(wgpu::Backends::all()); - //let instance = wgpu::Instance::new(wgpu::Backends::VULKAN); - //let instance = wgpu::Instance::new(wgpu::Backends::GL); - let app = App::new(instance); - run(event_loop, app); + run(event_loop); } +#[allow(dead_code)] #[cfg(target_os = "android")] #[no_mangle] fn android_main(app: AndroidApp) { - android_logger::init_once(android_logger::Config::default().with_min_level(Level::Trace)); + use winit::platform::android::EventLoopBuilderExtAndroid; + + android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace)); let event_loop = EventLoopBuilder::new().with_android_app(app).build(); _main(event_loop); } +#[allow(dead_code)] #[cfg(not(target_os = "android"))] fn main() { env_logger::builder() diff --git a/examples/na-winit-wgpu/src/lib.rs b/examples/na-winit-wgpu/src/lib.rs index b762a73..f8bb09b 100644 --- a/examples/na-winit-wgpu/src/lib.rs +++ b/examples/na-winit-wgpu/src/lib.rs @@ -1,9 +1,6 @@ use std::borrow::Cow; -use std::ops::Deref; -use std::sync::{Arc, RwLock}; use log::trace; -use log::Level; use wgpu::TextureFormat; use wgpu::{Adapter, Device, Instance, PipelineLayout, Queue, RenderPipeline, ShaderModule}; @@ -15,8 +12,6 @@ use winit::{ #[cfg(target_os = "android")] use android_activity::AndroidApp; -#[cfg(target_os = "android")] -use winit::platform::android::EventLoopBuilderExtAndroid; struct RenderState { device: Device, @@ -32,231 +27,202 @@ struct SurfaceState { surface: wgpu::Surface, } -struct AppInner { +struct App { instance: Instance, adapter: Option, surface_state: Option, render_state: Option, } -struct App { - inner: Arc>, -} - impl App { fn new(instance: Instance) -> Self { Self { - inner: Arc::new(RwLock::new(AppInner { - instance, - adapter: None, - surface_state: None, - render_state: None, - })), + instance, + adapter: None, + surface_state: None, + render_state: None, } } } -impl Deref for App { - type Target = Arc>; - fn deref(&self) -> &Self::Target { - &self.inner + +impl App { + fn create_surface(&mut self, event_loop: &EventLoopWindowTarget) { + let window = winit::window::Window::new(&event_loop).unwrap(); + trace!("WGPU: creating surface for native window"); + let surface = unsafe { self.instance.create_surface(&window) }; + self.surface_state = Some(SurfaceState { window, surface }); } -} -async fn init_render_state(adapter: &Adapter, target_format: TextureFormat) -> RenderState { - trace!("Initializing render state"); + async fn init_render_state(adapter: &Adapter, target_format: TextureFormat) -> RenderState { + trace!("Initializing render state"); - trace!("WGPU: requesting device"); - // Create the logical device and command queue - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - 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. - limits: wgpu::Limits::downlevel_webgl2_defaults() - .using_resolution(adapter.limits()), - }, - None, - ) - .await - .expect("Failed to create device"); - - trace!("WGPU: loading shader"); - // 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"))), - }); - - trace!("WGPU: creating pipeline layout"); - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - trace!("WGPU: creating render pipeline"); - 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: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[target_format.into()], - }), - primitive: wgpu::PrimitiveState::default(), - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - }); - - RenderState { - device, - queue, - _shader: shader, - target_format, - _pipeline_layout: pipeline_layout, - render_pipeline, - } -} - -fn configure_surface_swapchain(render_state: &RenderState, surface_state: &SurfaceState) { - let swapchain_format = render_state.target_format; - let size = surface_state.window.inner_size(); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: swapchain_format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Mailbox, - //present_mode: wgpu::PresentMode::Fifo, - }; - - trace!("WGPU: Configuring surface swapchain: format = {swapchain_format:?}, size = {size:?}"); - surface_state - .surface - .configure(&render_state.device, &config); -} - -// We want to defer the initialization of our render state until -// we have a surface so we can take its format into account. -// -// After we've initialized our render state once though we -// expect all future surfaces will have the same format and we -// so this stat will remain valid. -async fn ensure_render_state_for_surface(app: &App, new_surface_state: &SurfaceState) { - let mut app_guard = app.inner.write().unwrap(); - - if app_guard.adapter.is_none() { - trace!("WGPU: requesting a suitable adapter (compatible with our surface)"); - let adapter = app_guard - .instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - force_fallback_adapter: false, - // Request an adapter which can render to our surface - compatible_surface: Some(&new_surface_state.surface), - }) + trace!("WGPU: requesting device"); + // Create the logical device and command queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + 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. + limits: wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()), + }, + None, + ) .await - .expect("Failed to find an appropriate adapter"); + .expect("Failed to create device"); - app_guard.adapter = Some(adapter); + trace!("WGPU: loading shader"); + // 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"))), + }); + + trace!("WGPU: creating pipeline layout"); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + trace!("WGPU: creating render pipeline"); + 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: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(target_format.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + RenderState { + device, + queue, + _shader: shader, + target_format, + _pipeline_layout: pipeline_layout, + render_pipeline, + } } - let adapter = app_guard.adapter.as_ref().unwrap(); - if app_guard.render_state.is_none() { - trace!("WGPU: finding preferred swapchain format"); - let swapchain_format = new_surface_state - .surface - .get_preferred_format(&adapter) - .unwrap(); + // We want to defer the initialization of our render state until + // we have a surface so we can take its format into account. + // + // After we've initialized our render state once though we + // expect all future surfaces will have the same format and we + // so this stat will remain valid. + async fn ensure_render_state_for_surface(&mut self) { + if let Some(surface_state) = &self.surface_state { + if self.adapter.is_none() { + trace!("WGPU: requesting a suitable adapter (compatible with our surface)"); + let adapter = self + .instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + force_fallback_adapter: false, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface_state.surface), + }) + .await + .expect("Failed to find an appropriate adapter"); - let rs = init_render_state(adapter, swapchain_format).await; - app_guard.render_state = Some(rs); + self.adapter = Some(adapter); + } + let adapter = self.adapter.as_ref().unwrap(); + + if self.render_state.is_none() { + trace!("WGPU: finding supported swapchain format"); + let swapchain_format = surface_state.surface.get_supported_formats(&adapter)[0]; + + let rs = Self::init_render_state(adapter, swapchain_format).await; + self.render_state = Some(rs); + } + } + } + + fn configure_surface_swapchain(&mut self) { + if let (Some(render_state), Some(surface_state)) = (&self.render_state, &self.surface_state) { + let swapchain_format = render_state.target_format; + let size = surface_state.window.inner_size(); + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + //present_mode: wgpu::PresentMode::Fifo, + }; + + trace!("WGPU: Configuring surface swapchain: format = {swapchain_format:?}, size = {size:?}"); + surface_state + .surface + .configure(&render_state.device, &config); + } + } + + fn queue_redraw(&self) { + if let Some(surface_state) = &self.surface_state { + trace!("Making Redraw Request"); + surface_state.window.request_redraw(); + } + } + + fn resume(&mut self, event_loop: &EventLoopWindowTarget) { + trace!("Resumed, creating render state..."); + self.create_surface(event_loop); + pollster::block_on(self.ensure_render_state_for_surface()); + self.configure_surface_swapchain(); + self.queue_redraw(); } } -fn create_surface(app: &App, event_loop: &EventLoopWindowTarget) -> SurfaceState { - let window = winit::window::Window::new(&event_loop).unwrap(); - - trace!("WGPU: creating surface for native window"); - let guard = app.inner.read().unwrap(); - let surface = unsafe { guard.instance.create_surface(&window) }; - - SurfaceState { window, surface } -} - -fn resume(app: &App, event_loop: &EventLoopWindowTarget) { - trace!("Resumed, creating render state..."); - - let new_surface = create_surface(&app, event_loop); - - pollster::block_on(ensure_render_state_for_surface(&app, &new_surface)); - - app.write().unwrap().surface_state = Some(new_surface); - - let guard = app.read().unwrap(); - let render_state = guard.render_state.as_ref().unwrap(); - let surface_state = guard.surface_state.as_ref().unwrap(); - configure_surface_swapchain(render_state, surface_state); - - trace!("Making Redraw Request"); - surface_state.window.request_redraw(); -} - -fn run(event_loop: EventLoop<()>, app: App) { - //let mut running = false; - +fn run(event_loop: EventLoop<()>) { trace!("Running mainloop..."); + // doesn't need to be re-considered later + let instance = wgpu::Instance::new(wgpu::Backends::all()); + //let instance = wgpu::Instance::new(wgpu::Backends::VULKAN); + //let instance = wgpu::Instance::new(wgpu::Backends::GL); + + let mut app = App::new(instance); event_loop.run(move |event, event_loop, control_flow| { trace!("Received Winit event: {event:?}"); *control_flow = ControlFlow::Wait; match event { - Event::NewEvents(winit::event::StartCause::Init) => { - // Note: that because Winit doesn't currently support lifecycle events consistently - // across platforms then we effectively issue a fake 'resume' on non-android - // platforms... - #[cfg(not(target_os = "android"))] - resume(&app, event_loop) - } Event::Resumed => { - resume(&app, event_loop); + app.resume(event_loop); } Event::Suspended => { trace!("Suspended, dropping render state..."); - let mut guard = app.write().unwrap(); - //guard.running = false; - guard.render_state = None; + app.render_state = None; } Event::WindowEvent { event: WindowEvent::Resized(_size), .. } => { - let guard = app.read().unwrap(); - if let Some(ref surface_state) = guard.surface_state { - if let Some(ref render_state) = guard.render_state { - configure_surface_swapchain(render_state, surface_state); - - // Winit: doesn't currently implicitly request a redraw - // for a resize which may be required on some platforms... - surface_state.window.request_redraw(); - } - } + app.configure_surface_swapchain(); + // Winit: doesn't currently implicitly request a redraw + // for a resize which may be required on some platforms... + app.queue_redraw(); } Event::RedrawRequested(_) => { trace!("Handling Redraw Request"); - let guard = app.read().unwrap(); - if let Some(ref surface_state) = guard.surface_state { - if let Some(ref rs) = guard.render_state { + if let Some(ref surface_state) = app.surface_state { + if let Some(ref rs) = app.render_state { let frame = surface_state .surface .get_current_texture() @@ -273,14 +239,14 @@ fn run(event_loop: EventLoop<()>, app: App) { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, - color_attachments: &[wgpu::RenderPassColorAttachment { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: true, }, - }], + })], depth_stencil_attachment: None, }); rpass.set_pipeline(&rs.render_pipeline); @@ -303,26 +269,24 @@ fn run(event_loop: EventLoop<()>, app: App) { } fn _main(event_loop: EventLoop<()>) { - // We can decide on our graphics API / backend up-front and that - // doesn't need to be re-considered later - let instance = wgpu::Instance::new(wgpu::Backends::all()); - //let instance = wgpu::Instance::new(wgpu::Backends::VULKAN); - //let instance = wgpu::Instance::new(wgpu::Backends::GL); - let app = App::new(instance); - run(event_loop, app); + run(event_loop); } +#[allow(dead_code)] #[cfg(target_os = "android")] #[no_mangle] fn android_main(app: AndroidApp) { - android_logger::init_once(android_logger::Config::default().with_min_level(Level::Trace)); + use winit::platform::android::EventLoopBuilderExtAndroid; + + android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace)); let event_loop = EventLoopBuilder::new().with_android_app(app).build(); _main(event_loop); } +#[allow(dead_code)] #[cfg(not(target_os = "android"))] fn main() { env_logger::builder()