wgpu_noboiler/
app.rs

1use std::time::Instant;
2
3use wgpu::{Adapter, CommandEncoder, CommandEncoderDescriptor, CompositeAlphaMode, Device, DeviceDescriptor, Instance, Limits, PowerPreference, PresentMode, Queue, RenderPipeline, RequestAdapterOptions, Surface, SurfaceConfiguration, SurfaceError, TextureFormat, TextureUsages, TextureView, TextureViewDescriptor};
4use winit::dpi::PhysicalSize;
5use winit::event::{Event, WindowEvent};
6use winit::event_loop::{ControlFlow, EventLoop};
7use winit::window::{Window, WindowBuilder};
8
9pub struct App<T: 'static> {
10    state: T,
11    app_data: AppData,
12
13    window_event_fn: Option<WindowEventFn<T>>,
14    resize_fn: Option<ResizeFn<T>>,
15    update_fn: Option<UpdateFn<T>>,
16    render_fn: Option<RenderFn<T>>,
17    init_fn: Option<InitFn<T>>,
18}
19
20/// background data for your [App]
21pub struct AppData {
22    last_frame_instant: Instant,
23    render_instant: Instant,
24
25    ///Avg Frames Per Seconds of the last 30 Frames
26    pub fps: f64,
27    ///time since the last frame in seconds
28    pub delta_time: f64,
29    ///duration of the the last renderFunction in seconds
30    pub render_time: f64,
31    ///duration of the the last renderFunction in seconds
32    pub update_time: f64,
33
34    surface: Surface,
35
36    pub device: Device,
37    pub queue: Queue,
38    pub config: SurfaceConfiguration,
39
40    pub size: PhysicalSize<u32>,
41
42    ///vec of all RenderPipelines which got [created](AppCreator::init)
43    pub render_pipelines: Vec<RenderPipeline>,
44}
45
46impl<T: 'static> App<T> {
47    fn resize(&mut self, new_size: PhysicalSize<u32>) {
48        if new_size.width == 0 || new_size.height == 0 {
49            return;
50        }
51
52        self.app_data.size = new_size;
53        self.app_data.config.width = new_size.width;
54        self.app_data.config.height = new_size.height;
55        self.app_data
56            .surface
57            .configure(&self.app_data.device, &self.app_data.config);
58
59        if self.resize_fn.is_some() {
60            self.resize_fn.unwrap()(&self.app_data, &mut self.state, (self.app_data.size.width, self.app_data.size.height))
61        }
62    }
63
64    fn init(&mut self) {
65        if self.init_fn.is_none() {
66            return;
67        }
68
69        let mut render_pipelines = Vec::new();
70        self.init_fn.unwrap()(&self.app_data, &mut self.state, &mut render_pipelines);
71        self.app_data.render_pipelines = render_pipelines
72    }
73
74    fn render(&mut self) -> Result<(), SurfaceError> {
75        if self.render_fn.is_none() {
76            return Ok(());
77        }
78
79        let output = self.app_data.surface.get_current_texture()?;
80        let view = output
81            .texture
82            .create_view(&TextureViewDescriptor::default());
83        let encoder = self
84            .app_data
85            .device
86            .create_command_encoder(&CommandEncoderDescriptor {
87                label: Some("Render Encoder"),
88            });
89
90        self.render_fn.unwrap()(&self.app_data, &mut self.state, encoder, view);
91
92        output.present();
93
94        Ok(())
95    }
96
97    fn run(mut self, window: Window, event_loop: EventLoop<()>) {
98        self.init();
99
100        window.set_visible(true);
101
102        event_loop.run(move |event, _, control_flow| match event {
103            Event::WindowEvent {
104                ref event,
105                window_id,
106            } => {
107                if window_id != window.id() {
108                    return;
109                }
110                match event {
111                    WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
112                    WindowEvent::Resized(physical_size) => {
113                        self.resize(*physical_size);
114                    }
115                    WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
116                        self.resize(**new_inner_size);
117                    }
118                    _ => {
119                        if self.window_event_fn.is_some() {
120                            self.window_event_fn.unwrap()(&self.app_data, &mut self.state, event);
121                        }
122                    }
123                }
124            }
125            Event::RedrawRequested(window_id) => {
126                if window_id != window.id() {
127                    return;
128                }
129
130                self.app_data.render_time = self.app_data.render_instant.elapsed().as_secs_f64();
131
132                self.app_data.delta_time = self.app_data.last_frame_instant.elapsed().as_secs_f64();
133                self.app_data.fps = 1.0 / self.app_data.delta_time;
134
135                self.app_data.last_frame_instant = Instant::now();
136
137                let update_instant = Instant::now();
138
139                if self.update_fn.is_some() {
140                    self.update_fn.unwrap()(&self.app_data, &mut self.state);
141                }
142
143                self.app_data.update_time = update_instant.elapsed().as_secs_f64();
144                self.app_data.render_instant = Instant::now();
145
146                match self.render() {
147                    Ok(_) => {}
148                    Err(SurfaceError::Lost) => self.resize(self.app_data.size),
149                    Err(SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
150                    Err(e) => eprintln!("{:?}", e),
151                }
152            }
153            Event::MainEventsCleared => {
154                window.request_redraw();
155            }
156
157            _ => {}
158        });
159    }
160}
161
162/// builder for [App]
163pub struct AppCreator<T: 'static> {
164    state: T,
165
166    window: Window,
167    event_loop: EventLoop<()>,
168
169    window_event_fn: Option<WindowEventFn<T>>,
170    resize_fn: Option<ResizeFn<T>>,
171    update_fn: Option<UpdateFn<T>>,
172    render_fn: Option<RenderFn<T>>,
173    init_fn: Option<InitFn<T>>,
174
175    present_mode: PresentMode,
176    power_preference: PowerPreference,
177    device_limits: Limits,
178
179    view_formats: Vec<TextureFormat>,
180}
181
182impl<T: 'static> AppCreator<T> {
183    /// creates [AppCreator]
184    ///
185    /// # Arguments
186    ///
187    /// * `state`: data which describes your App can be changes on [update](AppCreator::update) and used to [render](AppCreator::render)
188    ///
189    pub fn new(state: T) -> AppCreator<T> {
190        let event_loop = EventLoop::new();
191        let window = WindowBuilder::new()
192            .with_visible(false)
193            .build(&event_loop)
194            .unwrap();
195
196        AppCreator {
197            state,
198
199            window,
200            event_loop,
201            window_event_fn: None,
202            resize_fn: None,
203            update_fn: None,
204            render_fn: None,
205            init_fn: None,
206
207            present_mode: PresentMode::Fifo,
208            power_preference: PowerPreference::LowPower,
209            device_limits: Limits::default(),
210
211            view_formats: vec![],
212        }
213    }
214
215    /// gets called on every [WindowEvent]
216    ///
217    /// e.g. mouse | keyboard input
218    pub fn window_event(mut self, input: WindowEventFn<T>) -> Self {
219        self.window_event_fn = Some(input);
220        self
221    }
222
223    /// gets called on every ResizeEvent
224    pub fn resize(mut self, resize: ResizeFn<T>) -> Self {
225        self.resize_fn = Some(resize);
226        self
227    }
228
229    ///gets called on every frame just before [AppCreator::render]
230    ///
231    /// here you can change your State
232    pub fn update(mut self, update: UpdateFn<T>) -> Self {
233        self.update_fn = Some(update);
234        self
235    }
236
237    ///gets called on every frame just after [AppCreator::update]
238    ///
239    /// here you can render your frame
240    pub fn render(mut self, render: RenderFn<T>) -> Self {
241        self.render_fn = Some(render);
242        self
243    }
244
245    /// gets called before opening the window
246    ///
247    /// mainly used to create your [RenderPipelines](RenderPipeline)
248    pub fn init(mut self, init: InitFn<T>) -> Self {
249        self.init_fn = Some(init);
250        self
251    }
252
253    /// sets the [PresentMode] of the [Surface]
254    ///
255    /// default: [PresentMode::Fifo]
256    pub fn present_mode(mut self, present_mode: PresentMode) -> Self {
257        self.present_mode = present_mode;
258        self
259    }
260
261    pub fn title(self, title: &str) -> Self {
262        self.window.set_title(title);
263        self
264    }
265
266    pub fn resizable(self, resizable: bool) -> Self {
267        self.window.set_resizable(resizable);
268        self
269    }
270
271    pub fn get_window(&mut self) -> &mut Window {
272        &mut self.window
273    }
274
275    pub fn add_view_formats(mut self, texture_format: TextureFormat) -> Self {
276        self.view_formats.push(texture_format);
277        self
278    }
279
280    /// sets the [PowerPreference] of the [Adapter]
281    ///
282    /// default: [PresentMode::Fifo]
283    pub fn power_preference(mut self, power_preference: PowerPreference) -> Self {
284        self.power_preference = power_preference;
285        self
286    }
287
288    /// sets the [Limits] of the [Device]
289    ///
290    /// default: [Limits::default]
291    pub fn device_limits(mut self, limits: Limits) -> Self {
292        self.device_limits = limits;
293        self
294    }
295
296    fn create_app_data(&self) -> AppData {
297        env_logger::init();
298        let size = self.window.inner_size();
299
300        let instance = Instance::default();
301        let surface = unsafe { instance.create_surface(&self.window).unwrap() };
302
303        let adapter: Adapter =
304            pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
305                power_preference: self.power_preference,
306                compatible_surface: Some(&surface),
307                force_fallback_adapter: false,
308            }))
309                .unwrap();
310
311        let (device, queue) = pollster::block_on(adapter.request_device(
312            &DeviceDescriptor {
313                features: wgpu::Features::empty(),
314                limits: self.device_limits.clone(),
315                label: None,
316            },
317            None,
318        ))
319            .unwrap();
320
321        let config = SurfaceConfiguration {
322            usage: TextureUsages::RENDER_ATTACHMENT,
323            format: surface.get_capabilities(&adapter).formats[0],
324            width: size.width,
325            height: size.height,
326            present_mode: self.present_mode,
327            alpha_mode: CompositeAlphaMode::Auto,
328            view_formats: self.view_formats.clone(),
329        };
330
331        surface.configure(&device, &config);
332
333        AppData {
334            surface,
335            device,
336            queue,
337            config,
338            size,
339
340            last_frame_instant: Instant::now(),
341            render_instant: Instant::now(),
342
343            render_pipelines: Vec::new(),
344            fps: 0.0,
345
346            delta_time: 1.0,
347            render_time: 1.0,
348            update_time: 1.0,
349        }
350    }
351
352    /// opens the window and starts the [AppCreator::update] | [AppCreator::render] loop
353    pub fn run(self) {
354        let app = App {
355            app_data: self.create_app_data(),
356
357            state: self.state,
358
359            window_event_fn: self.window_event_fn,
360            resize_fn: self.resize_fn,
361            update_fn: self.update_fn,
362            render_fn: self.render_fn,
363            init_fn: self.init_fn,
364        };
365
366        app.run(self.window, self.event_loop);
367    }
368}
369
370pub type WindowEventFn<T> = fn(app_data: &AppData, state: &mut T, window_event: &WindowEvent);
371
372pub type ResizeFn<T> = fn(app_data: &AppData, state: &mut T, size: (u32, u32));
373
374pub type UpdateFn<T> = fn(app_data: &AppData, state: &mut T);
375
376pub type RenderFn<T> = fn(
377    app_data: &AppData,
378    state: &mut T,
379    command_encoder: CommandEncoder,
380    texture_view: TextureView,
381);
382
383pub type InitFn<T> =
384fn(app_data: &AppData, state: &mut T, render_pipelines: &mut Vec<RenderPipeline>);