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
24pub struct Graphics<A> {
26 pub vulkan: Arc<VulkanContext>,
28
29 pub frames_in_flight: FramesInFlight,
31
32 pub swapchain: Swapchain,
34
35 pub args: A,
37
38 pub metrics: FrameMetrics,
39
40 fps_limiter: Interval,
41
42 swapchain_needs_rebuild: bool,
43 paused: bool,
44}
45
46pub 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 fn new(
68 window: &mut glfw::Window,
69 gfx: &mut Graphics<Self::Args>,
70 ) -> Result<Self>
71 where
72 Self: Sized;
73
74 fn required_device_features() -> RequiredDeviceFeatures {
76 RequiredDeviceFeatures::default()
77 }
78
79 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 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 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 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 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 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 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 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 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 self.graphics.fps_limiter.tick();
320
321 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
354pub fn demo_main<D: Demo + 'static>() -> Result<()> {
356 app_main::<DemoApp<D>>()
357}