1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
mod frame_metrics;
mod rolling_average;

use {
    self::frame_metrics::FrameMetrics,
    crate::{
        app::{app_main, App},
        graphics::vulkan::{
            Frame, FrameStatus, FramesInFlight, PresentImageStatus, Swapchain,
            VulkanContext,
        },
        trace,
    },
    anyhow::{Context, Result},
    ash::vk,
    clap::Parser,
    spin_sleep_util::Interval,
    std::{
        sync::Arc,
        time::{Duration, Instant},
    },
};

/// Standard graphics resources provided by the Demo.
pub struct Graphics<A> {
    /// The Vulkan entrypoint
    pub vulkan: Arc<VulkanContext>,

    /// The application's swapchain.
    pub swapchain: Swapchain,

    /// FramesInFlight synchronizes N frames in flight.
    pub frames_in_flight: FramesInFlight,

    /// The demo's cli args
    pub args: A,

    pub metrics: FrameMetrics,

    fps_limiter: Interval,

    swapchain_needs_rebuild: bool,
    paused: bool,
}

/// A demo is an opinionated application that automatically creates the
/// VulkanContext, Swapchain, FramesInFlight and other common utilities.
///
/// The demo splits the application's update() function into two parts:
/// * update(): update the application's state
/// * draw(): build the frame's command buffer
///
/// Draw is separate because update() does not wait for the next swapchain
/// image. There are some operations that may not depend on the swapchain and
/// can reasonably be started before the frame's command buffer is ready to be
/// recorded. (ticking a physics simulation, etc...)
pub trait Demo {
    type Args: Sized + Parser;
    const FRAMES_IN_FLIGHT_COUNT: usize = 3;
    const INITIAL_WINDOW_SIZE: (i32, i32) = (1024, 768);
    const FRAMES_PER_SECOND: u32 = 120;

    /// Creates a new instance of the demo.
    /// The application is allowed to modify the window based on its own
    /// requirements. This includes modifying the polling state, fullscreen
    /// status, size, etc...
    fn new(
        window: &mut glfw::Window,
        gfx: &mut Graphics<Self::Args>,
    ) -> Result<Self>
    where
        Self: Sized;

    /// Handles a single GLFW event.
    ///
    /// This function is called in a loop to consume any pending events before
    /// every call to update().
    fn handle_event(
        &mut self,
        #[allow(unused_variables)] window: &mut glfw::Window,
        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
        #[allow(unused_variables)] event: glfw::WindowEvent,
    ) -> Result<()> {
        if let glfw::WindowEvent::Key(
            glfw::Key::Escape,
            _,
            glfw::Action::Repeat,
            _,
        ) = event
        {
            window.set_should_close(true);
        }
        Ok(())
    }

    /// Called in a loop after all pending events have been processed.
    ///
    /// This is a good place for rendering logic. This method blocks event
    /// processing, so it should be kept as responsive as possible.
    fn update(
        &mut self,
        #[allow(unused_variables)] window: &mut glfw::Window,
        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
    ) -> Result<()> {
        Ok(())
    }

    /// Build the command buffer for the next frame.
    ///
    /// Called after update() once the Frame is started.
    fn draw(
        &mut self,
        #[allow(unused_variables)] window: &mut glfw::Window,
        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
        #[allow(unused_variables)] frame: &Frame,
    ) -> Result<()> {
        // By default, this method does nothing but transition the swapchain
        // image to presentation format.
        //
        // Normally, a real application will use a render pass to do this.

        let image_memory_barrier = vk::ImageMemoryBarrier {
            old_layout: vk::ImageLayout::UNDEFINED,
            new_layout: vk::ImageLayout::PRESENT_SRC_KHR,
            image: frame.swapchain_image(),
            subresource_range: vk::ImageSubresourceRange {
                aspect_mask: vk::ImageAspectFlags::COLOR,
                base_mip_level: 0,
                level_count: 1,
                base_array_layer: 0,
                layer_count: 1,
            },
            ..Default::default()
        };
        unsafe {
            gfx.vulkan.cmd_pipeline_barrier(
                frame.command_buffer(),
                vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
                vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
                vk::DependencyFlags::empty(),
                &[],
                &[],
                &[image_memory_barrier],
            );
        }

        Ok(())
    }

    /// Rebuild any of the demo's swapchain-dependent resources.
    ///
    /// All pending frames in flight are guaranteed to have completed by the
    /// time this function is called. E.G. any previously-recorded Frame command
    /// buffers are guaranteed to be finished.
    fn rebuild_swapchain_resources(
        &mut self,
        #[allow(unused_variables)] window: &mut glfw::Window,
        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
    ) -> Result<()> {
        Ok(())
    }

    /// Called when the application is paused.
    ///
    /// The application is paused automatically any time the framebuffer has
    /// size of 0. This occurs when the application is minimized, etc..
    fn paused(
        &mut self,
        #[allow(unused_variables)] window: &mut glfw::Window,
        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
    ) -> Result<()> {
        Ok(())
    }

    /// Called when the application is unpaused.
    fn unpaused(
        &mut self,
        #[allow(unused_variables)] window: &mut glfw::Window,
        #[allow(unused_variables)] gfx: &mut Graphics<Self::Args>,
    ) -> Result<()> {
        Ok(())
    }
}

struct DemoApp<D: Demo> {
    graphics: Graphics<D::Args>,
    demo: D,
}

impl<D: Demo> DemoApp<D> {
    /// Called any time the framebuffer size may have changed.
    /// Returns the current paused status for convenience.
    fn framebuffer_size_changed(
        &mut self,
        window: &mut glfw::Window,
    ) -> Result<bool> {
        let (w, h) = window.get_framebuffer_size();
        let should_pause = w == 0 || h == 0;

        if should_pause {
            if !self.graphics.paused {
                self.demo.paused(window, &mut self.graphics)?;
            }
            self.graphics.paused = true;
        } else {
            if self.graphics.paused {
                self.demo.unpaused(window, &mut self.graphics)?;
            }
            self.graphics.paused = false;
            self.graphics.metrics.unpause();
        }
        Ok(self.graphics.paused)
    }
}

impl<D: Demo + Sized> App for DemoApp<D> {
    type Args = D::Args;

    fn new(window: &mut glfw::Window, args: Self::Args) -> Result<Self>
    where
        Self: Sized,
    {
        window.set_size(D::INITIAL_WINDOW_SIZE.0, D::INITIAL_WINDOW_SIZE.1);
        window.set_title(std::any::type_name::<D>());

        let vulkan = VulkanContext::new(window)
            .with_context(trace!("Unable to create the Vulkan Context!"))?;

        let (w, h) = window.get_framebuffer_size();
        let swapchain =
            Swapchain::new(vulkan.clone(), (w as u32, h as u32), None)
                .with_context(trace!("Unable to create the swapchain!"))?;

        let frames_in_flight =
            FramesInFlight::new(vulkan.clone(), D::FRAMES_IN_FLIGHT_COUNT)
                .with_context(trace!("Unable to create frames in flight!"))?;

        let fps_limiter = spin_sleep_util::interval(Duration::from_secs_f64(
            1.0 / D::FRAMES_PER_SECOND as f64,
        ));

        let mut graphics = Graphics {
            fps_limiter,
            vulkan,
            swapchain,
            frames_in_flight,
            args,
            metrics: FrameMetrics::new(D::FRAMES_PER_SECOND as usize),
            swapchain_needs_rebuild: false,
            paused: false,
        };
        let demo = D::new(window, &mut graphics)
            .with_context(trace!("Error initializing demo!"))?;

        Ok(Self { graphics, demo })
    }

    fn handle_event(
        &mut self,
        window: &mut glfw::Window,
        event: glfw::WindowEvent,
    ) -> Result<()> {
        self.demo.handle_event(window, &mut self.graphics, event)
    }

    fn update(&mut self, window: &mut glfw::Window) -> Result<()> {
        if self.graphics.paused && self.framebuffer_size_changed(window)? {
            std::hint::spin_loop();
            return Ok(());
        }

        if self.graphics.swapchain_needs_rebuild {
            self.graphics
                .frames_in_flight
                .wait_for_all_frames_to_complete()
                .with_context(trace!(
                    "Error waiting for frames before swapchain rebuild!"
                ))?;
            if self.framebuffer_size_changed(window)? {
                return Ok(());
            }
            let (w, h) = window.get_framebuffer_size();
            self.graphics.swapchain = Swapchain::new(
                self.graphics.vulkan.clone(),
                (w as u32, h as u32),
                Some(self.graphics.swapchain.raw()),
            )
            .with_context(trace!("Error while rebuilding swapchain!"))?;

            self.demo
                .rebuild_swapchain_resources(window, &mut self.graphics)
                .with_context(trace!(
                    "Error while rebuilding demo swapchain resources!"
                ))?;

            self.graphics.swapchain_needs_rebuild = false;
        }

        self.graphics.metrics.frame_tick();

        // Update application logic
        // ------------------------

        let before_update = Instant::now();
        self.demo
            .update(window, &mut self.graphics)
            .with_context(trace!("Unhandled error in Demo::update()!"))?;
        self.graphics.metrics.update_tick(before_update);

        // Limit FPS, wait just before acquiring the frame
        self.graphics.fps_limiter.tick();

        // Prepare frame command buffer and submit
        // ---------------------------------------

        let before_draw = Instant::now();
        let frame = match self
            .graphics
            .frames_in_flight
            .start_frame(&self.graphics.swapchain)?
        {
            FrameStatus::FrameStarted(frame) => frame,
            FrameStatus::SwapchainNeedsRebuild => {
                self.graphics.swapchain_needs_rebuild = true;
                return Ok(());
            }
        };

        self.demo
            .draw(window, &mut self.graphics, &frame)
            .with_context(trace!("Unhandled error in Demo::draw()!"))?;

        let result = self
            .graphics
            .frames_in_flight
            .present_frame(&self.graphics.swapchain, frame)?;
        if result == PresentImageStatus::SwapchainNeedsRebuild {
            self.graphics.swapchain_needs_rebuild = true;
        }
        self.graphics.metrics.draw_tick(before_draw);

        Ok(())
    }
}

/// The main entrypoint for a demo.
pub fn demo_main<D: Demo + 'static>() {
    app_main::<DemoApp<D>>();
}