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
168struct WindowSurface {
170 surface: wgpu::Surface<'static>,
171 config: wgpu::SurfaceConfiguration,
172}
173
174pub struct WindowContext {
180 pub(crate) window: Window,
181 pub(crate) context: Arc<GraphicsContext>,
182 pub(crate) surface: wgpu::Surface<'static>,
183 pub(crate) config: wgpu::SurfaceConfiguration,
184 pub(crate) reconfigure: PendingReconfigure,
185 pub(crate) depth_texture: Option<DepthTexture>,
187}
188
189impl WindowContext {
190 pub fn new(
191 window: Window,
192 context: Arc<GraphicsContext>,
193 descriptor: WindowContextDescriptor,
194 ) -> Result<Self, GraphicsError> {
195 profile_function!();
196 let scale_factor = window.scale_factor();
197 let logical_size = window.logical_size();
198 let physical_size = logical_size.to_physical(scale_factor);
199
200 let surface = context
201 .instance()
202 .create_surface(window.window.clone())
203 .map_err(|e| GraphicsError::SurfaceCreationFailed(e.to_string()))?;
204
205 let mut config = surface
206 .get_default_config(context.adapter(), physical_size.width, physical_size.height)
207 .ok_or_else(|| {
208 GraphicsError::SurfaceConfigurationFailed(
209 "No suitable surface configuration found".to_string(),
210 )
211 })?;
212
213 if let Some(format) = descriptor.format {
214 config.format = format;
215 }
216 if let Some(present_mode) = descriptor.present_mode {
217 config.present_mode = present_mode;
218 }
219 if let Some(alpha_mode) = descriptor.alpha_mode {
220 config.alpha_mode = alpha_mode;
221 }
222
223 surface.configure(context.device(), &config);
224
225 let depth_texture = if descriptor.with_depth {
227 let depth_format = descriptor.depth_format.unwrap_or(DEFAULT_DEPTH_FORMAT);
228 Some(DepthTexture::with_label(
229 context.device(),
230 physical_size.width,
231 physical_size.height,
232 depth_format,
233 "Window Depth Texture",
234 ))
235 } else {
236 None
237 };
238
239 Ok(Self {
240 window,
241 surface,
242 config,
243 reconfigure: PendingReconfigure::new(),
244 context,
245 depth_texture,
246 })
247 }
248
249 pub fn resized(&mut self, new_size: LogicalSize<u32>) {
251 let scale_factor = self.window.scale_factor();
252 let physical_size = new_size.to_physical(scale_factor);
253 self.reconfigure.resize = Some(physical_size);
254 }
255
256 pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
258 self.reconfigure.resize = Some(new_size);
259
260 if let Some(ref mut depth) = self.depth_texture
262 && depth.needs_resize(new_size.width, new_size.height)
263 {
264 depth.resize(self.context.device(), new_size.width, new_size.height);
265 }
266 }
267
268 pub fn window(&self) -> &Window {
269 &self.window
270 }
271
272 pub fn graphics_context(&self) -> &GraphicsContext {
273 &self.context
274 }
275
276 pub fn surface(&self) -> &wgpu::Surface<'static> {
277 &self.surface
278 }
279
280 pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
281 &self.config
282 }
283
284 pub fn surface_format(&self) -> wgpu::TextureFormat {
290 self.config.format
291 }
292
293 pub fn logical_size(&self) -> LogicalSize<u32> {
295 self.window.logical_size()
296 }
297
298 pub fn physical_size(&self) -> PhysicalSize<u32> {
300 self.window.physical_size()
301 }
302
303 pub fn logical_size_f32(&self) -> LogicalSize<f32> {
305 let size = self.logical_size();
306 LogicalSize::new(size.width as f32, size.height as f32)
307 }
308
309 pub fn physical_size_f32(&self) -> PhysicalSize<f32> {
311 let size = self.physical_size();
312 PhysicalSize::new(size.width as f32, size.height as f32)
313 }
314
315 pub fn reconfigure_surface(&mut self, config: wgpu::SurfaceConfiguration) {
317 self.config = config;
318 self.surface.configure(self.context.device(), &self.config);
319 }
320
321 pub fn has_depth(&self) -> bool {
323 self.depth_texture.is_some()
324 }
325
326 pub fn depth_view(&self) -> Option<std::sync::Arc<wgpu::TextureView>> {
330 self.depth_texture.as_ref().map(|d| d.view())
331 }
332
333 pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
338 self.depth_texture.as_ref().map(|d| d.format())
339 }
340
341 pub fn ensure_depth(&mut self, format: wgpu::TextureFormat) {
346 if self.depth_texture.is_none() {
347 self.depth_texture = Some(DepthTexture::with_label(
348 self.context.device(),
349 self.config.width,
350 self.config.height,
351 format,
352 "Window Depth Texture",
353 ));
354 }
355 }
356}
357
358impl WindowContext {
359 fn try_acquire_surface_texture(&mut self) -> Result<wgpu::SurfaceTexture, GraphicsError> {
364 match self.surface.get_current_texture() {
366 Ok(frame) => return Ok(frame),
367 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
368 tracing::debug!("Surface lost/outdated, reconfiguring...");
370 self.surface.configure(self.context.device(), &self.config);
371 }
372 Err(wgpu::SurfaceError::OutOfMemory) => {
373 return Err(GraphicsError::SurfaceOutOfMemory);
374 }
375 Err(wgpu::SurfaceError::Timeout) => {
376 return Err(GraphicsError::SurfaceTimeout);
377 }
378 Err(e) => {
379 return Err(GraphicsError::SurfaceTextureAcquisitionFailed(
380 e.to_string(),
381 ));
382 }
383 }
384
385 match self.surface.get_current_texture() {
387 Ok(frame) => Ok(frame),
388 Err(wgpu::SurfaceError::Lost) => Err(GraphicsError::SurfaceLost),
389 Err(wgpu::SurfaceError::Outdated) => Err(GraphicsError::SurfaceOutdated),
390 Err(wgpu::SurfaceError::OutOfMemory) => Err(GraphicsError::SurfaceOutOfMemory),
391 Err(wgpu::SurfaceError::Timeout) => Err(GraphicsError::SurfaceTimeout),
392 Err(e) => Err(GraphicsError::SurfaceTextureAcquisitionFailed(
393 e.to_string(),
394 )),
395 }
396 }
397}
398
399#[derive(Default)]
421pub struct RenderWindowBuilder {
422 present_mode: Option<wgpu::PresentMode>,
423 color_format: Option<wgpu::TextureFormat>,
424 alpha_mode: Option<wgpu::CompositeAlphaMode>,
425 depth_format: Option<wgpu::TextureFormat>,
426 enable_profiling: bool,
427}
428
429impl RenderWindowBuilder {
430 pub fn new() -> Self {
432 Self::default()
433 }
434
435 pub fn present_mode(mut self, mode: wgpu::PresentMode) -> Self {
437 self.present_mode = Some(mode);
438 self
439 }
440
441 pub fn color_format(mut self, format: wgpu::TextureFormat) -> Self {
443 self.color_format = Some(format);
444 self
445 }
446
447 pub fn alpha_mode(mut self, mode: wgpu::CompositeAlphaMode) -> Self {
449 self.alpha_mode = Some(mode);
450 self
451 }
452
453 pub fn with_depth(mut self, format: wgpu::TextureFormat) -> Self {
455 self.depth_format = Some(format);
456 self
457 }
458
459 pub fn with_depth_default(mut self) -> Self {
461 self.depth_format = Some(DEFAULT_DEPTH_FORMAT);
462 self
463 }
464
465 pub fn with_profiling(mut self, enabled: bool) -> Self {
467 self.enable_profiling = enabled;
468 self
469 }
470
471 pub fn build(
473 self,
474 window: Window,
475 graphics: Arc<GraphicsContext>,
476 ) -> Result<RenderWindow, GraphicsError> {
477 let descriptor = WindowContextDescriptor {
478 format: self.color_format,
479 present_mode: self.present_mode,
480 alpha_mode: self.alpha_mode,
481 with_depth: self.depth_format.is_some(),
482 depth_format: self.depth_format,
483 };
484
485 let context = WindowContext::new(window, graphics.clone(), descriptor)?;
486
487 let gpu_profiler = if self.enable_profiling {
488 Some(Arc::new(GpuFrameProfiler::new(&graphics)?))
489 } else {
490 None
491 };
492
493 Ok(RenderWindow {
494 context,
495 gpu_profiler,
496 })
497 }
498}
499
500pub struct RenderWindow {
538 pub(crate) context: WindowContext,
539 pub(crate) gpu_profiler: Option<Arc<GpuFrameProfiler>>,
540}
541
542impl RenderWindow {
543 pub fn builder() -> RenderWindowBuilder {
545 RenderWindowBuilder::new()
546 }
547
548 pub fn new(window: Window, context: Arc<GraphicsContext>) -> Result<Self, GraphicsError> {
550 Self::new_with_descriptor(window, context, WindowContextDescriptor::default())
551 }
552
553 pub fn new_with_depth(
557 window: Window,
558 context: Arc<GraphicsContext>,
559 ) -> Result<Self, GraphicsError> {
560 Self::builder().with_depth_default().build(window, context)
561 }
562
563 pub fn new_with_descriptor(
565 window: Window,
566 context: Arc<GraphicsContext>,
567 descriptor: WindowContextDescriptor,
568 ) -> Result<Self, GraphicsError> {
569 profile_function!();
570 let context = WindowContext::new(window, context, descriptor)?;
571 Ok(Self {
572 context,
573 gpu_profiler: None,
574 })
575 }
576
577 pub fn begin_frame(&mut self) -> Option<Frame<'_>> {
595 self.try_begin_frame().ok()
596 }
597
598 pub fn try_begin_frame(&mut self) -> Result<Frame<'_>, GraphicsError> {
603 profile_function!();
604
605 let mut configure_needed = false;
607 if let Some(new_size) = self.context.reconfigure.resize.take() {
608 self.context.config.width = new_size.width;
609 self.context.config.height = new_size.height;
610 configure_needed = true;
611
612 if let Some(ref mut depth) = self.context.depth_texture
614 && depth.needs_resize(new_size.width, new_size.height)
615 {
616 depth.resize(
617 self.context.context.device(),
618 new_size.width,
619 new_size.height,
620 );
621 }
622 }
623
624 if configure_needed {
625 self.context
626 .surface
627 .configure(self.context.context.device(), &self.context.config);
628 }
629
630 let surface_texture = self.context.try_acquire_surface_texture()?;
632 let view = surface_texture
633 .texture
634 .create_view(&wgpu::TextureViewDescriptor::default());
635
636 Ok(Frame {
637 window: self,
638 surface: Some(Surface {
639 texture: surface_texture,
640 view,
641 }),
642 command_buffers: RefCell::new(Vec::new()),
643 stats: Arc::new(AtomicFrameStats::new()),
644 submitted: Cell::new(false),
645 surface_format: self.context.config.format,
646 gpu_profiler: self.gpu_profiler.clone(),
647 winit_window: self.context.window.window.clone(),
648 })
649 }
650
651 pub fn id(&self) -> WindowId {
653 self.context.window.id()
654 }
655
656 pub fn window(&self) -> &Window {
658 &self.context.window
659 }
660
661 pub fn context(&self) -> &WindowContext {
663 &self.context
664 }
665
666 pub fn context_mut(&mut self) -> &mut WindowContext {
668 &mut self.context
669 }
670
671 pub fn graphics(&self) -> &GraphicsContext {
673 &self.context.context
674 }
675
676 pub fn graphics_arc(&self) -> &Arc<GraphicsContext> {
678 &self.context.context
679 }
680
681 pub fn surface_format(&self) -> wgpu::TextureFormat {
683 self.context.surface_format()
684 }
685
686 pub fn resized(&mut self, new_size: LogicalSize<u32>) {
688 self.context.resized(new_size);
689 }
690
691 pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
693 self.context.resized_physical(new_size);
694 }
695
696 pub fn physical_size(&self) -> PhysicalSize<u32> {
698 self.context.physical_size()
699 }
700
701 pub fn size(&self) -> (u32, u32) {
703 (self.context.config.width, self.context.config.height)
704 }
705
706 pub fn scale_factor(&self) -> ScaleFactor {
708 self.window().scale_factor()
709 }
710
711 pub fn viewport(&self) -> Viewport {
713 let physical_size = self.physical_size();
714 let scale_factor = self.scale_factor();
715
716 Viewport {
717 position: PhysicalPosition::new(0.0, 0.0),
718 size: PhysicalSize::new(physical_size.width as f32, physical_size.height as f32),
719 scale_factor,
720 }
721 }
722
723 pub fn set_gpu_profiler(&mut self, profiler: Arc<GpuFrameProfiler>) {
725 self.gpu_profiler = Some(profiler);
726 }
727
728 pub fn remove_gpu_profiler(&mut self) -> Option<Arc<GpuFrameProfiler>> {
730 self.gpu_profiler.take()
731 }
732
733 pub fn gpu_profiler(&self) -> Option<&Arc<GpuFrameProfiler>> {
735 self.gpu_profiler.as_ref()
736 }
737
738 pub fn has_depth(&self) -> bool {
740 self.context.has_depth()
741 }
742
743 pub fn depth_view(&self) -> Option<Arc<wgpu::TextureView>> {
745 self.context.depth_view()
746 }
747
748 pub fn depth_view_ref(&self) -> Option<&wgpu::TextureView> {
750 self.context.depth_texture.as_ref().map(|d| d.view_ref())
751 }
752
753 pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
758 self.context.depth_format()
759 }
760
761 pub fn ensure_depth(&mut self, format: wgpu::TextureFormat) {
763 self.context.ensure_depth(format);
764 }
765}
766
767impl std::ops::Deref for RenderWindow {
768 type Target = WindowContext;
769
770 fn deref(&self) -> &Self::Target {
771 &self.context
772 }
773}
774
775impl std::ops::DerefMut for RenderWindow {
776 fn deref_mut(&mut self) -> &mut Self::Target {
777 &mut self.context
778 }
779}
780
781impl WindowBackend for RenderWindow {
786 type FrameContext = Frame<'static>;
787 type Error = GraphicsError;
788
789 fn try_begin_drawing(&mut self) -> Result<Self::FrameContext, Self::Error> {
790 unimplemented!(
794 "Use RenderWindow::begin_frame() instead of WindowBackend::try_begin_drawing()"
795 )
796 }
797}
798
799#[deprecated(since = "0.2.0", note = "Use RenderWindow instead")]
805pub type RenderableWindow = RenderWindow;