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