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 profiling::finish_frame!();
346
347 return;
348 }
349 Err(err) => {
350 self.error = Some(WindowError::Graphchain(err));
351 event_loop.exit();
352 }
353 Ok(false) => {
354 event_loop.exit();
355 }
356 Ok(true) => {}
357 }
358
359 profiling::finish_frame!();
360 self.active_window = Some(active_window);
361 }
362 WindowEvent::Resized(size) if size.width * size.height > 0 => {
363 active_window.swapchain_resize = Some((size.width, size.height));
364 self.active_window = Some(active_window);
365 }
366 _ => self.active_window = Some(active_window),
367 }
368
369 if let Some(active_window) = self.active_window.as_mut() {
370 active_window
371 .events
372 .push(Event::WindowEvent { window_id, event });
373 }
374 }
375 }
376 }
377
378 struct ActiveWindow {
379 graphchain: Option<Graphchain>,
380 swapchain_pool: HashPool,
381 swapchain_resize: Option<(u32, u32)>,
382 events: Vec<Event<()>>,
383 window: winit::window::Window,
384 }
385
386 impl ActiveWindow {
387 fn draw(
388 &mut self,
389 device: &Device,
390 data: &WindowData,
391 mut f: impl FnMut(FrameContext),
392 ) -> Result<bool, GraphchainError> {
393 if self.graphchain.is_none() {
394 self.graphchain = Some(create_graphchain(device, data, &self.window)?);
395 }
396
397 let graphchain = self.graphchain.as_mut().expect("missing graphchain");
398
399 if let Some((width, height)) = self.swapchain_resize.take() {
400 if width == 0 || height == 0 {
401 self.swapchain_resize = Some((width, height));
402 self.events.clear();
403
404 return Ok(true);
405 }
406
407 let mut graphchain_info = graphchain.info;
408 graphchain_info.width = width;
409 graphchain_info.height = height;
410 graphchain.set_info(graphchain_info);
411 }
412
413 if let Some(swapchain_image) = graphchain.acquire_next_image()? {
414 let mut graph = Graph::default();
415 let swapchain_image = graph.bind_resource(swapchain_image);
416 let graphchain_info = graphchain.info;
417
418 let mut will_exit = false;
419
420 trace!("drawing");
421
422 f(FrameContext {
423 device,
424 events: &self.events,
425 height: graphchain_info.height,
426 graph: &mut graph,
427 swapchain_image,
428 width: graphchain_info.width,
429 will_exit: &mut will_exit,
430 window: &self.window,
431 });
432
433 self.events.clear();
434
435 if will_exit {
436 info!("exit requested");
437
438 return Ok(false);
439 }
440
441 self.window.pre_present_notify();
442 graphchain
443 .present_image(&mut self.swapchain_pool, graph, swapchain_image, 0)
444 .inspect_err(|err| {
445 warn!("unable to present graphchain image: {err}");
446 })?;
447 } else {
448 warn!("unable to acquire graphchain image");
449 }
450
451 self.window.request_redraw();
452
453 Ok(true)
454 }
455 }
456
457 let mut app = Application {
458 active_window: None,
459 data: self.data,
460 device: self.read_only.device,
461 draw_fn,
462 error: None,
463 primary_monitor: None,
464 };
465
466 let proxy = self.read_only.event_loop.create_proxy();
467 if let Err(e) = ctrlc::set_handler(move || {
468 trace!("received SIGINT/SIGTERM");
469
470 let _ = proxy.send_event(());
471 }) {
472 warn!("failed to set Ctrl-C handler: {e}");
473 }
474
475 self.read_only.event_loop.run_app(&mut app)?;
476
477 if let Some(ActiveWindow {
478 graphchain, window, ..
479 }) = app.active_window.take()
480 {
481 drop(graphchain);
482 drop(window);
483 }
484
485 info!("Window closed");
486
487 if let Some(err) = app.error {
488 Err(err)
489 } else {
490 Ok(())
491 }
492 }
493}
494
495impl HasDisplayHandle for Window {
496 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
497 self.event_loop.display_handle()
498 }
499}
500
501pub struct WindowBuilder {
503 attributes: WindowAttributes,
504 cmd_buf_count: usize,
505 device_info: DeviceInfo,
506 min_image_count: Option<u32>,
507 surface_format_fn: Option<Box<SurfaceFormatFn>>,
508 v_sync: Option<bool>,
509 window_mode_override: Option<Option<FullscreenMode>>,
510}
511
512impl WindowBuilder {
513 pub fn build(self) -> Result<Window, WindowError> {
515 let event_loop = EventLoop::new()?;
516 let device = Device::try_from_display(&event_loop, self.device_info)?;
517
518 Ok(Window {
519 data: WindowData {
520 attributes: self.attributes,
521 cmd_buf_count: self.cmd_buf_count,
522 min_image_count: self.min_image_count,
523 surface_format_fn: self.surface_format_fn,
524 v_sync: self.v_sync,
525 window_mode_override: self.window_mode_override,
526 },
527 read_only: ReadOnlyWindow { device, event_loop },
528 })
529 }
530
531 pub fn command_buffer_count(mut self, count: usize) -> Self {
540 self.cmd_buf_count = count;
541 self
542 }
543
544 pub fn debug(mut self, enabled: bool) -> Self {
553 self.device_info.debug = enabled;
554 self
555 }
556
557 pub fn desired_surface_format<F>(mut self, f: F) -> Self
561 where
562 F: 'static + Fn(&[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR,
563 {
564 self.surface_format_fn = Some(Box::new(f));
565 self
566 }
567
568 pub fn min_image_count(mut self, count: u32) -> Self {
574 self.min_image_count = Some(count);
575 self
576 }
577
578 pub fn fullscreen_mode(mut self, mode: FullscreenMode) -> Self {
586 self.window_mode_override = Some(Some(mode));
587 self
588 }
589
590 pub fn v_sync(mut self, enabled: bool) -> Self {
596 self.v_sync = Some(enabled);
597 self
598 }
599
600 pub fn window<WindowFn>(mut self, f: WindowFn) -> Self
602 where
603 WindowFn: FnOnce(WindowAttributes) -> WindowAttributes,
604 {
605 self.attributes = f(self.attributes);
606 self
607 }
608
609 pub fn window_mode(mut self) -> Self {
616 self.window_mode_override = Some(None);
617 self
618 }
619}
620
621impl fmt::Debug for WindowBuilder {
622 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
623 f.debug_struct("WindowBuilder")
624 .field("attributes", &self.attributes)
625 .field("cmd_buffer_count", &self.cmd_buf_count)
626 .field("device_info", &self.device_info)
627 .field("min_image_count", &self.min_image_count)
628 .field(
629 "surface_format_fn",
630 &self.surface_format_fn.as_ref().map(|_| ()),
631 )
632 .field("v_sync", &self.v_sync)
633 .field("window_mode_override", &self.window_mode_override)
634 .finish()
635 }
636}
637
638impl Default for WindowBuilder {
639 fn default() -> Self {
640 Self {
641 attributes: Default::default(),
642 cmd_buf_count: 5,
643 device_info: Default::default(),
644 min_image_count: None,
645 surface_format_fn: None,
646 v_sync: None,
647 window_mode_override: None,
648 }
649 }
650}
651
652struct WindowData {
653 attributes: WindowAttributes,
654 cmd_buf_count: usize,
655 min_image_count: Option<u32>,
656 surface_format_fn: Option<Box<SurfaceFormatFn>>,
657 v_sync: Option<bool>,
658 window_mode_override: Option<Option<FullscreenMode>>,
659}
660
661#[derive(Debug)]
663pub enum WindowError {
664 Driver(DriverError),
666 EventLoop(EventLoopError),
668 Graphchain(GraphchainError),
670}
671
672impl error::Error for WindowError {
673 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
674 Some(match self {
675 Self::Driver(err) => err,
676 Self::EventLoop(err) => err,
677 Self::Graphchain(err) => err,
678 })
679 }
680}
681
682impl fmt::Display for WindowError {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 match self {
685 Self::Driver(err) => err.fmt(f),
686 Self::EventLoop(err) => err.fmt(f),
687 Self::Graphchain(err) => err.fmt(f),
688 }
689 }
690}
691
692impl From<DriverError> for WindowError {
693 fn from(err: DriverError) -> Self {
694 Self::Driver(err)
695 }
696}
697
698impl From<EventLoopError> for WindowError {
699 fn from(err: EventLoopError) -> Self {
700 Self::EventLoop(err)
701 }
702}