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/// Internal surface management for a window.
169struct WindowSurface {
170    surface: wgpu::Surface<'static>,
171    config: wgpu::SurfaceConfiguration,
172}
173
174/// Manages a wgpu [`Surface`](wgpu::Surface) and its configuration for a single window.
175///
176/// Handles surface creation, reconfiguration on resize, and frame acquisition.
177/// Most users should interact with [`RenderWindow`] instead, which wraps
178/// this type and adds convenience methods.
179pub 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    /// Optional depth texture for this window, auto-resizes with window.
186    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        // Create depth texture if requested
226        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    /// Handle window resize event (logical size).
250    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    /// Handle window resize event (physical size).
257    pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
258        self.reconfigure.resize = Some(new_size);
259
260        // Resize depth texture if present
261        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    /// Get the surface texture format.
285    ///
286    /// This is the format that render pipelines must use when rendering to this
287    /// window's surface. Pass this to renderer constructors like
288    /// [`LineRenderer::new`](crate::LineRenderer::new).
289    pub fn surface_format(&self) -> wgpu::TextureFormat {
290        self.config.format
291    }
292
293    /// Get the logical size of the window.
294    pub fn logical_size(&self) -> LogicalSize<u32> {
295        self.window.logical_size()
296    }
297
298    /// Get the physical size of the window.
299    pub fn physical_size(&self) -> PhysicalSize<u32> {
300        self.window.physical_size()
301    }
302
303    /// Get the logical size as f32.
304    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    /// Get the physical size as f32.
310    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    /// Reconfigure the surface with a new configuration.
316    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    /// Check if this window has a depth texture.
322    pub fn has_depth(&self) -> bool {
323        self.depth_texture.is_some()
324    }
325
326    /// Get the depth texture view if available.
327    ///
328    /// Returns an Arc-wrapped view for cheap, lifetime-free sharing.
329    pub fn depth_view(&self) -> Option<std::sync::Arc<wgpu::TextureView>> {
330        self.depth_texture.as_ref().map(|d| d.view())
331    }
332
333    /// Get the depth texture format, if depth is enabled.
334    ///
335    /// Returns the format used for the depth texture, or None if depth is not enabled.
336    /// Use this to configure renderers that need to match the depth format.
337    pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
338        self.depth_texture.as_ref().map(|d| d.format())
339    }
340
341    /// Ensure a depth texture exists for this window.
342    ///
343    /// If no depth texture exists, creates one with the given format.
344    /// If a depth texture already exists, this is a no-op.
345    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    /// Try to acquire a surface texture, handling recoverable errors by reconfiguring.
360    ///
361    /// This method will attempt to reconfigure the surface if it's lost or outdated,
362    /// providing automatic recovery for common scenarios like window minimize/restore.
363    fn try_acquire_surface_texture(&mut self) -> Result<wgpu::SurfaceTexture, GraphicsError> {
364        // First attempt
365        match self.surface.get_current_texture() {
366            Ok(frame) => return Ok(frame),
367            Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
368                // Surface needs reconfiguration - try to recover
369                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        // Second attempt after reconfiguration
386        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// ============================================================================
400// RenderWindow - Main window type
401// ============================================================================
402
403/// Builder for configuring a [`RenderWindow`].
404///
405/// # Example
406///
407/// ```rust,no_run
408/// # use astrelis_render::{RenderWindow, GraphicsContext};
409/// # use astrelis_winit::window::Window;
410/// # use std::sync::Arc;
411/// # let window: Window = todo!();
412/// # let graphics: Arc<GraphicsContext> = todo!();
413/// let render_window = RenderWindow::builder()
414///     .present_mode(wgpu::PresentMode::Fifo)
415///     .with_depth_default()
416///     .with_profiling(true)
417///     .build(window, graphics)
418///     .expect("Failed to create window");
419/// ```
420#[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    /// Create a new builder with default settings.
431    pub fn new() -> Self {
432        Self::default()
433    }
434
435    /// Set the present mode for vsync behavior.
436    pub fn present_mode(mut self, mode: wgpu::PresentMode) -> Self {
437        self.present_mode = Some(mode);
438        self
439    }
440
441    /// Set the color format for the surface.
442    pub fn color_format(mut self, format: wgpu::TextureFormat) -> Self {
443        self.color_format = Some(format);
444        self
445    }
446
447    /// Set the alpha compositing mode.
448    pub fn alpha_mode(mut self, mode: wgpu::CompositeAlphaMode) -> Self {
449        self.alpha_mode = Some(mode);
450        self
451    }
452
453    /// Enable depth buffer with the specified format.
454    pub fn with_depth(mut self, format: wgpu::TextureFormat) -> Self {
455        self.depth_format = Some(format);
456        self
457    }
458
459    /// Enable depth buffer with default format (Depth32Float).
460    pub fn with_depth_default(mut self) -> Self {
461        self.depth_format = Some(DEFAULT_DEPTH_FORMAT);
462        self
463    }
464
465    /// Enable GPU profiling for this window.
466    pub fn with_profiling(mut self, enabled: bool) -> Self {
467        self.enable_profiling = enabled;
468        self
469    }
470
471    /// Build the render window.
472    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
500/// A renderable window that combines a winit [`Window`] with a [`WindowContext`].
501///
502/// This is the primary type for rendering to a window. It implements
503/// `Deref<Target = WindowContext>`, so all `WindowContext` methods are
504/// available directly.
505///
506/// # GPU Profiling
507///
508/// Attach a [`GpuFrameProfiler`] via [`set_gpu_profiler`](Self::set_gpu_profiler)
509/// to automatically profile render passes. Once attached, all frames created via
510/// [`begin_frame`](Self::begin_frame) will include GPU profiling.
511///
512/// # Example
513///
514/// ```rust,no_run
515/// # use astrelis_render::{RenderWindow, GraphicsContext, Color};
516/// # use astrelis_winit::window::Window;
517/// # use std::sync::Arc;
518/// # fn example(window: Window, graphics: Arc<GraphicsContext>) {
519/// let mut render_window = RenderWindow::builder()
520///     .with_depth_default()
521///     .build(window, graphics)
522///     .expect("Failed to create window");
523///
524/// // In render loop:
525/// let frame = render_window.begin_frame().expect("Surface available");
526/// {
527///     let mut pass = frame.render_pass()
528///         .clear_color(Color::BLACK)
529///         .with_window_depth()
530///         .clear_depth(0.0)
531///         .build();
532///     // Render commands...
533/// }
534/// // Frame auto-submits on drop
535/// # }
536/// ```
537pub struct RenderWindow {
538    pub(crate) context: WindowContext,
539    pub(crate) gpu_profiler: Option<Arc<GpuFrameProfiler>>,
540}
541
542impl RenderWindow {
543    /// Create a new builder for configuring a render window.
544    pub fn builder() -> RenderWindowBuilder {
545        RenderWindowBuilder::new()
546    }
547
548    /// Create a new renderable window with default settings.
549    pub fn new(window: Window, context: Arc<GraphicsContext>) -> Result<Self, GraphicsError> {
550        Self::new_with_descriptor(window, context, WindowContextDescriptor::default())
551    }
552
553    /// Create a new renderable window with an auto-resizing depth texture.
554    ///
555    /// This is equivalent to using [`builder()`](Self::builder) with `with_depth_default()`.
556    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    /// Create a new renderable window with a descriptor.
564    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    /// Begin a new frame for rendering.
578    ///
579    /// Returns `Some(Frame)` if the surface is available, or `None` if the
580    /// surface is temporarily unavailable (e.g., window minimized).
581    ///
582    /// # Example
583    ///
584    /// ```rust,no_run
585    /// # use astrelis_render::{RenderWindow, Color};
586    /// # let mut window: RenderWindow = todo!();
587    /// if let Some(frame) = window.begin_frame() {
588    ///     let mut pass = frame.render_pass()
589    ///         .clear_color(Color::BLACK)
590    ///         .build();
591    ///     // Render commands...
592    /// }
593    /// ```
594    pub fn begin_frame(&mut self) -> Option<Frame<'_>> {
595        self.try_begin_frame().ok()
596    }
597
598    /// Try to begin a new frame, returning an error on failure.
599    ///
600    /// Unlike [`begin_frame`](Self::begin_frame), this returns the actual error
601    /// for debugging or error handling.
602    pub fn try_begin_frame(&mut self) -> Result<Frame<'_>, GraphicsError> {
603        profile_function!();
604
605        // Handle pending resize
606        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            // Resize depth texture if present
613            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        // Acquire surface texture
631        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    /// Get the window ID.
652    pub fn id(&self) -> WindowId {
653        self.context.window.id()
654    }
655
656    /// Get the underlying window.
657    pub fn window(&self) -> &Window {
658        &self.context.window
659    }
660
661    /// Get the window context.
662    pub fn context(&self) -> &WindowContext {
663        &self.context
664    }
665
666    /// Get mutable access to the window context.
667    pub fn context_mut(&mut self) -> &mut WindowContext {
668        &mut self.context
669    }
670
671    /// Get the graphics context.
672    pub fn graphics(&self) -> &GraphicsContext {
673        &self.context.context
674    }
675
676    /// Get the Arc-wrapped graphics context.
677    pub fn graphics_arc(&self) -> &Arc<GraphicsContext> {
678        &self.context.context
679    }
680
681    /// Get the surface texture format.
682    pub fn surface_format(&self) -> wgpu::TextureFormat {
683        self.context.surface_format()
684    }
685
686    /// Handle window resize event (logical size).
687    pub fn resized(&mut self, new_size: LogicalSize<u32>) {
688        self.context.resized(new_size);
689    }
690
691    /// Handle window resize event (physical size).
692    pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
693        self.context.resized_physical(new_size);
694    }
695
696    /// Get the physical size of the window.
697    pub fn physical_size(&self) -> PhysicalSize<u32> {
698        self.context.physical_size()
699    }
700
701    /// Get the surface size in pixels.
702    pub fn size(&self) -> (u32, u32) {
703        (self.context.config.width, self.context.config.height)
704    }
705
706    /// Get the scale factor.
707    pub fn scale_factor(&self) -> ScaleFactor {
708        self.window().scale_factor()
709    }
710
711    /// Get the viewport for this window.
712    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    /// Attach a GPU profiler to this window.
724    pub fn set_gpu_profiler(&mut self, profiler: Arc<GpuFrameProfiler>) {
725        self.gpu_profiler = Some(profiler);
726    }
727
728    /// Remove the GPU profiler from this window.
729    pub fn remove_gpu_profiler(&mut self) -> Option<Arc<GpuFrameProfiler>> {
730        self.gpu_profiler.take()
731    }
732
733    /// Get a reference to the GPU profiler, if attached.
734    pub fn gpu_profiler(&self) -> Option<&Arc<GpuFrameProfiler>> {
735        self.gpu_profiler.as_ref()
736    }
737
738    /// Check if this window has a depth texture.
739    pub fn has_depth(&self) -> bool {
740        self.context.has_depth()
741    }
742
743    /// Get the depth texture view if available (Arc-wrapped).
744    pub fn depth_view(&self) -> Option<Arc<wgpu::TextureView>> {
745        self.context.depth_view()
746    }
747
748    /// Get a reference to the depth texture view (without Arc).
749    pub fn depth_view_ref(&self) -> Option<&wgpu::TextureView> {
750        self.context.depth_texture.as_ref().map(|d| d.view_ref())
751    }
752
753    /// Get the depth texture format, if depth is enabled.
754    ///
755    /// Returns the format used for the depth texture, or None if depth is not enabled.
756    /// Use this to configure renderers that need to match the depth format.
757    pub fn depth_format(&self) -> Option<wgpu::TextureFormat> {
758        self.context.depth_format()
759    }
760
761    /// Ensure a depth texture exists for this window.
762    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
781// ============================================================================
782// WindowBackend implementation for compatibility
783// ============================================================================
784
785impl 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        // This is a compatibility shim - the new API uses begin_frame()
791        // We can't actually return Frame<'static> safely, so this will need
792        // to be updated in the WindowBackend trait
793        unimplemented!(
794            "Use RenderWindow::begin_frame() instead of WindowBackend::try_begin_drawing()"
795        )
796    }
797}
798
799// ============================================================================
800// Backwards Compatibility Aliases
801// ============================================================================
802
803/// Deprecated alias for [`RenderWindow`].
804#[deprecated(since = "0.2.0", note = "Use RenderWindow instead")]
805pub type RenderableWindow = RenderWindow;