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        self.demo
296            .handle_window_event(window, &mut self.graphics, event)
297    }
298
299    fn handle_device_event(
300        &mut self,
301        window: &mut Window,
302        event: DeviceEvent,
303    ) -> Result<AppState> {
304        self.demo
305            .handle_device_event(window, &mut self.graphics, event)
306    }
307
308    fn update(&mut self, window: &mut Window) -> Result<AppState> {
309        if self.graphics.paused && self.check_paused(window)? {
310            std::hint::spin_loop();
311            return Ok(AppState::Continue);
312        }
313
314        if self.graphics.swapchain_needs_rebuild {
315            unwrap_here!(
316                "Swapchain needs rebuild - wait for all frames to complete",
317                self.graphics
318                    .frames_in_flight
319                    .wait_for_all_frames_to_complete()
320            );
321            if self.check_paused(window)? {
322                return Ok(AppState::Continue);
323            }
324            let window_size = window.inner_size();
325            self.graphics.swapchain = unwrap_here!(
326                "Swapchain needs rebuild - rebuild swapchain",
327                Swapchain::new(
328                    self.graphics.vulkan.clone(),
329                    (window_size.width, window_size.height),
330                    Some(self.graphics.swapchain.raw()),
331                )
332            );
333            unwrap_here!(
334                "Rebuild swapchain semaphores for frames-in-flight sync",
335                self.graphics.frames_in_flight.rebuild_swapchain_semaphores(
336                    &self.graphics.vulkan,
337                    self.graphics.swapchain.images().len(),
338                )
339            );
340
341            unwrap_here!(
342                "Rebuild Demo's swapchain resources",
343                self.demo
344                    .rebuild_swapchain_resources(window, &mut self.graphics)
345            );
346
347            self.graphics.swapchain_needs_rebuild = false;
348        }
349
350        self.graphics.metrics.frame_tick();
351
352        // Update application logic
353        // ------------------------
354
355        let before_update = Instant::now();
356        if unwrap_here!(
357            "Demo::update()",
358            self.demo.update(window, &mut self.graphics)
359        ) == AppState::Exit
360        {
361            return Ok(AppState::Exit);
362        }
363        self.graphics.metrics.update_tick(before_update);
364
365        // Limit FPS, wait just before acquiring the frame
366        self.graphics.fps_limiter.tick();
367
368        // Prepare frame command buffer and submit
369        // ---------------------------------------
370
371        let before_draw = Instant::now();
372        let frame = match self
373            .graphics
374            .frames_in_flight
375            .start_frame(&self.graphics.swapchain)?
376        {
377            FrameStatus::FrameStarted(frame) => frame,
378            FrameStatus::SwapchainNeedsRebuild => {
379                self.graphics.swapchain_needs_rebuild = true;
380                return Ok(AppState::Continue);
381            }
382        };
383
384        if unwrap_here!(
385            "Demo draw",
386            self.demo.draw(window, &mut self.graphics, &frame)
387        ) == AppState::Exit
388        {
389            return Ok(AppState::Exit);
390        }
391
392        let result = self
393            .graphics
394            .frames_in_flight
395            .present_frame(&self.graphics.swapchain, frame)?;
396        if result == PresentImageStatus::SwapchainNeedsRebuild {
397            self.graphics.swapchain_needs_rebuild = true;
398        }
399        self.graphics.metrics.draw_tick(before_draw);
400
401        Ok(AppState::Continue)
402    }
403}
404
405/// The main entrypoint for a demo.
406pub fn demo_main<D: Demo + 'static>() -> Result<()> {
407    app_main::<DemoApp<D>>()
408}