1#![deny(missing_docs)]
4#![deny(rustdoc::broken_intra_doc_links)]
5
6mod frame;
7pub mod graphchain;
8
9pub use {self::frame::FrameContext, winit};
10
11use {
12 self::graphchain::{Graphchain, GraphchainError, GraphchainInfo},
13 log::{error, info, trace, warn},
14 std::{error, fmt, ops::Deref},
15 vk_graph::{
16 Graph,
17 driver::{
18 DriverError,
19 ash::vk,
20 device::{Device, DeviceInfo},
21 surface::Surface,
22 },
23 pool::hash::HashPool,
24 },
25 winit::raw_window_handle::{DisplayHandle, HandleError, HasDisplayHandle},
26 winit::{
27 application::ApplicationHandler,
28 error::EventLoopError,
29 event::{DeviceEvent, DeviceId, Event, WindowEvent},
30 event_loop::{ActiveEventLoop, EventLoop},
31 monitor::MonitorHandle,
32 window::{WindowAttributes, WindowId},
33 },
34};
35
36pub type SurfaceFormatFn = dyn Fn(&[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR;
38
39fn create_graphchain(
40 device: &Device,
41 data: &WindowData,
42 window: &winit::window::Window,
43) -> Result<Graphchain, DriverError> {
44 let surface = Surface::create(device, window, window)?;
45 let surface_formats = Surface::formats(&surface)?;
46 let surface_format = data
47 .surface_format_fn
48 .as_ref()
49 .map(|f| f(&surface_formats))
50 .unwrap_or_else(|| Surface::linear_or_default(&surface_formats));
51 let window_size = window.inner_size();
52
53 let mut graphchain_info =
54 GraphchainInfo::new(window_size.width, window_size.height, surface_format)
55 .into_builder()
56 .frame_capacity(data.cmd_buf_count);
57
58 if let Some(min_image_count) = data.min_image_count {
59 graphchain_info = graphchain_info.min_image_count(min_image_count);
60 }
61
62 let v_sync = data.v_sync.unwrap_or_default();
63 let present_modes = Surface::present_modes(&surface)?;
64 if !present_modes.is_empty() {
65 let best_modes = if v_sync {
66 [vk::PresentModeKHR::FIFO_RELAXED, vk::PresentModeKHR::FIFO].as_slice()
67 } else {
68 [vk::PresentModeKHR::MAILBOX, vk::PresentModeKHR::IMMEDIATE].as_slice()
69 };
70
71 graphchain_info = graphchain_info.present_mode(
72 best_modes
73 .iter()
74 .copied()
75 .find(|best| present_modes.contains(best))
76 .or_else(|| {
77 warn!("requested present modes unsupported: {best_modes:?}");
78
79 present_modes.first().copied()
80 })
81 .ok_or_else(|| {
82 error!("display does not support presentation");
83
84 DriverError::Unsupported
85 })?,
86 );
87 }
88
89 let graphchain = Graphchain::new(surface, graphchain_info)?;
90
91 trace!("created graphchain");
92
93 Ok(graphchain)
94}
95
96#[derive(Clone, Copy, Debug)]
98pub enum FullscreenMode {
99 Borderless,
101
102 Exclusive,
104}
105
106#[read_only::embed]
108pub struct Window {
109 data: WindowData,
110
111 #[readonly]
115 pub device: Device,
116
117 #[readonly]
118 pub(self) event_loop: EventLoop<()>,
119}
120
121impl Deref for ReadOnlyWindow {
122 type Target = EventLoop<()>;
123
124 fn deref(&self) -> &Self::Target {
125 &self.event_loop
126 }
127}
128
129impl Window {
130 pub fn new() -> Result<Self, WindowError> {
132 Self::builder().build()
133 }
134
135 pub fn builder() -> WindowBuilder {
137 Default::default()
138 }
139
140 pub fn run<F>(self, draw_fn: F) -> Result<(), WindowError>
142 where
143 F: FnMut(FrameContext),
144 {
145 struct Application<F> {
146 active_window: Option<ActiveWindow>,
147 data: WindowData,
148 device: Device,
149 draw_fn: F,
150 error: Option<WindowError>,
151 primary_monitor: Option<MonitorHandle>,
152 }
153
154 impl<F> Application<F> {
155 fn create_graphchain(
156 &mut self,
157 window: &winit::window::Window,
158 ) -> Result<Graphchain, DriverError> {
159 create_graphchain(&self.device, &self.data, window)
160 }
161
162 fn window_mode_attributes(
163 &self,
164 attributes: WindowAttributes,
165 window_mode_override: Option<Option<FullscreenMode>>,
166 ) -> WindowAttributes {
167 match window_mode_override {
168 Some(Some(mode)) => {
169 let inner_size;
170 let attributes = attributes
171 .with_decorations(false)
172 .with_maximized(true)
173 .with_fullscreen(Some(match mode {
174 FullscreenMode::Borderless => {
175 info!("Using borderless fullscreen");
176
177 inner_size = None;
178
179 winit::window::Fullscreen::Borderless(None)
180 }
181 FullscreenMode::Exclusive => {
182 if let Some(video_mode) =
183 self.primary_monitor.as_ref().and_then(|monitor| {
184 let monitor_size = monitor.size();
185 monitor.video_modes().find(|mode| {
186 let mode_size = mode.size();
187
188 mode_size.height <= monitor_size.height
193 && mode_size.width <= monitor_size.width
194 })
195 })
196 {
197 info!(
198 "Using {}x{} {}bpp @ {}hz exclusive fullscreen",
199 video_mode.size().width,
200 video_mode.size().height,
201 video_mode.bit_depth(),
202 video_mode.refresh_rate_millihertz() / 1_000
203 );
204
205 inner_size = Some(video_mode.size());
206
207 winit::window::Fullscreen::Exclusive(video_mode)
208 } else {
209 warn!("unsupported exclusive fullscreen mode");
210
211 inner_size = None;
212
213 winit::window::Fullscreen::Borderless(None)
214 }
215 }
216 }));
217
218 if let Some(inner_size) = inner_size
219 .or_else(|| self.primary_monitor.as_ref().map(|monitor| monitor.size()))
220 {
221 attributes.with_inner_size(inner_size)
222 } else {
223 attributes
224 }
225 }
226 Some(None) => attributes.with_fullscreen(None),
227 _ => attributes,
228 }
229 }
230 }
231
232 impl<F> ApplicationHandler for Application<F>
233 where
234 F: FnMut(FrameContext),
235 {
236 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
237 if event_loop.exiting() {
238 return;
239 }
240
241 if let Some(ActiveWindow { window, .. }) = self.active_window.as_ref() {
242 window.request_redraw();
243 }
244 }
245
246 fn device_event(
247 &mut self,
248 _event_loop: &ActiveEventLoop,
249 device_id: DeviceId,
250 event: DeviceEvent,
251 ) {
252 if let Some(ActiveWindow { events, .. }) = self.active_window.as_mut() {
253 events.push(Event::DeviceEvent { device_id, event });
254 }
255 }
256
257 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
258 info!("Resumed");
259
260 self.data.attributes = self.window_mode_attributes(
261 self.data.attributes.clone(),
262 self.data.window_mode_override,
263 );
264
265 let window = match event_loop.create_window(self.data.attributes.clone()) {
266 Err(err) => {
267 warn!("unable to create window: {err}");
268
269 self.error = Some(EventLoopError::Os(err).into());
270 event_loop.exit();
271
272 return;
273 }
274 Ok(res) => res,
275 };
276 let graphchain = match self.create_graphchain(&window) {
277 Err(err) => {
278 warn!("unable to create graphchain: {err}");
279
280 self.error = Some(err.into());
281 event_loop.exit();
282
283 return;
284 }
285 Ok(res) => res,
286 };
287 let swapchain_pool = HashPool::new(&self.device);
288
289 self.active_window = Some(ActiveWindow {
290 graphchain: Some(graphchain),
291 swapchain_pool,
292 swapchain_resize: None,
293 events: vec![],
294 window,
295 });
296 }
297
298 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: ()) {
299 info!("signal received, exiting event loop");
300
301 event_loop.exit();
302
303 if let Some(ActiveWindow { events, .. }) = self.active_window.as_mut() {
304 events.push(Event::UserEvent(event));
305 }
306 }
307
308 fn window_event(
309 &mut self,
310 event_loop: &ActiveEventLoop,
311 window_id: WindowId,
312 event: WindowEvent,
313 ) {
314 if let Some(mut active_window) = self.active_window.take() {
315 match &event {
316 WindowEvent::CloseRequested => {
317 if event_loop.exiting() {
318 self.active_window = Some(active_window);
319
320 return;
321 }
322
323 info!("close requested");
324
325 event_loop.exit();
326 self.active_window = Some(active_window);
327 }
328 WindowEvent::RedrawRequested => {
329 if event_loop.exiting() {
330 self.active_window = Some(active_window);
331
332 return;
333 }
334
335 match active_window.draw(&self.device, &self.data, &mut self.draw_fn) {
337 Err(GraphchainError::SurfaceLost) => {
338 warn!("surface lost; abandoning current frame");
339
340 let _ = active_window.graphchain.take();
341
342 active_window.window.request_redraw();
343 self.active_window = Some(active_window);
344
345 return;
346 }
347 Err(err) => {
348 self.error = Some(WindowError::Graphchain(err));
349 event_loop.exit();
350 }
351 Ok(false) => {
352 event_loop.exit();
353 }
354 Ok(true) => {}
355 }
356
357 self.active_window = Some(active_window);
358 }
359 WindowEvent::Resized(size) if size.width * size.height > 0 => {
360 active_window.swapchain_resize = Some((size.width, size.height));
361 self.active_window = Some(active_window);
362 }
363 _ => self.active_window = Some(active_window),
364 }
365
366 if let Some(active_window) = self.active_window.as_mut() {
367 active_window
368 .events
369 .push(Event::WindowEvent { window_id, event });
370 }
371 }
372 }
373 }
374
375 struct ActiveWindow {
376 graphchain: Option<Graphchain>,
377 swapchain_pool: HashPool,
378 swapchain_resize: Option<(u32, u32)>,
379 events: Vec<Event<()>>,
380 window: winit::window::Window,
381 }
382
383 impl ActiveWindow {
384 fn draw(
385 &mut self,
386 device: &Device,
387 data: &WindowData,
388 mut f: impl FnMut(FrameContext),
389 ) -> Result<bool, GraphchainError> {
390 profiling::scope!("Frame");
391
392 if self.graphchain.is_none() {
393 self.graphchain = Some(create_graphchain(device, data, &self.window)?);
394 }
395
396 let graphchain = self.graphchain.as_mut().expect("missing graphchain");
397
398 if let Some((width, height)) = self.swapchain_resize.take() {
399 if width == 0 || height == 0 {
400 self.swapchain_resize = Some((width, height));
401 self.events.clear();
402
403 return Ok(true);
404 }
405
406 let mut graphchain_info = graphchain.info;
407 graphchain_info.width = width;
408 graphchain_info.height = height;
409 graphchain.set_info(graphchain_info);
410 }
411
412 if let Some(swapchain_image) = graphchain.acquire_next_image()? {
413 let mut graph = Graph::default();
414 let swapchain_image = graph.bind_resource(swapchain_image);
415 let graphchain_info = graphchain.info;
416
417 let mut will_exit = false;
418
419 trace!("drawing");
420
421 f(FrameContext {
422 device,
423 events: &self.events,
424 height: graphchain_info.height,
425 graph: &mut graph,
426 swapchain_image,
427 width: graphchain_info.width,
428 will_exit: &mut will_exit,
429 window: &self.window,
430 });
431
432 self.events.clear();
433
434 if will_exit {
435 info!("exit requested");
436
437 return Ok(false);
438 }
439
440 self.window.pre_present_notify();
441 graphchain
442 .present_image(&mut self.swapchain_pool, graph, swapchain_image, 0)
443 .inspect_err(|err| {
444 warn!("unable to present graphchain image: {err}");
445 })?;
446 } else {
447 warn!("unable to acquire graphchain image");
448 }
449
450 self.window.request_redraw();
451
452 profiling::finish_frame!();
453
454 Ok(true)
455 }
456 }
457
458 let mut app = Application {
459 active_window: None,
460 data: self.data,
461 device: self.read_only.device,
462 draw_fn,
463 error: None,
464 primary_monitor: None,
465 };
466
467 let proxy = self.read_only.event_loop.create_proxy();
468 if let Err(e) = ctrlc::set_handler(move || {
469 trace!("received SIGINT/SIGTERM");
470
471 let _ = proxy.send_event(());
472 }) {
473 warn!("failed to set Ctrl-C handler: {e}");
474 }
475
476 self.read_only.event_loop.run_app(&mut app)?;
477
478 if let Some(ActiveWindow {
479 graphchain, window, ..
480 }) = app.active_window.take()
481 {
482 drop(graphchain);
483 drop(window);
484 }
485
486 info!("Window closed");
487
488 if let Some(err) = app.error {
489 Err(err)
490 } else {
491 Ok(())
492 }
493 }
494}
495
496impl HasDisplayHandle for Window {
497 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
498 self.event_loop.display_handle()
499 }
500}
501
502pub struct WindowBuilder {
504 attributes: WindowAttributes,
505 cmd_buf_count: usize,
506 device_info: DeviceInfo,
507 min_image_count: Option<u32>,
508 surface_format_fn: Option<Box<SurfaceFormatFn>>,
509 v_sync: Option<bool>,
510 window_mode_override: Option<Option<FullscreenMode>>,
511}
512
513impl WindowBuilder {
514 pub fn build(self) -> Result<Window, WindowError> {
516 let event_loop = EventLoop::new()?;
517 let device = Device::try_from_display(&event_loop, self.device_info)?;
518
519 Ok(Window {
520 data: WindowData {
521 attributes: self.attributes,
522 cmd_buf_count: self.cmd_buf_count,
523 min_image_count: self.min_image_count,
524 surface_format_fn: self.surface_format_fn,
525 v_sync: self.v_sync,
526 window_mode_override: self.window_mode_override,
527 },
528 read_only: ReadOnlyWindow { device, event_loop },
529 })
530 }
531
532 pub fn command_buffer_count(mut self, count: usize) -> Self {
541 self.cmd_buf_count = count;
542 self
543 }
544
545 pub fn debug(mut self, enabled: bool) -> Self {
554 self.device_info.debug = enabled;
555 self
556 }
557
558 pub fn desired_surface_format<F>(mut self, f: F) -> Self
562 where
563 F: 'static + Fn(&[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR,
564 {
565 self.surface_format_fn = Some(Box::new(f));
566 self
567 }
568
569 pub fn min_image_count(mut self, count: u32) -> Self {
575 self.min_image_count = Some(count);
576 self
577 }
578
579 pub fn fullscreen_mode(mut self, mode: FullscreenMode) -> Self {
587 self.window_mode_override = Some(Some(mode));
588 self
589 }
590
591 pub fn v_sync(mut self, enabled: bool) -> Self {
597 self.v_sync = Some(enabled);
598 self
599 }
600
601 pub fn window<WindowFn>(mut self, f: WindowFn) -> Self
603 where
604 WindowFn: FnOnce(WindowAttributes) -> WindowAttributes,
605 {
606 self.attributes = f(self.attributes);
607 self
608 }
609
610 pub fn window_mode(mut self) -> Self {
617 self.window_mode_override = Some(None);
618 self
619 }
620}
621
622impl fmt::Debug for WindowBuilder {
623 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
624 f.debug_struct("WindowBuilder")
625 .field("attributes", &self.attributes)
626 .field("cmd_buffer_count", &self.cmd_buf_count)
627 .field("device_info", &self.device_info)
628 .field("min_image_count", &self.min_image_count)
629 .field(
630 "surface_format_fn",
631 &self.surface_format_fn.as_ref().map(|_| ()),
632 )
633 .field("v_sync", &self.v_sync)
634 .field("window_mode_override", &self.window_mode_override)
635 .finish()
636 }
637}
638
639impl Default for WindowBuilder {
640 fn default() -> Self {
641 Self {
642 attributes: Default::default(),
643 cmd_buf_count: 5,
644 device_info: Default::default(),
645 min_image_count: None,
646 surface_format_fn: None,
647 v_sync: None,
648 window_mode_override: None,
649 }
650 }
651}
652
653struct WindowData {
654 attributes: WindowAttributes,
655 cmd_buf_count: usize,
656 min_image_count: Option<u32>,
657 surface_format_fn: Option<Box<SurfaceFormatFn>>,
658 v_sync: Option<bool>,
659 window_mode_override: Option<Option<FullscreenMode>>,
660}
661
662#[derive(Debug)]
664pub enum WindowError {
665 Driver(DriverError),
667 EventLoop(EventLoopError),
669 Graphchain(GraphchainError),
671}
672
673impl error::Error for WindowError {
674 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
675 Some(match self {
676 Self::Driver(err) => err,
677 Self::EventLoop(err) => err,
678 Self::Graphchain(err) => err,
679 })
680 }
681}
682
683impl fmt::Display for WindowError {
684 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685 match self {
686 Self::Driver(err) => err.fmt(f),
687 Self::EventLoop(err) => err.fmt(f),
688 Self::Graphchain(err) => err.fmt(f),
689 }
690 }
691}
692
693impl From<DriverError> for WindowError {
694 fn from(err: DriverError) -> Self {
695 Self::Driver(err)
696 }
697}
698
699impl From<EventLoopError> for WindowError {
700 fn from(err: EventLoopError) -> Self {
701 Self::EventLoop(err)
702 }
703}