demo_vk/demo/
mod.rs

1mod egui_integration;
2mod frame_metrics;
3mod rolling_average;
4
5use {
6    self::frame_metrics::FrameMetrics,
7    crate::{
8        app::{app_main, App, AppState},
9        graphics::vulkan::{
10            Frame, FrameStatus, FramesInFlight, PresentImageStatus,
11            RequiredDeviceFeatures, Swapchain, VulkanContext,
12        },
13        unwrap_here,
14    },
15    anyhow::Result,
16    ash::vk::{self},
17    clap::Parser,
18    spin_sleep_util::Interval,
19    std::{
20        sync::Arc,
21        time::{Duration, Instant},
22    },
23    winit::{
24        dpi::PhysicalSize,
25        event::{DeviceEvent, WindowEvent},
26        keyboard::{KeyCode, PhysicalKey},
27        window::Window,
28    },
29};
30
31pub use self::egui_integration::EguiPainter;
32
33/// Standard graphics resources provided by the Demo.
34pub struct Graphics {
35    /// The Vulkan entrypoint
36    pub vulkan: Arc<VulkanContext>,
37
38    /// FramesInFlight synchronizes N frames in flight.
39    pub frames_in_flight: FramesInFlight,
40
41    /// The application's swapchain.
42    pub swapchain: Swapchain,
43
44    pub metrics: FrameMetrics,
45
46    fps_limiter: Interval,
47
48    swapchain_needs_rebuild: bool,
49    paused: bool,
50}
51
52/// A demo is an opinionated application that automatically creates the
53/// VulkanContext, Swapchain, FramesInFlight and other common utilities.
54///
55/// The demo splits the application's update() function into two parts:
56/// * update(): update the application's state
57/// * draw(): build the frame's command buffer
58///
59/// Draw is separate because update() does not wait for the next swapchain
60/// image. There are some operations that may not depend on the swapchain and
61/// can reasonably be started before the frame's command buffer is ready to be
62/// recorded. (ticking a physics simulation, etc...)
63pub trait Demo {
64    type Args: Sized + Parser;
65    const FRAMES_IN_FLIGHT_COUNT: usize = 3;
66    const INITIAL_WINDOW_SIZE: (i32, i32) = (1024, 768);
67    const FRAMES_PER_SECOND: u32 = 120;
68
69    /// Creates a new instance of the demo.
70    /// The application is allowed to modify the window based on its own
71    /// requirements. This includes modifying the polling state, fullscreen
72    /// status, size, etc...
73    fn new(
74        window: &mut Window,
75        gfx: &mut Graphics,
76        args: &Self::Args,
77    ) -> Result<Self>
78    where
79        Self: Sized;
80
81    /// Returns the required device features for this demo.
82    fn required_device_features() -> RequiredDeviceFeatures {
83        RequiredDeviceFeatures::default()
84    }
85
86    /// Handles a single window event.
87    fn handle_window_event(
88        &mut self,
89        #[allow(unused_variables)] window: &mut Window,
90        #[allow(unused_variables)] gfx: &mut Graphics,
91        #[allow(unused_variables)] event: WindowEvent,
92    ) -> Result<AppState> {
93        if let WindowEvent::KeyboardInput { event, .. } = event {
94            if event.physical_key == PhysicalKey::Code(KeyCode::Escape) {
95                // If Esc is pressed, then quit
96                return Ok(AppState::Exit);
97            }
98        }
99        Ok(AppState::Continue)
100    }
101
102    /// Handles a single device event.
103    fn handle_device_event(
104        &mut self,
105        #[allow(unused_variables)] window: &mut Window,
106        #[allow(unused_variables)] gfx: &mut Graphics,
107        #[allow(unused_variables)] event: DeviceEvent,
108    ) -> Result<AppState> {
109        Ok(AppState::Continue)
110    }
111
112    /// The application is updated once every frame right before calling
113    /// [Self::draw].
114    fn update(
115        &mut self,
116        #[allow(unused_variables)] window: &mut Window,
117        #[allow(unused_variables)] gfx: &mut Graphics,
118    ) -> Result<AppState> {
119        Ok(AppState::Continue)
120    }
121
122    /// Build the command buffer for the next frame.
123    ///
124    /// Called after update() once the Frame is started.
125    fn draw(
126        &mut self,
127        #[allow(unused_variables)] window: &mut Window,
128        #[allow(unused_variables)] gfx: &mut Graphics,
129        #[allow(unused_variables)] frame: &Frame,
130    ) -> Result<AppState> {
131        // By default, this method does nothing but transition the swapchain
132        // image to presentation format.
133        //
134        // Normally, a real application will use a render pass to do this.
135
136        let image_memory_barrier = vk::ImageMemoryBarrier {
137            old_layout: vk::ImageLayout::UNDEFINED,
138            new_layout: vk::ImageLayout::PRESENT_SRC_KHR,
139            image: frame.swapchain_image(),
140            subresource_range: vk::ImageSubresourceRange {
141                aspect_mask: vk::ImageAspectFlags::COLOR,
142                base_mip_level: 0,
143                level_count: 1,
144                base_array_layer: 0,
145                layer_count: 1,
146            },
147            ..Default::default()
148        };
149        unsafe {
150            gfx.vulkan.cmd_pipeline_barrier(
151                frame.command_buffer(),
152                vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
153                vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
154                vk::DependencyFlags::empty(),
155                &[],
156                &[],
157                &[image_memory_barrier],
158            );
159        }
160
161        Ok(AppState::Continue)
162    }
163
164    /// Rebuild any of the demo's swapchain-dependent resources.
165    ///
166    /// All pending frames in flight are guaranteed to have completed by the
167    /// time this function is called. E.G. any previously-recorded Frame command
168    /// buffers are guaranteed to be finished.
169    fn rebuild_swapchain_resources(
170        &mut self,
171        #[allow(unused_variables)] window: &mut Window,
172        #[allow(unused_variables)] gfx: &mut Graphics,
173    ) -> Result<()> {
174        Ok(())
175    }
176
177    /// Called when the application is paused.
178    ///
179    /// The application is paused automatically any time the framebuffer has
180    /// size of 0. This occurs when the application is minimized, etc..
181    fn paused(
182        &mut self,
183        #[allow(unused_variables)] window: &mut Window,
184        #[allow(unused_variables)] gfx: &mut Graphics,
185    ) -> Result<()> {
186        Ok(())
187    }
188
189    /// Called when the application is unpaused.
190    fn unpaused(
191        &mut self,
192        #[allow(unused_variables)] window: &mut Window,
193        #[allow(unused_variables)] gfx: &mut Graphics,
194    ) -> Result<()> {
195        Ok(())
196    }
197}
198
199struct DemoApp<D: Demo> {
200    graphics: Graphics,
201    demo: D,
202}
203
204impl<D: Demo> DemoApp<D> {
205    /// Called any time the framebuffer size may have changed.
206    /// Returns the current paused status for convenience.
207    fn check_paused(&mut self, window: &mut Window) -> Result<bool> {
208        let PhysicalSize {
209            width: w,
210            height: h,
211        } = window.inner_size();
212        let should_pause = w == 0 || h == 0;
213
214        if should_pause {
215            if !self.graphics.paused {
216                self.demo.paused(window, &mut self.graphics)?;
217            }
218            self.graphics.paused = true;
219        } else {
220            if self.graphics.paused {
221                self.demo.unpaused(window, &mut self.graphics)?;
222            }
223            self.graphics.paused = false;
224            self.graphics.metrics.unpause();
225        }
226        Ok(self.graphics.paused)
227    }
228}
229
230impl<D: Demo + Sized> App for DemoApp<D> {
231    type Args = D::Args;
232
233    fn new(window: &mut Window, args: &Self::Args) -> Result<Self>
234    where
235        Self: Sized,
236    {
237        let _ = window.request_inner_size(PhysicalSize {
238            width: D::INITIAL_WINDOW_SIZE.0,
239            height: D::INITIAL_WINDOW_SIZE.1,
240        });
241        window.set_title(std::any::type_name::<D>());
242
243        let vulkan = unwrap_here!(
244            "Create Vulkan context",
245            VulkanContext::new(window, D::required_device_features())
246        );
247
248        let PhysicalSize { width, height } = window.inner_size();
249        let swapchain = unwrap_here!(
250            "Create initial swapchain",
251            Swapchain::new(vulkan.clone(), (width, height), None)
252        );
253
254        let frames_in_flight = unwrap_here!(
255            "Create frames-in-flight",
256            FramesInFlight::new(
257                vulkan.clone(),
258                swapchain.images().len(),
259                D::FRAMES_IN_FLIGHT_COUNT,
260            )
261        );
262
263        let fps_limiter = spin_sleep_util::interval(Duration::from_secs_f64(
264            1.0 / D::FRAMES_PER_SECOND as f64,
265        ));
266
267        let mut graphics = Graphics {
268            fps_limiter,
269            vulkan,
270            swapchain,
271            frames_in_flight,
272            metrics: FrameMetrics::new(D::FRAMES_PER_SECOND as usize),
273            swapchain_needs_rebuild: false,
274            paused: false,
275        };
276        let demo = unwrap_here!(
277            "Initialize Demo",
278            D::new(window, &mut graphics, args)
279        );
280
281        let mut app = Self { graphics, demo };
282        unwrap_here!("Render first frame", app.update(window));
283
284        // only show the window after rendering the first frame
285        window.set_visible(true);
286
287        Ok(app)
288    }
289
290    fn handle_window_event(
291        &mut self,
292        window: &mut Window,
293        event: WindowEvent,
294    ) -> Result<AppState> {
295        if let WindowEvent::Resized(PhysicalSize { .. }) = &event {
296            // Note: Some platforms (Windows, X11) seem to consistently
297            // invalidate the swapchain when the window is resized.
298            // Wayland, however, does not, and will instead silently break
299            // the way the application renders because the compositor and
300            // application disagree on the size. (but not in a way which
301            // triggers an error while acquiring the next image).
302            //
303            // The easy option is to simply always indicate that the swapchain
304            // needs to be rebuilt when a resize event is processed.
305            self.graphics.swapchain_needs_rebuild = true;
306        }
307        self.demo
308            .handle_window_event(window, &mut self.graphics, event)
309    }
310
311    fn handle_device_event(
312        &mut self,
313        window: &mut Window,
314        event: DeviceEvent,
315    ) -> Result<AppState> {
316        self.demo
317            .handle_device_event(window, &mut self.graphics, event)
318    }
319
320    fn update(&mut self, window: &mut Window) -> Result<AppState> {
321        if self.graphics.paused && self.check_paused(window)? {
322            std::hint::spin_loop();
323            return Ok(AppState::Continue);
324        }
325
326        if self.graphics.swapchain_needs_rebuild {
327            unwrap_here!(
328                "Swapchain needs rebuild - wait for all frames to complete",
329                self.graphics
330                    .frames_in_flight
331                    .wait_for_all_frames_to_complete()
332            );
333            if self.check_paused(window)? {
334                return Ok(AppState::Continue);
335            }
336            let window_size = window.inner_size();
337            self.graphics.swapchain = unwrap_here!(
338                "Swapchain needs rebuild - rebuild swapchain",
339                Swapchain::new(
340                    self.graphics.vulkan.clone(),
341                    (window_size.width, window_size.height),
342                    Some(self.graphics.swapchain.raw()),
343                )
344            );
345            unwrap_here!(
346                "Rebuild swapchain semaphores for frames-in-flight sync",
347                self.graphics.frames_in_flight.rebuild_swapchain_semaphores(
348                    &self.graphics.vulkan,
349                    self.graphics.swapchain.images().len(),
350                )
351            );
352
353            unwrap_here!(
354                "Rebuild Demo's swapchain resources",
355                self.demo
356                    .rebuild_swapchain_resources(window, &mut self.graphics)
357            );
358
359            self.graphics.swapchain_needs_rebuild = false;
360        }
361
362        self.graphics.metrics.frame_tick();
363
364        // Update application logic
365        // ------------------------
366
367        let before_update = Instant::now();
368        if unwrap_here!(
369            "Demo::update()",
370            self.demo.update(window, &mut self.graphics)
371        ) == AppState::Exit
372        {
373            return Ok(AppState::Exit);
374        }
375        self.graphics.metrics.update_tick(before_update);
376
377        // Limit FPS, wait just before acquiring the frame
378        self.graphics.fps_limiter.tick();
379
380        // Prepare frame command buffer and submit
381        // ---------------------------------------
382
383        let before_draw = Instant::now();
384        let frame = match self
385            .graphics
386            .frames_in_flight
387            .start_frame(&self.graphics.swapchain)?
388        {
389            FrameStatus::FrameStarted(frame) => frame,
390            FrameStatus::SwapchainNeedsRebuild => {
391                self.graphics.swapchain_needs_rebuild = true;
392                return Ok(AppState::Continue);
393            }
394        };
395
396        if unwrap_here!(
397            "Demo draw",
398            self.demo.draw(window, &mut self.graphics, &frame)
399        ) == AppState::Exit
400        {
401            return Ok(AppState::Exit);
402        }
403
404        let result = self
405            .graphics
406            .frames_in_flight
407            .present_frame(&self.graphics.swapchain, frame)?;
408        if result == PresentImageStatus::SwapchainNeedsRebuild {
409            self.graphics.swapchain_needs_rebuild = true;
410        }
411        self.graphics.metrics.draw_tick(before_draw);
412
413        Ok(AppState::Continue)
414    }
415}
416
417/// The main entrypoint for a demo.
418pub fn demo_main<D: Demo + 'static>() -> Result<()> {
419    app_main::<DemoApp<D>>()
420}