Skip to main content

astrelis_render/
window.rs

1//! Window and surface management for rendering.
2//!
3//! This module provides [`RenderWindow`], which wraps a [`Window`] and manages
4//! its GPU surface for rendering. It handles surface configuration, frame presentation,
5//! and surface loss recovery.
6//!
7//! # Lifecycle
8//!
9//! 1. Create with [`RenderWindow::builder()`] or [`RenderWindow::new()`]
10//! 2. Call [`begin_frame()`](RenderWindow::begin_frame) to start a frame
11//! 3. Use the returned [`Frame`] for rendering
12//! 4. Drop the frame to submit commands and present
13//!
14//! # Example
15//!
16//! ```rust,no_run
17//! use astrelis_render::{GraphicsContext, RenderWindow, Color};
18//! use astrelis_winit::window::Window;
19//! # use std::sync::Arc;
20//!
21//! # fn example(window: Window, graphics: Arc<GraphicsContext>) {
22//! let mut window = RenderWindow::builder()
23//!     .with_depth_default()
24//!     .build(window, graphics)
25//!     .expect("Failed to create render window");
26//!
27//! // In render loop:
28//! if let Some(frame) = window.begin_frame() {
29//!     let mut pass = frame.render_pass()
30//!         .clear_color(Color::BLACK)
31//!         .with_window_depth()
32//!         .clear_depth(0.0)
33//!         .build();
34//!     // Render commands...
35//! } // Frame auto-submits and presents on drop
36//! # }
37//! ```
38//!
39//! # Surface Loss
40//!
41//! The surface can be lost due to window minimization, GPU driver resets, or other
42//! platform events. [`RenderWindow`] handles this automatically by recreating
43//! the surface when [`begin_frame()`](RenderWindow::begin_frame) is called.
44
45use 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/// Viewport definition for rendering.
64///
65/// A viewport represents the renderable area of a window in physical coordinates,
66/// along with the scale factor for coordinate conversions.
67#[derive(Debug, Clone, Copy)]
68pub struct Viewport {
69    /// Position in physical coordinates (pixels).
70    pub position: PhysicalPosition<f32>,
71    /// Size in physical coordinates (pixels).
72    pub size: PhysicalSize<f32>,
73    /// Scale factor for logical/physical conversion.
74    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            // it needs to be 1.0 to avoid division by zero and other issues
83            scale_factor: ScaleFactor(1.0),
84        }
85    }
86}
87
88impl Viewport {
89    /// Create a new viewport with the given physical size and scale factor.
90    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    /// Create a viewport from physical size.
99    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    /// Check if the viewport is valid (has positive dimensions).
108    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    /// Get the size in logical pixels.
113    pub fn to_logical(&self) -> LogicalSize<f32> {
114        self.size.to_logical(self.scale_factor)
115    }
116
117    /// Get the width in physical pixels.
118    pub fn width(&self) -> f32 {
119        self.size.width
120    }
121
122    /// Get the height in physical pixels.
123    pub fn height(&self) -> f32 {
124        self.size.height
125    }
126
127    /// Get the x position in physical pixels.
128    pub fn x(&self) -> f32 {
129        self.position.x
130    }
131
132    /// Get the y position in physical pixels.
133    pub fn y(&self) -> f32 {
134        self.position.y
135    }
136}
137
138/// Descriptor for configuring a window's rendering context.
139#[derive(Default)]
140pub struct WindowContextDescriptor {
141    /// The surface texture format. If None, uses the default format for the surface.
142    pub format: Option<wgpu::TextureFormat>,
143    /// Present mode for the surface.
144    pub present_mode: Option<wgpu::PresentMode>,
145    /// Alpha mode for the surface.
146    pub alpha_mode: Option<wgpu::CompositeAlphaMode>,
147    /// Whether to create a depth texture for this window.
148    ///
149    /// When enabled, the window will maintain an auto-resizing depth texture
150    /// that can be accessed via [`Frame::depth_view()`].
151    pub with_depth: bool,
152    /// The depth texture format. Defaults to `Depth32Float` if not specified.
153    ///
154    /// Only used when `with_depth` is `true`.
155    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
168/// Manages a wgpu [`Surface`](wgpu::Surface) and its configuration for a single window.
169///
170/// Handles surface creation, reconfiguration on resize, and frame acquisition.
171/// Most users should interact with [`RenderWindow`] instead, which wraps
172/// this type and adds convenience methods.
173pub 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    /// Optional depth texture for this window, auto-resizes with window.
180    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        // Create depth texture if requested
220        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    /// Handle window resize event (logical size).
244    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    /// Handle window resize event (physical size).
251    pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
252        self.reconfigure.resize = Some(new_size);
253
254        // Resize depth texture if present
255        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    /// Get the surface texture format.
279    ///
280    /// This is the format that render pipelines must use when rendering to this
281    /// window's surface. Pass this to renderer constructors like
282    /// [`LineRenderer::new`](crate::LineRenderer::new).
283    pub fn surface_format(&self) -> wgpu::TextureFormat {
284        self.config.format
285    }
286
287    /// Get the logical size of the window.
288    pub fn logical_size(&self) -> LogicalSize<u32> {
289        self.window.logical_size()
290    }
291
292    /// Get the physical size of the window.
293    pub fn physical_size(&self) -> PhysicalSize<u32> {
294        self.window.physical_size()
295    }
296
297    /// Get the logical size as f32.
298    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    /// Get the physical size as f32.
304    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    /// Reconfigure the surface with a new configuration.
310    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    /// Check if this window has a depth texture.
316    pub fn has_depth(&self) -> bool {
317        self.depth_texture.is_some()
318    }
319
320    /// Get the depth texture view if available.
321    ///
322    /// Returns an Arc-wrapped view for cheap, lifetime-free sharing.
323    pub fn depth_view(&self) -> Option<std::sync::Arc<wgpu::TextureView>> {
324        self.depth_texture.as_ref().map(|d| d.view())
325    }
326
327    /// Get the depth texture format, if depth is enabled.
328    ///
329    /// Returns the format used for the depth texture, or None if depth is not enabled.
330    /// Use this to configure renderers that need to match the depth format.
331    pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
332        self.depth_texture.as_ref().map(|d| d.format())
333    }
334
335    /// Ensure a depth texture exists for this window.
336    ///
337    /// If no depth texture exists, creates one with the given format.
338    /// If a depth texture already exists, this is a no-op.
339    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    /// Try to acquire a surface texture, handling recoverable errors by reconfiguring.
354    ///
355    /// This method will attempt to reconfigure the surface if it's lost or outdated,
356    /// providing automatic recovery for common scenarios like window minimize/restore.
357    fn try_acquire_surface_texture(&mut self) -> Result<wgpu::SurfaceTexture, GraphicsError> {
358        // First attempt
359        match self.surface.get_current_texture() {
360            Ok(frame) => return Ok(frame),
361            Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
362                // Surface needs reconfiguration - try to recover
363                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        // Second attempt after reconfiguration
380        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// ============================================================================
394// RenderWindow - Main window type
395// ============================================================================
396
397/// Builder for configuring a [`RenderWindow`].
398///
399/// # Example
400///
401/// ```rust,no_run
402/// # use astrelis_render::{RenderWindow, GraphicsContext};
403/// # use astrelis_winit::window::Window;
404/// # use std::sync::Arc;
405/// # let window: Window = todo!();
406/// # let graphics: Arc<GraphicsContext> = todo!();
407/// let render_window = RenderWindow::builder()
408///     .present_mode(wgpu::PresentMode::Fifo)
409///     .with_depth_default()
410///     .with_profiling(true)
411///     .build(window, graphics)
412///     .expect("Failed to create window");
413/// ```
414#[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    /// Create a new builder with default settings.
425    pub fn new() -> Self {
426        Self::default()
427    }
428
429    /// Set the present mode for vsync behavior.
430    pub fn present_mode(mut self, mode: wgpu::PresentMode) -> Self {
431        self.present_mode = Some(mode);
432        self
433    }
434
435    /// Set the color format for the surface.
436    pub fn color_format(mut self, format: wgpu::TextureFormat) -> Self {
437        self.color_format = Some(format);
438        self
439    }
440
441    /// Set the alpha compositing mode.
442    pub fn alpha_mode(mut self, mode: wgpu::CompositeAlphaMode) -> Self {
443        self.alpha_mode = Some(mode);
444        self
445    }
446
447    /// Enable depth buffer with the specified format.
448    pub fn with_depth(mut self, format: wgpu::TextureFormat) -> Self {
449        self.depth_format = Some(format);
450        self
451    }
452
453    /// Enable depth buffer with default format (Depth32Float).
454    pub fn with_depth_default(mut self) -> Self {
455        self.depth_format = Some(DEFAULT_DEPTH_FORMAT);
456        self
457    }
458
459    /// Enable GPU profiling for this window.
460    pub fn with_profiling(mut self, enabled: bool) -> Self {
461        self.enable_profiling = enabled;
462        self
463    }
464
465    /// Build the render window.
466    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
494/// A renderable window that combines a winit [`Window`] with a [`WindowContext`].
495///
496/// This is the primary type for rendering to a window. It implements
497/// `Deref<Target = WindowContext>`, so all `WindowContext` methods are
498/// available directly.
499///
500/// # GPU Profiling
501///
502/// Attach a [`GpuFrameProfiler`] via [`set_gpu_profiler`](Self::set_gpu_profiler)
503/// to automatically profile render passes. Once attached, all frames created via
504/// [`begin_frame`](Self::begin_frame) will include GPU profiling.
505///
506/// # Example
507///
508/// ```rust,no_run
509/// # use astrelis_render::{RenderWindow, GraphicsContext, Color};
510/// # use astrelis_winit::window::Window;
511/// # use std::sync::Arc;
512/// # fn example(window: Window, graphics: Arc<GraphicsContext>) {
513/// let mut render_window = RenderWindow::builder()
514///     .with_depth_default()
515///     .build(window, graphics)
516///     .expect("Failed to create window");
517///
518/// // In render loop:
519/// let frame = render_window.begin_frame().expect("Surface available");
520/// {
521///     let mut pass = frame.render_pass()
522///         .clear_color(Color::BLACK)
523///         .with_window_depth()
524///         .clear_depth(0.0)
525///         .build();
526///     // Render commands...
527/// }
528/// // Frame auto-submits on drop
529/// # }
530/// ```
531pub struct RenderWindow {
532    pub(crate) context: WindowContext,
533    pub(crate) gpu_profiler: Option<Arc<GpuFrameProfiler>>,
534}
535
536impl RenderWindow {
537    /// Create a new builder for configuring a render window.
538    pub fn builder() -> RenderWindowBuilder {
539        RenderWindowBuilder::new()
540    }
541
542    /// Create a new renderable window with default settings.
543    pub fn new(window: Window, context: Arc<GraphicsContext>) -> Result<Self, GraphicsError> {
544        Self::new_with_descriptor(window, context, WindowContextDescriptor::default())
545    }
546
547    /// Create a new renderable window with an auto-resizing depth texture.
548    ///
549    /// This is equivalent to using [`builder()`](Self::builder) with `with_depth_default()`.
550    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    /// Create a new renderable window with a descriptor.
558    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    /// Begin a new frame for rendering.
572    ///
573    /// Returns `Some(Frame)` if the surface is available, or `None` if the
574    /// surface is temporarily unavailable (e.g., window minimized).
575    ///
576    /// # Example
577    ///
578    /// ```rust,no_run
579    /// # use astrelis_render::{RenderWindow, Color};
580    /// # let mut window: RenderWindow = todo!();
581    /// if let Some(frame) = window.begin_frame() {
582    ///     let mut pass = frame.render_pass()
583    ///         .clear_color(Color::BLACK)
584    ///         .build();
585    ///     // Render commands...
586    /// }
587    /// ```
588    pub fn begin_frame(&mut self) -> Option<Frame<'_>> {
589        self.try_begin_frame().ok()
590    }
591
592    /// Try to begin a new frame, returning an error on failure.
593    ///
594    /// Unlike [`begin_frame`](Self::begin_frame), this returns the actual error
595    /// for debugging or error handling.
596    pub fn try_begin_frame(&mut self) -> Result<Frame<'_>, GraphicsError> {
597        profile_function!();
598
599        // Handle pending resize
600        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            // Resize depth texture if present
607            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        // Acquire surface texture
625        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    /// Get the window ID.
646    pub fn id(&self) -> WindowId {
647        self.context.window.id()
648    }
649
650    /// Get the underlying window.
651    pub fn window(&self) -> &Window {
652        &self.context.window
653    }
654
655    /// Get the window context.
656    pub fn context(&self) -> &WindowContext {
657        &self.context
658    }
659
660    /// Get mutable access to the window context.
661    pub fn context_mut(&mut self) -> &mut WindowContext {
662        &mut self.context
663    }
664
665    /// Get the graphics context.
666    pub fn graphics(&self) -> &GraphicsContext {
667        &self.context.context
668    }
669
670    /// Get the Arc-wrapped graphics context.
671    pub fn graphics_arc(&self) -> &Arc<GraphicsContext> {
672        &self.context.context
673    }
674
675    /// Get the surface texture format.
676    pub fn surface_format(&self) -> wgpu::TextureFormat {
677        self.context.surface_format()
678    }
679
680    /// Handle window resize event (logical size).
681    pub fn resized(&mut self, new_size: LogicalSize<u32>) {
682        self.context.resized(new_size);
683    }
684
685    /// Handle window resize event (physical size).
686    pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
687        self.context.resized_physical(new_size);
688    }
689
690    /// Get the physical size of the window.
691    pub fn physical_size(&self) -> PhysicalSize<u32> {
692        self.context.physical_size()
693    }
694
695    /// Get the surface size in pixels.
696    pub fn size(&self) -> (u32, u32) {
697        (self.context.config.width, self.context.config.height)
698    }
699
700    /// Get the scale factor.
701    pub fn scale_factor(&self) -> ScaleFactor {
702        self.window().scale_factor()
703    }
704
705    /// Get the viewport for this window.
706    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    /// Attach a GPU profiler to this window.
718    pub fn set_gpu_profiler(&mut self, profiler: Arc<GpuFrameProfiler>) {
719        self.gpu_profiler = Some(profiler);
720    }
721
722    /// Remove the GPU profiler from this window.
723    pub fn remove_gpu_profiler(&mut self) -> Option<Arc<GpuFrameProfiler>> {
724        self.gpu_profiler.take()
725    }
726
727    /// Get a reference to the GPU profiler, if attached.
728    pub fn gpu_profiler(&self) -> Option<&Arc<GpuFrameProfiler>> {
729        self.gpu_profiler.as_ref()
730    }
731
732    /// Check if this window has a depth texture.
733    pub fn has_depth(&self) -> bool {
734        self.context.has_depth()
735    }
736
737    /// Get the depth texture view if available (Arc-wrapped).
738    pub fn depth_view(&self) -> Option<Arc<wgpu::TextureView>> {
739        self.context.depth_view()
740    }
741
742    /// Get a reference to the depth texture view (without Arc).
743    pub fn depth_view_ref(&self) -> Option<&wgpu::TextureView> {
744        self.context.depth_texture.as_ref().map(|d| d.view_ref())
745    }
746
747    /// Get the depth texture format, if depth is enabled.
748    ///
749    /// Returns the format used for the depth texture, or None if depth is not enabled.
750    /// Use this to configure renderers that need to match the depth format.
751    pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
752        self.context.depth_format()
753    }
754
755    /// Ensure a depth texture exists for this window.
756    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
775// ============================================================================
776// WindowBackend implementation for compatibility
777// ============================================================================
778
779impl 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        // This is a compatibility shim - the new API uses begin_frame()
785        // We can't actually return Frame<'static> safely, so this will need
786        // to be updated in the WindowBackend trait
787        unimplemented!(
788            "Use RenderWindow::begin_frame() instead of WindowBackend::try_begin_drawing()"
789        )
790    }
791}