demo_vk/demo/
mod.rs

1mod frame_metrics;
2mod rolling_average;
3
4use {
5    self::frame_metrics::FrameMetrics,
6    crate::{
7        app::{app_main, App},
8        graphics::vulkan::{
9            Frame, FrameStatus, FramesInFlight, PresentImageStatus,
10            RequiredDeviceFeatures, Swapchain, VulkanContext,
11        },
12        trace,
13    },
14    anyhow::{Context, Result},
15    ash::vk,
16    clap::Parser,
17    spin_sleep_util::Interval,
18    std::{
19        sync::Arc,
20        time::{Duration, Instant},
21    },
22};
23
24/// Standard graphics resources provided by the Demo.
25pub struct Graphics<A> {
26    /// The Vulkan entrypoint
27    pub vulkan: Arc<VulkanContext>,
28
29    /// FramesInFlight synchronizes N frames in flight.
30    pub frames_in_flight: FramesInFlight,
31
32    /// The application's swapchain.
33    pub swapchain: Swapchain,
34
35    /// The demo's cli args
36    pub args: A,
37
38    pub metrics: FrameMetrics,
39
40    fps_limiter: Interval,
41
42    swapchain_needs_rebuild: bool,
43    paused: bool,
44}
45
46/// A demo is an opinionated application that automatically creates the
47/// VulkanContext, Swapchain, FramesInFlight and other common utilities.
48///
49/// The demo splits the application's update() function into two parts:
50/// * update(): update the application's state
51/// * draw(): build the frame's command buffer
52///
53/// Draw is separate because update() does not wait for the next swapchain
54/// image. There are some operations that may not depend on the swapchain and
55/// can reasonably be started before the frame's command buffer is ready to be
56/// recorded. (ticking a physics simulation, etc...)
57pub trait Demo {
58    type Args: Sized + Parser;
59    const FRAMES_IN_FLIGHT_COUNT: usize = 3;
60    const INITIAL_WINDOW_SIZE: (i32, i32) = (1024, 768);
61    const FRAMES_PER_SECOND: u32 = 120;
62
63    /// Creates a new instance of the demo.
64    /// The application is allowed to modify the window based on its own
65    /// requirements. This includes modifying the polling state, fullscreen
66    /// status, size, etc...
67    fn new(
68        window: &mut glfw::Window,
69        gfx: &mut Graphics<Self::Args>,
70    ) -> Result<Self>
71    where
72        Self: Sized;
73
74    /// Returns the required device features for this demo.
75    fn required_device_features() -> RequiredDeviceFeatures {
76        RequiredDeviceFeatures::default()
77    }
78
79    /// Handles a single GLFW event.
80    ///
81    /// This function is called in a loop to consume any pending events before
82    /// every call to update().
83    fn handle_event(
84        &mut self,
85        #[allow(unused_variables)] window: &mut glfw::Window,
86        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
87        #[allow(unused_variables)] event: glfw::WindowEvent,
88    ) -> Result<()> {
89        if let glfw::WindowEvent::Key(
90            glfw::Key::Escape,
91            _,
92            glfw::Action::Repeat,
93            _,
94        ) = event
95        {
96            window.set_should_close(true);
97        }
98        Ok(())
99    }
100
101    /// Called in a loop after all pending events have been processed.
102    ///
103    /// This is a good place for rendering logic. This method blocks event
104    /// processing, so it should be kept as responsive as possible.
105    fn update(
106        &mut self,
107        #[allow(unused_variables)] window: &mut glfw::Window,
108        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
109    ) -> Result<()> {
110        Ok(())
111    }
112
113    /// Build the command buffer for the next frame.
114    ///
115    /// Called after update() once the Frame is started.
116    fn draw(
117        &mut self,
118        #[allow(unused_variables)] window: &mut glfw::Window,
119        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
120        #[allow(unused_variables)] frame: &Frame,
121    ) -> Result<()> {
122        // By default, this method does nothing but transition the swapchain
123        // image to presentation format.
124        //
125        // Normally, a real application will use a render pass to do this.
126
127        let image_memory_barrier = vk::ImageMemoryBarrier {
128            old_layout: vk::ImageLayout::UNDEFINED,
129            new_layout: vk::ImageLayout::PRESENT_SRC_KHR,
130            image: frame.swapchain_image(),
131            subresource_range: vk::ImageSubresourceRange {
132                aspect_mask: vk::ImageAspectFlags::COLOR,
133                base_mip_level: 0,
134                level_count: 1,
135                base_array_layer: 0,
136                layer_count: 1,
137            },
138            ..Default::default()
139        };
140        unsafe {
141            gfx.vulkan.cmd_pipeline_barrier(
142                frame.command_buffer(),
143                vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
144                vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
145                vk::DependencyFlags::empty(),
146                &[],
147                &[],
148                &[image_memory_barrier],
149            );
150        }
151
152        Ok(())
153    }
154
155    /// Rebuild any of the demo's swapchain-dependent resources.
156    ///
157    /// All pending frames in flight are guaranteed to have completed by the
158    /// time this function is called. E.G. any previously-recorded Frame command
159    /// buffers are guaranteed to be finished.
160    fn rebuild_swapchain_resources(
161        &mut self,
162        #[allow(unused_variables)] window: &mut glfw::Window,
163        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
164    ) -> Result<()> {
165        Ok(())
166    }
167
168    /// Called when the application is paused.
169    ///
170    /// The application is paused automatically any time the framebuffer has
171    /// size of 0. This occurs when the application is minimized, etc..
172    fn paused(
173        &mut self,
174        #[allow(unused_variables)] window: &mut glfw::Window,
175        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
176    ) -> Result<()> {
177        Ok(())
178    }
179
180    /// Called when the application is unpaused.
181    fn unpaused(
182        &mut self,
183        #[allow(unused_variables)] window: &mut glfw::Window,
184        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
185    ) -> Result<()> {
186        Ok(())
187    }
188}
189
190struct DemoApp<D: Demo> {
191    graphics: Graphics<D::Args>,
192    demo: D,
193}
194
195impl<D: Demo> DemoApp<D> {
196    /// Called any time the framebuffer size may have changed.
197    /// Returns the current paused status for convenience.
198    fn framebuffer_size_changed(
199        &mut self,
200        window: &mut glfw::Window,
201    ) -> Result<bool> {
202        let (w, h) = window.get_framebuffer_size();
203        let should_pause = w == 0 || h == 0;
204
205        if should_pause {
206            if !self.graphics.paused {
207                self.demo.paused(window, &mut self.graphics)?;
208            }
209            self.graphics.paused = true;
210        } else {
211            if self.graphics.paused {
212                self.demo.unpaused(window, &mut self.graphics)?;
213            }
214            self.graphics.paused = false;
215            self.graphics.metrics.unpause();
216        }
217        Ok(self.graphics.paused)
218    }
219}
220
221impl<D: Demo + Sized> App for DemoApp<D> {
222    type Args = D::Args;
223
224    fn new(window: &mut glfw::Window, args: Self::Args) -> Result<Self>
225    where
226        Self: Sized,
227    {
228        window.set_size(D::INITIAL_WINDOW_SIZE.0, D::INITIAL_WINDOW_SIZE.1);
229        window.set_title(std::any::type_name::<D>());
230
231        let vulkan = VulkanContext::new(window, D::required_device_features())
232            .with_context(trace!("Unable to create the Vulkan Context!"))?;
233
234        let (w, h) = window.get_framebuffer_size();
235        let swapchain =
236            Swapchain::new(vulkan.clone(), (w as u32, h as u32), None)
237                .with_context(trace!("Unable to create the swapchain!"))?;
238
239        let frames_in_flight = FramesInFlight::new(
240            vulkan.clone(),
241            swapchain.images().len(),
242            D::FRAMES_IN_FLIGHT_COUNT,
243        )
244        .with_context(trace!("Unable to create frames in flight!"))?;
245
246        let fps_limiter = spin_sleep_util::interval(Duration::from_secs_f64(
247            1.0 / D::FRAMES_PER_SECOND as f64,
248        ));
249
250        let mut graphics = Graphics {
251            fps_limiter,
252            vulkan,
253            swapchain,
254            frames_in_flight,
255            args,
256            metrics: FrameMetrics::new(D::FRAMES_PER_SECOND as usize),
257            swapchain_needs_rebuild: false,
258            paused: false,
259        };
260        let demo = D::new(window, &mut graphics)
261            .with_context(trace!("Error initializing demo!"))?;
262
263        Ok(Self { graphics, demo })
264    }
265
266    fn handle_event(
267        &mut self,
268        window: &mut glfw::Window,
269        event: glfw::WindowEvent,
270    ) -> Result<()> {
271        self.demo.handle_event(window, &mut self.graphics, event)
272    }
273
274    fn update(&mut self, window: &mut glfw::Window) -> Result<()> {
275        if self.graphics.paused && self.framebuffer_size_changed(window)? {
276            std::hint::spin_loop();
277            return Ok(());
278        }
279
280        if self.graphics.swapchain_needs_rebuild {
281            self.graphics
282                .frames_in_flight
283                .wait_for_all_frames_to_complete()
284                .with_context(trace!(
285                    "Error waiting for frames before swapchain rebuild!"
286                ))?;
287            if self.framebuffer_size_changed(window)? {
288                return Ok(());
289            }
290            let (w, h) = window.get_framebuffer_size();
291            self.graphics.swapchain = Swapchain::new(
292                self.graphics.vulkan.clone(),
293                (w as u32, h as u32),
294                Some(self.graphics.swapchain.raw()),
295            )
296            .with_context(trace!("Error while rebuilding swapchain!"))?;
297
298            self.demo
299                .rebuild_swapchain_resources(window, &mut self.graphics)
300                .with_context(trace!(
301                    "Error while rebuilding demo swapchain resources!"
302                ))?;
303
304            self.graphics.swapchain_needs_rebuild = false;
305        }
306
307        self.graphics.metrics.frame_tick();
308
309        // Update application logic
310        // ------------------------
311
312        let before_update = Instant::now();
313        self.demo
314            .update(window, &mut self.graphics)
315            .with_context(trace!("Unhandled error in Demo::update()!"))?;
316        self.graphics.metrics.update_tick(before_update);
317
318        // Limit FPS, wait just before acquiring the frame
319        self.graphics.fps_limiter.tick();
320
321        // Prepare frame command buffer and submit
322        // ---------------------------------------
323
324        let before_draw = Instant::now();
325        let frame = match self
326            .graphics
327            .frames_in_flight
328            .start_frame(&self.graphics.swapchain)?
329        {
330            FrameStatus::FrameStarted(frame) => frame,
331            FrameStatus::SwapchainNeedsRebuild => {
332                self.graphics.swapchain_needs_rebuild = true;
333                return Ok(());
334            }
335        };
336
337        self.demo
338            .draw(window, &mut self.graphics, &frame)
339            .with_context(trace!("Unhandled error in Demo::draw()!"))?;
340
341        let result = self
342            .graphics
343            .frames_in_flight
344            .present_frame(&self.graphics.swapchain, frame)?;
345        if result == PresentImageStatus::SwapchainNeedsRebuild {
346            self.graphics.swapchain_needs_rebuild = true;
347        }
348        self.graphics.metrics.draw_tick(before_draw);
349
350        Ok(())
351    }
352}
353
354/// The main entrypoint for a demo.
355pub fn demo_main<D: Demo + 'static>() -> Result<()> {
356    app_main::<DemoApp<D>>()
357}