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
33pub struct Graphics {
35 pub vulkan: Arc<VulkanContext>,
37
38 pub frames_in_flight: FramesInFlight,
40
41 pub swapchain: Swapchain,
43
44 pub metrics: FrameMetrics,
45
46 fps_limiter: Interval,
47
48 swapchain_needs_rebuild: bool,
49 paused: bool,
50}
51
52pub 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 fn new(
74 window: &mut Window,
75 gfx: &mut Graphics,
76 args: &Self::Args,
77 ) -> Result<Self>
78 where
79 Self: Sized;
80
81 fn required_device_features() -> RequiredDeviceFeatures {
83 RequiredDeviceFeatures::default()
84 }
85
86 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 return Ok(AppState::Exit);
97 }
98 }
99 Ok(AppState::Continue)
100 }
101
102 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 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 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 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 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 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 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 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 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 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 self.graphics.fps_limiter.tick();
367
368 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
405pub fn demo_main<D: Demo + 'static>() -> Result<()> {
407 app_main::<DemoApp<D>>()
408}