1#![deny(missing_docs)]
4#![deny(rustdoc::broken_intra_doc_links)]
5
6mod frame;
7pub mod swapchain;
8
9pub use {self::frame::FrameContext, winit};
10
11use {
12 self::swapchain::{Swapchain, SwapchainError, SwapchainInfo},
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
39#[derive(Clone, Copy, Debug)]
41pub enum FullscreenMode {
42 Borderless,
44
45 Exclusive,
47}
48
49#[read_only::embed]
51pub struct Window {
52 data: WindowData,
53
54 #[readonly]
58 pub device: Device,
59
60 #[readonly]
61 pub(self) event_loop: EventLoop<()>,
62}
63
64impl Deref for ReadOnlyWindow {
65 type Target = EventLoop<()>;
66
67 fn deref(&self) -> &Self::Target {
68 &self.event_loop
69 }
70}
71
72impl Window {
73 pub fn new() -> Result<Self, WindowError> {
75 Self::builder().build()
76 }
77
78 pub fn builder() -> WindowBuilder {
80 Default::default()
81 }
82
83 pub fn run<F>(self, draw_fn: F) -> Result<(), WindowError>
85 where
86 F: FnMut(FrameContext),
87 {
88 struct Application<F> {
89 active_window: Option<ActiveWindow>,
90 data: WindowData,
91 device: Device,
92 draw_fn: F,
93 error: Option<WindowError>,
94 primary_monitor: Option<MonitorHandle>,
95 }
96
97 impl<F> Application<F> {
98 fn create_swapchain(
99 &mut self,
100 window: &winit::window::Window,
101 ) -> Result<Swapchain, DriverError> {
102 let surface = Surface::create(&self.device, window, window)?;
103 let surface_formats = Surface::formats(&surface)?;
104 let surface_format = self
105 .data
106 .surface_format_fn
107 .as_ref()
108 .map(|f| f(&surface_formats))
109 .unwrap_or_else(|| Surface::linear_or_default(&surface_formats));
110 let window_size = window.inner_size();
111
112 let mut swapchain_info =
113 SwapchainInfo::new(window_size.width, window_size.height, surface_format)
114 .into_builder()
115 .command_buffer_count(self.data.cmd_buf_count);
116
117 if let Some(min_image_count) = self.data.min_image_count {
118 swapchain_info = swapchain_info.min_image_count(min_image_count);
119 }
120
121 let v_sync = self.data.v_sync.unwrap_or_default();
122 let present_modes = Surface::present_modes(&surface)?;
123 if !present_modes.is_empty() {
124 let best_modes = if v_sync {
125 [vk::PresentModeKHR::FIFO_RELAXED, vk::PresentModeKHR::FIFO].as_slice()
126 } else {
127 [vk::PresentModeKHR::MAILBOX, vk::PresentModeKHR::IMMEDIATE].as_slice()
128 };
129
130 swapchain_info = swapchain_info.present_mode(
131 best_modes
132 .iter()
133 .copied()
134 .find(|best| present_modes.contains(best))
135 .or_else(|| {
136 warn!("requested present modes unsupported: {best_modes:?}");
137
138 present_modes.first().copied()
139 })
140 .ok_or_else(|| {
141 error!("display does not support presentation");
142
143 DriverError::Unsupported
144 })?,
145 );
146 }
147
148 let swapchain = Swapchain::new(surface, swapchain_info)?;
149
150 trace!("created swapchain");
151
152 Ok(swapchain)
153 }
154
155 fn window_mode_attributes(
156 &self,
157 attributes: WindowAttributes,
158 window_mode_override: Option<Option<FullscreenMode>>,
159 ) -> WindowAttributes {
160 match window_mode_override {
161 Some(Some(mode)) => {
162 let inner_size;
163 let attributes = attributes
164 .with_decorations(false)
165 .with_maximized(true)
166 .with_fullscreen(Some(match mode {
167 FullscreenMode::Borderless => {
168 info!("Using borderless fullscreen");
169
170 inner_size = None;
171
172 winit::window::Fullscreen::Borderless(None)
173 }
174 FullscreenMode::Exclusive => {
175 if let Some(video_mode) =
176 self.primary_monitor.as_ref().and_then(|monitor| {
177 let monitor_size = monitor.size();
178 monitor.video_modes().find(|mode| {
179 let mode_size = mode.size();
180
181 mode_size.height <= monitor_size.height
185 && mode_size.width <= monitor_size.width
186 })
187 })
188 {
189 info!(
190 "Using {}x{} {}bpp @ {}hz exclusive fullscreen",
191 video_mode.size().width,
192 video_mode.size().height,
193 video_mode.bit_depth(),
194 video_mode.refresh_rate_millihertz() / 1_000
195 );
196
197 inner_size = Some(video_mode.size());
198
199 winit::window::Fullscreen::Exclusive(video_mode)
200 } else {
201 warn!("unsupported exclusive fullscreen mode");
202
203 inner_size = None;
204
205 winit::window::Fullscreen::Borderless(None)
206 }
207 }
208 }));
209
210 if let Some(inner_size) = inner_size
211 .or_else(|| self.primary_monitor.as_ref().map(|monitor| monitor.size()))
212 {
213 attributes.with_inner_size(inner_size)
214 } else {
215 attributes
216 }
217 }
218 Some(None) => attributes.with_fullscreen(None),
219 _ => attributes,
220 }
221 }
222 }
223
224 impl<F> ApplicationHandler for Application<F>
225 where
226 F: FnMut(FrameContext),
227 {
228 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
229 if event_loop.exiting() {
230 return;
231 }
232
233 if let Some(ActiveWindow { window, .. }) = self.active_window.as_ref() {
234 window.request_redraw();
235 }
236 }
237
238 fn device_event(
239 &mut self,
240 _event_loop: &ActiveEventLoop,
241 device_id: DeviceId,
242 event: DeviceEvent,
243 ) {
244 if let Some(ActiveWindow { events, .. }) = self.active_window.as_mut() {
245 events.push(Event::DeviceEvent { device_id, event });
246 }
247 }
248
249 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
250 info!("Resumed");
251
252 self.data.attributes = self.window_mode_attributes(
253 self.data.attributes.clone(),
254 self.data.window_mode_override,
255 );
256
257 let window = match event_loop.create_window(self.data.attributes.clone()) {
258 Err(err) => {
259 warn!("unable to create window: {err}");
260
261 self.error = Some(EventLoopError::Os(err).into());
262 event_loop.exit();
263
264 return;
265 }
266 Ok(res) => res,
267 };
268 let swapchain = match self.create_swapchain(&window) {
269 Err(err) => {
270 warn!("unable to create swapchain: {err}");
271
272 self.error = Some(err.into());
273 event_loop.exit();
274
275 return;
276 }
277 Ok(res) => res,
278 };
279 let swapchain_pool = HashPool::new(&self.device);
280
281 self.active_window = Some(ActiveWindow {
282 swapchain,
283 swapchain_pool,
284 swapchain_resize: None,
285 events: vec![],
286 window,
287 });
288 }
289
290 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: ()) {
291 if let Some(ActiveWindow { events, .. }) = self.active_window.as_mut() {
292 events.push(Event::UserEvent(event));
293 }
294 }
295
296 fn window_event(
297 &mut self,
298 event_loop: &ActiveEventLoop,
299 window_id: WindowId,
300 event: WindowEvent,
301 ) {
302 if let Some(active_window) = self.active_window.as_mut() {
303 match &event {
304 WindowEvent::CloseRequested => {
305 if event_loop.exiting() {
306 return;
307 }
308
309 info!("close requested");
310
311 event_loop.exit();
312 }
313 WindowEvent::RedrawRequested => {
314 if event_loop.exiting() {
315 return;
316 }
317
318 match active_window.draw(&self.device, &mut self.draw_fn) {
319 Ok(true) => {}
320 res => {
321 if let Err(err) = res {
322 self.error = Some(WindowError::Swapchain(err));
323 }
324
325 event_loop.exit();
326 }
327 }
328
329 profiling::finish_frame!();
330 }
331 WindowEvent::Resized(size) => {
332 active_window.swapchain_resize = Some((size.width, size.height));
333 }
334 _ => (),
335 }
336
337 active_window
338 .events
339 .push(Event::WindowEvent { window_id, event });
340 }
341 }
342 }
343
344 struct ActiveWindow {
345 swapchain: Swapchain,
346 swapchain_pool: HashPool,
347 swapchain_resize: Option<(u32, u32)>,
348 events: Vec<Event<()>>,
349 window: winit::window::Window,
350 }
351
352 impl ActiveWindow {
353 fn draw(
354 &mut self,
355 device: &Device,
356 mut f: impl FnMut(FrameContext),
357 ) -> Result<bool, SwapchainError> {
358 if let Some((width, height)) = self.swapchain_resize.take() {
359 let mut swapchain_info = self.swapchain.info;
360 swapchain_info.width = width;
361 swapchain_info.height = height;
362 self.swapchain.set_info(swapchain_info);
363 }
364
365 if let Some(swapchain_image) = self.swapchain.acquire_next_image()? {
366 let mut graph = Graph::default();
367 let swapchain_image = graph.bind_resource(swapchain_image);
368 let swapchain_info = self.swapchain.info;
369
370 let mut will_exit = false;
371
372 trace!("drawing");
373
374 f(FrameContext {
375 device,
376 events: &self.events,
377 height: swapchain_info.height,
378 graph: &mut graph,
379 swapchain_image,
380 width: swapchain_info.width,
381 will_exit: &mut will_exit,
382 window: &self.window,
383 });
384
385 self.events.clear();
386
387 if will_exit {
388 info!("exit requested");
389
390 return Ok(false);
391 }
392
393 self.window.pre_present_notify();
394 self.swapchain
395 .present_image(&mut self.swapchain_pool, graph, swapchain_image, 0)
396 .inspect_err(|err| {
397 warn!("unable to present swapchain image: {err}");
398 })?;
399 } else {
400 warn!("unable to acquire swapchain image");
401 }
402
403 self.window.request_redraw();
404
405 Ok(true)
406 }
407 }
408
409 let mut app = Application {
410 active_window: None,
411 data: self.data,
412 device: self.read_only.device,
413 draw_fn,
414 error: None,
415 primary_monitor: None,
416 };
417
418 self.read_only.event_loop.run_app(&mut app)?;
419
420 if let Some(ActiveWindow {
421 swapchain, window, ..
422 }) = app.active_window.take()
423 {
424 drop(swapchain);
425 drop(window);
426 }
427
428 info!("Window closed");
429
430 if let Some(err) = app.error {
431 Err(err)
432 } else {
433 Ok(())
434 }
435 }
436}
437
438impl HasDisplayHandle for Window {
439 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
440 self.event_loop.display_handle()
441 }
442}
443
444pub struct WindowBuilder {
446 attributes: WindowAttributes,
447 cmd_buf_count: usize,
448 device_info: DeviceInfo,
449 min_image_count: Option<u32>,
450 surface_format_fn: Option<Box<SurfaceFormatFn>>,
451 v_sync: Option<bool>,
452 window_mode_override: Option<Option<FullscreenMode>>,
453}
454
455impl WindowBuilder {
456 pub fn build(self) -> Result<Window, WindowError> {
458 let event_loop = EventLoop::new()?;
459 let device = Device::try_from_display(&event_loop, self.device_info)?;
460
461 Ok(Window {
462 data: WindowData {
463 attributes: self.attributes,
464 cmd_buf_count: self.cmd_buf_count,
465 min_image_count: self.min_image_count,
466 surface_format_fn: self.surface_format_fn,
467 v_sync: self.v_sync,
468 window_mode_override: self.window_mode_override,
469 },
470 read_only: ReadOnlyWindow { device, event_loop },
471 })
472 }
473
474 pub fn command_buffer_count(mut self, count: usize) -> Self {
483 self.cmd_buf_count = count;
484 self
485 }
486
487 pub fn debug(mut self, enabled: bool) -> Self {
497 self.device_info.debug = enabled;
498 self
499 }
500
501 pub fn desired_surface_format<F>(mut self, f: F) -> Self
505 where
506 F: 'static + Fn(&[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR,
507 {
508 self.surface_format_fn = Some(Box::new(f));
509 self
510 }
511
512 pub fn min_image_count(mut self, count: u32) -> Self {
518 self.min_image_count = Some(count);
519 self
520 }
521
522 pub fn fullscreen_mode(mut self, mode: FullscreenMode) -> Self {
530 self.window_mode_override = Some(Some(mode));
531 self
532 }
533
534 pub fn v_sync(mut self, enabled: bool) -> Self {
541 self.v_sync = Some(enabled);
542 self
543 }
544
545 pub fn window<WindowFn>(mut self, f: WindowFn) -> Self
547 where
548 WindowFn: FnOnce(WindowAttributes) -> WindowAttributes,
549 {
550 self.attributes = f(self.attributes);
551 self
552 }
553
554 pub fn window_mode(mut self) -> Self {
561 self.window_mode_override = Some(None);
562 self
563 }
564}
565
566impl fmt::Debug for WindowBuilder {
567 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
568 f.debug_struct("WindowBuilder")
569 .field("attributes", &self.attributes)
570 .field("cmd_buffer_count", &self.cmd_buf_count)
571 .field("device_info", &self.device_info)
572 .field("min_image_count", &self.min_image_count)
573 .field(
574 "surface_format_fn",
575 &self.surface_format_fn.as_ref().map(|_| ()),
576 )
577 .field("v_sync", &self.v_sync)
578 .field("window_mode_override", &self.window_mode_override)
579 .finish()
580 }
581}
582
583impl Default for WindowBuilder {
584 fn default() -> Self {
585 Self {
586 attributes: Default::default(),
587 cmd_buf_count: 5,
588 device_info: Default::default(),
589 min_image_count: None,
590 surface_format_fn: None,
591 v_sync: None,
592 window_mode_override: None,
593 }
594 }
595}
596
597struct WindowData {
598 attributes: WindowAttributes,
599 cmd_buf_count: usize,
600 min_image_count: Option<u32>,
601 surface_format_fn: Option<Box<SurfaceFormatFn>>,
602 v_sync: Option<bool>,
603 window_mode_override: Option<Option<FullscreenMode>>,
604}
605
606#[derive(Debug)]
608pub enum WindowError {
609 Driver(DriverError),
611 EventLoop(EventLoopError),
613 Swapchain(SwapchainError),
615}
616
617impl error::Error for WindowError {
618 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
619 Some(match self {
620 Self::Driver(err) => err,
621 Self::EventLoop(err) => err,
622 Self::Swapchain(err) => err,
623 })
624 }
625}
626
627impl fmt::Display for WindowError {
628 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
629 match self {
630 Self::Driver(err) => err.fmt(f),
631 Self::EventLoop(err) => err.fmt(f),
632 Self::Swapchain(err) => err.fmt(f),
633 }
634 }
635}
636
637impl From<DriverError> for WindowError {
638 fn from(err: DriverError) -> Self {
639 Self::Driver(err)
640 }
641}
642
643impl From<EventLoopError> for WindowError {
644 fn from(err: EventLoopError) -> Self {
645 Self::EventLoop(err)
646 }
647}