astrelis_render/
window.rs

1use astrelis_core::{
2    geometry::{LogicalSize, PhysicalPosition, PhysicalSize, ScaleFactor},
3    profiling::profile_function,
4};
5use astrelis_winit::{
6    WindowId,
7    window::{Window, WindowBackend},
8};
9use std::sync::Arc;
10
11use crate::{
12    context::{GraphicsContext, GraphicsError},
13    frame::{FrameContext, FrameStats, Surface},
14};
15
16/// Viewport definition for rendering.
17///
18/// A viewport represents the renderable area of a window in physical coordinates,
19/// along with the scale factor for coordinate conversions.
20#[derive(Debug, Clone, Copy)]
21pub struct Viewport {
22    /// Position in physical coordinates (pixels).
23    pub position: PhysicalPosition<f32>,
24    /// Size in physical coordinates (pixels).
25    pub size: PhysicalSize<f32>,
26    /// Scale factor for logical/physical conversion.
27    pub scale_factor: ScaleFactor,
28}
29
30impl Default for Viewport {
31    fn default() -> Self {
32        Self {
33            position: PhysicalPosition::new(0.0, 0.0),
34            size: PhysicalSize::new(800.0, 600.0),
35            // it needs to be 1.0 to avoid division by zero and other issues
36            scale_factor: ScaleFactor(1.0),
37        }
38    }
39}
40
41impl Viewport {
42    /// Create a new viewport with the given physical size and scale factor.
43    pub fn new(width: f32, height: f32, scale_factor: ScaleFactor) -> Self {
44        Self {
45            position: PhysicalPosition::new(0.0, 0.0),
46            size: PhysicalSize::new(width, height),
47            scale_factor,
48        }
49    }
50
51    /// Create a viewport from physical size.
52    pub fn from_physical_size(size: PhysicalSize<u32>, scale_factor: ScaleFactor) -> Self {
53        Self {
54            position: PhysicalPosition::new(0.0, 0.0),
55            size: PhysicalSize::new(size.width as f32, size.height as f32),
56            scale_factor,
57        }
58    }
59
60    /// Check if the viewport is valid (has positive dimensions).
61    pub fn is_valid(&self) -> bool {
62        self.size.width > 0.0 && self.size.height > 0.0 && self.scale_factor.0 > 0.0
63    }
64
65    /// Get the size in logical pixels.
66    pub fn to_logical(&self) -> LogicalSize<f32> {
67        self.size.to_logical(self.scale_factor)
68    }
69
70    /// Get the width in physical pixels.
71    pub fn width(&self) -> f32 {
72        self.size.width
73    }
74
75    /// Get the height in physical pixels.
76    pub fn height(&self) -> f32 {
77        self.size.height
78    }
79
80    /// Get the x position in physical pixels.
81    pub fn x(&self) -> f32 {
82        self.position.x
83    }
84
85    /// Get the y position in physical pixels.
86    pub fn y(&self) -> f32 {
87        self.position.y
88    }
89}
90
91/// Descriptor for configuring a window's rendering context.
92#[derive(Default)]
93pub struct WindowContextDescriptor {
94    /// The surface texture format. If None, uses the default format for the surface.
95    pub format: Option<wgpu::TextureFormat>,
96    /// Present mode for the surface.
97    pub present_mode: Option<wgpu::PresentMode>,
98    /// Alpha mode for the surface.
99    pub alpha_mode: Option<wgpu::CompositeAlphaMode>,
100}
101
102pub struct PendingReconfigure {
103    pub resize: Option<PhysicalSize<u32>>,
104}
105
106impl PendingReconfigure {
107    const fn new() -> Self {
108        Self { resize: None }
109    }
110}
111
112/// Window rendering context that manages a surface and its configuration.
113pub struct WindowContext {
114    pub(crate) window: Window,
115    pub(crate) context: Arc<GraphicsContext>,
116    pub(crate) surface: wgpu::Surface<'static>,
117    pub(crate) config: wgpu::SurfaceConfiguration,
118    pub(crate) reconfigure: PendingReconfigure,
119}
120
121impl WindowContext {
122    pub fn new(
123        window: Window,
124        context: Arc<GraphicsContext>,
125        descriptor: WindowContextDescriptor,
126    ) -> Result<Self, GraphicsError> {
127        let scale_factor = window.scale_factor();
128        let logical_size = window.logical_size();
129        let physical_size = logical_size.to_physical(scale_factor);
130
131        let surface = context
132            .instance
133            .create_surface(window.window.clone())
134            .map_err(|e| GraphicsError::SurfaceCreationFailed(e.to_string()))?;
135
136        let mut config = surface
137            .get_default_config(&context.adapter, physical_size.width, physical_size.height)
138            .ok_or_else(|| GraphicsError::SurfaceConfigurationFailed(
139                "No suitable surface configuration found".to_string()
140            ))?;
141
142        if let Some(format) = descriptor.format {
143            config.format = format;
144        }
145        if let Some(present_mode) = descriptor.present_mode {
146            config.present_mode = present_mode;
147        }
148        if let Some(alpha_mode) = descriptor.alpha_mode {
149            config.alpha_mode = alpha_mode;
150        }
151
152        surface.configure(&context.device, &config);
153
154        Ok(Self {
155            window,
156            surface,
157            config,
158            reconfigure: PendingReconfigure::new(),
159            context,
160        })
161    }
162
163    /// Handle window resize event (logical size).
164    pub fn resized(&mut self, new_size: LogicalSize<u32>) {
165        let scale_factor = self.window.scale_factor();
166        let physical_size = new_size.to_physical(scale_factor);
167        self.reconfigure.resize = Some(physical_size);
168    }
169
170    /// Handle window resize event (physical size).
171    pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
172        self.reconfigure.resize = Some(new_size);
173    }
174
175    pub fn window(&self) -> &Window {
176        &self.window
177    }
178
179    pub fn graphics_context(&self) -> &GraphicsContext {
180        &self.context
181    }
182
183    pub fn surface(&self) -> &wgpu::Surface<'static> {
184        &self.surface
185    }
186
187    pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
188        &self.config
189    }
190
191    /// Get the logical size of the window.
192    pub fn logical_size(&self) -> LogicalSize<u32> {
193        self.window.logical_size()
194    }
195
196    /// Get the physical size of the window.
197    pub fn physical_size(&self) -> PhysicalSize<u32> {
198        self.window.physical_size()
199    }
200
201    /// Get the logical size as f32.
202    pub fn logical_size_f32(&self) -> LogicalSize<f32> {
203        let size = self.logical_size();
204        LogicalSize::new(size.width as f32, size.height as f32)
205    }
206
207    /// Get the physical size as f32.
208    pub fn physical_size_f32(&self) -> PhysicalSize<f32> {
209        let size = self.physical_size();
210        PhysicalSize::new(size.width as f32, size.height as f32)
211    }
212
213    /// Reconfigure the surface with a new configuration.
214    pub fn reconfigure_surface(&mut self, config: wgpu::SurfaceConfiguration) {
215        self.config = config;
216        self.surface.configure(&self.context.device, &self.config);
217    }
218}
219
220impl WindowBackend for WindowContext {
221    type FrameContext = FrameContext;
222
223    fn begin_drawing(&mut self) -> Self::FrameContext {
224        profile_function!();
225
226        let mut configure_needed = false;
227        if let Some(new_size) = self.reconfigure.resize.take() {
228            self.config.width = new_size.width;
229            self.config.height = new_size.height;
230            configure_needed = true;
231        }
232
233        if configure_needed {
234            self.surface.configure(&self.context.device, &self.config);
235        }
236
237        let frame = self.surface.get_current_texture().unwrap_or_else(|e| {
238            tracing::error!("Failed to acquire surface texture: {}. This may indicate the surface is lost or outdated.", e);
239            panic!("Surface texture acquisition failed: {}. Consider handling surface recreation in your application.", e);
240        });
241        let view = frame
242            .texture
243            .create_view(&wgpu::TextureViewDescriptor::default());
244
245        let encoder = self
246            .context
247            .device
248            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
249                label: Some("Frame Encoder"),
250            });
251
252        FrameContext {
253            surface: Some(Surface {
254                texture: frame,
255                view,
256            }),
257            encoder: Some(encoder),
258            context: self.context.clone(),
259            stats: FrameStats::new(),
260            window: self.window.window.clone(),
261            surface_format: self.config.format,
262        }
263    }
264}
265
266/// A renderable window that combines a window with a rendering context.
267pub struct RenderableWindow {
268    pub(crate) context: WindowContext,
269}
270
271impl RenderableWindow {
272    pub fn new(window: Window, context: Arc<GraphicsContext>) -> Result<Self, GraphicsError> {
273        Self::new_with_descriptor(window, context, WindowContextDescriptor::default())
274    }
275
276    pub fn new_with_descriptor(
277        window: Window,
278        context: Arc<GraphicsContext>,
279        descriptor: WindowContextDescriptor,
280    ) -> Result<Self, GraphicsError> {
281        let context = WindowContext::new(window, context, descriptor)?;
282        Ok(Self { context })
283    }
284
285    pub fn id(&self) -> WindowId {
286        self.context.window.id()
287    }
288
289    pub fn window(&self) -> &Window {
290        &self.context.window
291    }
292
293    pub fn context(&self) -> &WindowContext {
294        &self.context
295    }
296
297    pub fn context_mut(&mut self) -> &mut WindowContext {
298        &mut self.context
299    }
300
301    /// Handle window resize event (logical size).
302    pub fn resized(&mut self, new_size: LogicalSize<u32>) {
303        self.context.resized(new_size);
304    }
305
306    /// Handle window resize event (physical size).
307    pub fn resized_physical(&mut self, new_size: PhysicalSize<u32>) {
308        self.context.resized_physical(new_size);
309    }
310
311    /// Get the physical size of the window.
312    pub fn physical_size(&self) -> PhysicalSize<u32> {
313        self.context.physical_size()
314    }
315
316    /// Get the scale factor.
317    pub fn scale_factor(&self) -> ScaleFactor {
318        self.window().scale_factor()
319    }
320
321    /// Get the viewport for this window.
322    pub fn viewport(&self) -> Viewport {
323        let physical_size = self.physical_size();
324        let scale_factor = self.scale_factor();
325
326        Viewport {
327            position: PhysicalPosition::new(0.0, 0.0),
328            size: PhysicalSize::new(physical_size.width as f32, physical_size.height as f32),
329            scale_factor,
330        }
331    }
332}
333
334impl std::ops::Deref for RenderableWindow {
335    type Target = WindowContext;
336
337    fn deref(&self) -> &Self::Target {
338        &self.context
339    }
340}
341
342impl std::ops::DerefMut for RenderableWindow {
343    fn deref_mut(&mut self) -> &mut Self::Target {
344        &mut self.context
345    }
346}
347
348impl WindowBackend for RenderableWindow {
349    type FrameContext = FrameContext;
350
351    fn begin_drawing(&mut self) -> Self::FrameContext {
352        self.context.begin_drawing()
353    }
354}