1use astrelis_core::{
46 geometry::{LogicalSize, PhysicalPosition, PhysicalSize, ScaleFactor},
47 profiling::profile_function,
48};
49use astrelis_winit::{
50 WindowId,
51 window::{Window, WindowBackend},
52};
53use std::cell::{Cell, RefCell};
54use std::sync::Arc;
55
56use crate::{
57 context::{GraphicsContext, GraphicsError},
58 depth::{DEFAULT_DEPTH_FORMAT, DepthTexture},
59 frame::{AtomicFrameStats, Frame, Surface},
60 gpu_profiling::GpuFrameProfiler,
61};
62
63#[derive(Debug, Clone, Copy)]
68pub struct Viewport {
69 pub position: PhysicalPosition<f32>,
71 pub size: PhysicalSize<f32>,
73 pub scale_factor: ScaleFactor,
75}
76
77impl Default for Viewport {
78 fn default() -> Self {
79 Self {
80 position: PhysicalPosition::new(0.0, 0.0),
81 size: PhysicalSize::new(800.0, 600.0),
82 scale_factor: ScaleFactor(1.0),
84 }
85 }
86}
87
88impl Viewport {
89 pub fn new(width: f32, height: f32, scale_factor: ScaleFactor) -> Self {
91 Self {
92 position: PhysicalPosition::new(0.0, 0.0),
93 size: PhysicalSize::new(width, height),
94 scale_factor,
95 }
96 }
97
98 pub fn from_physical_size(size: PhysicalSize<u32>, scale_factor: ScaleFactor) -> Self {
100 Self {
101 position: PhysicalPosition::new(0.0, 0.0),
102 size: PhysicalSize::new(size.width as f32, size.height as f32),
103 scale_factor,
104 }
105 }
106
107 pub fn is_valid(&self) -> bool {
109 self.size.width > 0.0 && self.size.height > 0.0 && self.scale_factor.0 > 0.0
110 }
111
112 pub fn to_logical(&self) -> LogicalSize<f32> {
114 self.size.to_logical(self.scale_factor)
115 }
116
117 pub fn width(&self) -> f32 {
119 self.size.width
120 }
121
122 pub fn height(&self) -> f32 {
124 self.size.height
125 }
126
127 pub fn x(&self) -> f32 {
129 self.position.x
130 }
131
132 pub fn y(&self) -> f32 {
134 self.position.y
135 }
136}
137
138#[derive(Default)]
140pub struct WindowContextDescriptor {
141 pub format: Option<wgpu::TextureFormat>,
143 pub present_mode: Option<wgpu::PresentMode>,
145 pub alpha_mode: Option<wgpu::CompositeAlphaMode>,
147 pub with_depth: bool,
152 pub depth_format: Option<wgpu::TextureFormat>,
156}
157
158pub(crate) struct PendingReconfigure {
159 pub(crate) resize: Option<PhysicalSize<u32>>,
160}
161
162impl PendingReconfigure {
163 const fn new() -> Self {
164 Self { resize: None }
165 }
166}
167
168pub struct WindowContext {
174 pub(crate) window: Window,
175 pub(crate) context: Arc<GraphicsContext>,
176 pub(crate) surface: wgpu::Surface<'static>,
177 pub(crate) config: wgpu::SurfaceConfiguration,
178 pub(crate) reconfigure: PendingReconfigure,
179 pub(crate) depth_texture: Option<DepthTexture>,
181}
182
183impl WindowContext {
184 pub fn new(
185 window: Window,
186 context: Arc<GraphicsContext>,
187 descriptor: WindowContextDescriptor,
188 ) -> Result<Self, GraphicsError> {
189 profile_function!();
190 let scale_factor = window.scale_factor();
191 let logical_size = window.logical_size();
192 let physical_size = logical_size.to_physical(scale_factor);
193
194 let surface = context
195 .instance()
196 .create_surface(window.window.clone())
197 .map_err(|e| GraphicsError::SurfaceCreationFailed(e.to_string()))?;
198
199 let mut config = surface
200 .get_default_config(context.adapter(), physical_size.width, physical_size.height)
201 .ok_or_else(|| {
202 GraphicsError::SurfaceConfigurationFailed(
203 "No suitable surface configuration found".to_string(),
204 )
205 })?;
206
207 if let Some(format) = descriptor.format {
208 config.format = format;
209 }
210 if let Some(present_mode) = descriptor.present_mode {
211 config.present_mode = present_mode;
212 }
213 if let Some(alpha_mode) = descriptor.alpha_mode {
214 config.alpha_mode = alpha_mode;
215 }
216
217 surface.configure(context.device(), &config);
218
219 let depth_texture = if descriptor.with_depth {
221 let depth_format = descriptor.depth_format.unwrap_or(DEFAULT_DEPTH_FORMAT);
222 Some(DepthTexture::with_label(
223 context.device(),
224 physical_size.width,
225 physical_size.height,
226 depth_format,
227 "Window Depth Texture",
228 ))
229 } else {
230 None
231 };
232
233 Ok(Self {
234 window,
235 surface,
236 config,
237 reconfigure: PendingReconfigure::new(),
238 context,
239 depth_texture,
240 })
241 }
242
243 pub fn resized(&mut self, new_size: LogicalSize<u32>) {
245 let scale_factor = self.window.scale_factor();
246 let physical_size = new_size.to_physical(scale_factor);
247 self.reconfigure.resize = Some(physical_size);
248 }
249
250 pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
252 self.reconfigure.resize = Some(new_size);
253
254 if let Some(ref mut depth) = self.depth_texture
256 && depth.needs_resize(new_size.width, new_size.height)
257 {
258 depth.resize(self.context.device(), new_size.width, new_size.height);
259 }
260 }
261
262 pub fn window(&self) -> &Window {
263 &self.window
264 }
265
266 pub fn graphics_context(&self) -> &GraphicsContext {
267 &self.context
268 }
269
270 pub fn surface(&self) -> &wgpu::Surface<'static> {
271 &self.surface
272 }
273
274 pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
275 &self.config
276 }
277
278 pub fn surface_format(&self) -> wgpu::TextureFormat {
284 self.config.format
285 }
286
287 pub fn logical_size(&self) -> LogicalSize<u32> {
289 self.window.logical_size()
290 }
291
292 pub fn physical_size(&self) -> PhysicalSize<u32> {
294 self.window.physical_size()
295 }
296
297 pub fn logical_size_f32(&self) -> LogicalSize<f32> {
299 let size = self.logical_size();
300 LogicalSize::new(size.width as f32, size.height as f32)
301 }
302
303 pub fn physical_size_f32(&self) -> PhysicalSize<f32> {
305 let size = self.physical_size();
306 PhysicalSize::new(size.width as f32, size.height as f32)
307 }
308
309 pub fn reconfigure_surface(&mut self, config: wgpu::SurfaceConfiguration) {
311 self.config = config;
312 self.surface.configure(self.context.device(), &self.config);
313 }
314
315 pub fn has_depth(&self) -> bool {
317 self.depth_texture.is_some()
318 }
319
320 pub fn depth_view(&self) -> Option<std::sync::Arc<wgpu::TextureView>> {
324 self.depth_texture.as_ref().map(|d| d.view())
325 }
326
327 pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
332 self.depth_texture.as_ref().map(|d| d.format())
333 }
334
335 pub fn ensure_depth(&mut self, format: wgpu::TextureFormat) {
340 if self.depth_texture.is_none() {
341 self.depth_texture = Some(DepthTexture::with_label(
342 self.context.device(),
343 self.config.width,
344 self.config.height,
345 format,
346 "Window Depth Texture",
347 ));
348 }
349 }
350}
351
352impl WindowContext {
353 fn try_acquire_surface_texture(&mut self) -> Result<wgpu::SurfaceTexture, GraphicsError> {
358 match self.surface.get_current_texture() {
360 Ok(frame) => return Ok(frame),
361 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
362 tracing::debug!("Surface lost/outdated, reconfiguring...");
364 self.surface.configure(self.context.device(), &self.config);
365 }
366 Err(wgpu::SurfaceError::OutOfMemory) => {
367 return Err(GraphicsError::SurfaceOutOfMemory);
368 }
369 Err(wgpu::SurfaceError::Timeout) => {
370 return Err(GraphicsError::SurfaceTimeout);
371 }
372 Err(e) => {
373 return Err(GraphicsError::SurfaceTextureAcquisitionFailed(
374 e.to_string(),
375 ));
376 }
377 }
378
379 match self.surface.get_current_texture() {
381 Ok(frame) => Ok(frame),
382 Err(wgpu::SurfaceError::Lost) => Err(GraphicsError::SurfaceLost),
383 Err(wgpu::SurfaceError::Outdated) => Err(GraphicsError::SurfaceOutdated),
384 Err(wgpu::SurfaceError::OutOfMemory) => Err(GraphicsError::SurfaceOutOfMemory),
385 Err(wgpu::SurfaceError::Timeout) => Err(GraphicsError::SurfaceTimeout),
386 Err(e) => Err(GraphicsError::SurfaceTextureAcquisitionFailed(
387 e.to_string(),
388 )),
389 }
390 }
391}
392
393#[derive(Default)]
415pub struct RenderWindowBuilder {
416 present_mode: Option<wgpu::PresentMode>,
417 color_format: Option<wgpu::TextureFormat>,
418 alpha_mode: Option<wgpu::CompositeAlphaMode>,
419 depth_format: Option<wgpu::TextureFormat>,
420 enable_profiling: bool,
421}
422
423impl RenderWindowBuilder {
424 pub fn new() -> Self {
426 Self::default()
427 }
428
429 pub fn present_mode(mut self, mode: wgpu::PresentMode) -> Self {
431 self.present_mode = Some(mode);
432 self
433 }
434
435 pub fn color_format(mut self, format: wgpu::TextureFormat) -> Self {
437 self.color_format = Some(format);
438 self
439 }
440
441 pub fn alpha_mode(mut self, mode: wgpu::CompositeAlphaMode) -> Self {
443 self.alpha_mode = Some(mode);
444 self
445 }
446
447 pub fn with_depth(mut self, format: wgpu::TextureFormat) -> Self {
449 self.depth_format = Some(format);
450 self
451 }
452
453 pub fn with_depth_default(mut self) -> Self {
455 self.depth_format = Some(DEFAULT_DEPTH_FORMAT);
456 self
457 }
458
459 pub fn with_profiling(mut self, enabled: bool) -> Self {
461 self.enable_profiling = enabled;
462 self
463 }
464
465 pub fn build(
467 self,
468 window: Window,
469 graphics: Arc<GraphicsContext>,
470 ) -> Result<RenderWindow, GraphicsError> {
471 let descriptor = WindowContextDescriptor {
472 format: self.color_format,
473 present_mode: self.present_mode,
474 alpha_mode: self.alpha_mode,
475 with_depth: self.depth_format.is_some(),
476 depth_format: self.depth_format,
477 };
478
479 let context = WindowContext::new(window, graphics.clone(), descriptor)?;
480
481 let gpu_profiler = if self.enable_profiling {
482 Some(Arc::new(GpuFrameProfiler::new(&graphics)?))
483 } else {
484 None
485 };
486
487 Ok(RenderWindow {
488 context,
489 gpu_profiler,
490 })
491 }
492}
493
494pub struct RenderWindow {
532 pub(crate) context: WindowContext,
533 pub(crate) gpu_profiler: Option<Arc<GpuFrameProfiler>>,
534}
535
536impl RenderWindow {
537 pub fn builder() -> RenderWindowBuilder {
539 RenderWindowBuilder::new()
540 }
541
542 pub fn new(window: Window, context: Arc<GraphicsContext>) -> Result<Self, GraphicsError> {
544 Self::new_with_descriptor(window, context, WindowContextDescriptor::default())
545 }
546
547 pub fn new_with_depth(
551 window: Window,
552 context: Arc<GraphicsContext>,
553 ) -> Result<Self, GraphicsError> {
554 Self::builder().with_depth_default().build(window, context)
555 }
556
557 pub fn new_with_descriptor(
559 window: Window,
560 context: Arc<GraphicsContext>,
561 descriptor: WindowContextDescriptor,
562 ) -> Result<Self, GraphicsError> {
563 profile_function!();
564 let context = WindowContext::new(window, context, descriptor)?;
565 Ok(Self {
566 context,
567 gpu_profiler: None,
568 })
569 }
570
571 pub fn begin_frame(&mut self) -> Option<Frame<'_>> {
589 self.try_begin_frame().ok()
590 }
591
592 pub fn try_begin_frame(&mut self) -> Result<Frame<'_>, GraphicsError> {
597 profile_function!();
598
599 let mut configure_needed = false;
601 if let Some(new_size) = self.context.reconfigure.resize.take() {
602 self.context.config.width = new_size.width;
603 self.context.config.height = new_size.height;
604 configure_needed = true;
605
606 if let Some(ref mut depth) = self.context.depth_texture
608 && depth.needs_resize(new_size.width, new_size.height)
609 {
610 depth.resize(
611 self.context.context.device(),
612 new_size.width,
613 new_size.height,
614 );
615 }
616 }
617
618 if configure_needed {
619 self.context
620 .surface
621 .configure(self.context.context.device(), &self.context.config);
622 }
623
624 let surface_texture = self.context.try_acquire_surface_texture()?;
626 let view = surface_texture
627 .texture
628 .create_view(&wgpu::TextureViewDescriptor::default());
629
630 Ok(Frame {
631 window: self,
632 surface: Some(Surface {
633 texture: surface_texture,
634 view,
635 }),
636 command_buffers: RefCell::new(Vec::new()),
637 stats: Arc::new(AtomicFrameStats::new()),
638 submitted: Cell::new(false),
639 surface_format: self.context.config.format,
640 gpu_profiler: self.gpu_profiler.clone(),
641 winit_window: self.context.window.window.clone(),
642 })
643 }
644
645 pub fn id(&self) -> WindowId {
647 self.context.window.id()
648 }
649
650 pub fn window(&self) -> &Window {
652 &self.context.window
653 }
654
655 pub fn context(&self) -> &WindowContext {
657 &self.context
658 }
659
660 pub fn context_mut(&mut self) -> &mut WindowContext {
662 &mut self.context
663 }
664
665 pub fn graphics(&self) -> &GraphicsContext {
667 &self.context.context
668 }
669
670 pub fn graphics_arc(&self) -> &Arc<GraphicsContext> {
672 &self.context.context
673 }
674
675 pub fn surface_format(&self) -> wgpu::TextureFormat {
677 self.context.surface_format()
678 }
679
680 pub fn resized(&mut self, new_size: LogicalSize<u32>) {
682 self.context.resized(new_size);
683 }
684
685 pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
687 self.context.resized_physical(new_size);
688 }
689
690 pub fn physical_size(&self) -> PhysicalSize<u32> {
692 self.context.physical_size()
693 }
694
695 pub fn size(&self) -> (u32, u32) {
697 (self.context.config.width, self.context.config.height)
698 }
699
700 pub fn scale_factor(&self) -> ScaleFactor {
702 self.window().scale_factor()
703 }
704
705 pub fn viewport(&self) -> Viewport {
707 let physical_size = self.physical_size();
708 let scale_factor = self.scale_factor();
709
710 Viewport {
711 position: PhysicalPosition::new(0.0, 0.0),
712 size: PhysicalSize::new(physical_size.width as f32, physical_size.height as f32),
713 scale_factor,
714 }
715 }
716
717 pub fn set_gpu_profiler(&mut self, profiler: Arc<GpuFrameProfiler>) {
719 self.gpu_profiler = Some(profiler);
720 }
721
722 pub fn remove_gpu_profiler(&mut self) -> Option<Arc<GpuFrameProfiler>> {
724 self.gpu_profiler.take()
725 }
726
727 pub fn gpu_profiler(&self) -> Option<&Arc<GpuFrameProfiler>> {
729 self.gpu_profiler.as_ref()
730 }
731
732 pub fn has_depth(&self) -> bool {
734 self.context.has_depth()
735 }
736
737 pub fn depth_view(&self) -> Option<Arc<wgpu::TextureView>> {
739 self.context.depth_view()
740 }
741
742 pub fn depth_view_ref(&self) -> Option<&wgpu::TextureView> {
744 self.context.depth_texture.as_ref().map(|d| d.view_ref())
745 }
746
747 pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
752 self.context.depth_format()
753 }
754
755 pub fn ensure_depth(&mut self, format: wgpu::TextureFormat) {
757 self.context.ensure_depth(format);
758 }
759}
760
761impl std::ops::Deref for RenderWindow {
762 type Target = WindowContext;
763
764 fn deref(&self) -> &Self::Target {
765 &self.context
766 }
767}
768
769impl std::ops::DerefMut for RenderWindow {
770 fn deref_mut(&mut self) -> &mut Self::Target {
771 &mut self.context
772 }
773}
774
775impl WindowBackend for RenderWindow {
780 type FrameContext = Frame<'static>;
781 type Error = GraphicsError;
782
783 fn try_begin_drawing(&mut self) -> Result<Self::FrameContext, Self::Error> {
784 unimplemented!(
788 "Use RenderWindow::begin_frame() instead of WindowBackend::try_begin_drawing()"
789 )
790 }
791}