astrelis_render/
window.rs

1use astrelis_core::{geometry::Size, profiling::profile_function};
2use astrelis_winit::{
3    WindowId,
4    event::PhysicalSize,
5    window::{Window, WindowBackend},
6};
7
8use crate::{
9    context::GraphicsContext,
10    frame::{FrameContext, FrameStats, Surface},
11};
12
13/// Viewport definition for rendering.
14#[derive(Debug, Clone, Copy)]
15pub struct Viewport {
16    pub x: f32,
17    pub y: f32,
18    pub width: f32,
19    pub height: f32,
20    pub scale_factor: f64,
21}
22
23impl Default for Viewport {
24    fn default() -> Self {
25        Self {
26            x: 0.0,
27            y: 0.0,
28            width: 800.0,
29            height: 600.0,
30            // it needs to be 1.0 to avoid division by zero and other issues
31            scale_factor: 1.0,
32        }
33    }
34}
35
36impl Viewport {
37    pub fn is_valid(&self) -> bool {
38        self.width > 0.0 && self.height > 0.0 && self.scale_factor > 0.0
39    }
40
41    /// Get the size in logical pixels.
42    pub fn to_logical(&self) -> Size<f32> {
43        Size {
44            width: self.width as f32 / self.scale_factor as f32,
45            height: self.height as f32 / self.scale_factor as f32,
46        }
47    }
48}
49
50/// Descriptor for configuring a window's rendering context.
51#[derive(Default)]
52pub struct WindowContextDescriptor {
53    /// The surface texture format. If None, uses the default format for the surface.
54    pub format: Option<wgpu::TextureFormat>,
55    /// Present mode for the surface.
56    pub present_mode: Option<wgpu::PresentMode>,
57    /// Alpha mode for the surface.
58    pub alpha_mode: Option<wgpu::CompositeAlphaMode>,
59}
60
61pub struct PendingReconfigure {
62    pub resize: Option<PhysicalSize<u32>>,
63}
64
65impl PendingReconfigure {
66    const fn new() -> Self {
67        Self { resize: None }
68    }
69}
70
71/// Window rendering context that manages a surface and its configuration.
72pub struct WindowContext {
73    pub(crate) window: Window,
74    pub(crate) context: &'static GraphicsContext,
75    pub(crate) surface: wgpu::Surface<'static>,
76    pub(crate) config: wgpu::SurfaceConfiguration,
77    pub(crate) reconfigure: PendingReconfigure,
78}
79
80impl WindowContext {
81    pub fn new(
82        window: Window,
83        context: &'static GraphicsContext,
84        descriptor: WindowContextDescriptor,
85    ) -> Self {
86        let scale_factor = window.window.scale_factor();
87        let Size { width, height } = window.size();
88        let (width, height) = (
89            (width as f64 * scale_factor) as u32,
90            (height as f64 * scale_factor) as u32,
91        );
92
93        let surface = context
94            .instance
95            .create_surface(window.window.clone())
96            .expect("Failed to create surface");
97
98        let mut config = surface
99            .get_default_config(&context.adapter, width, height)
100            .expect("Failed to get default surface configuration");
101
102        if let Some(format) = descriptor.format {
103            config.format = format;
104        }
105        if let Some(present_mode) = descriptor.present_mode {
106            config.present_mode = present_mode;
107        }
108        if let Some(alpha_mode) = descriptor.alpha_mode {
109            config.alpha_mode = alpha_mode;
110        }
111
112        surface.configure(&context.device, &config);
113
114        Self {
115            window,
116            surface,
117            config,
118            reconfigure: PendingReconfigure::new(),
119            context,
120        }
121    }
122
123    /// Handle window resize event
124    pub fn resized(&mut self, new_size: Size<u32>) {
125        let scale_factor = self.window.window.scale_factor();
126        self.reconfigure.resize = Some(PhysicalSize {
127            width: (new_size.width as f64 * scale_factor) as u32,
128            height: (new_size.height as f64 * scale_factor) as u32,
129        });
130    }
131
132    pub fn window(&self) -> &Window {
133        &self.window
134    }
135
136    pub fn graphics_context(&self) -> &GraphicsContext {
137        self.context
138    }
139
140    pub fn surface(&self) -> &wgpu::Surface<'static> {
141        &self.surface
142    }
143
144    pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
145        &self.config
146    }
147
148    /// Get the size of the window.
149    pub fn size(&self) -> Size<u32> {
150        self.window.size()
151    }
152
153    pub fn size_f32(&self) -> Size<f32> {
154        let size = self.size();
155        Size {
156            width: size.width as f32,
157            height: size.height as f32,
158        }
159    }
160
161    /// Get the inner size of the window.
162    pub fn inner_size(&self) -> PhysicalSize<u32> {
163        self.window.inner_size()
164    }
165
166    /// Reconfigure the surface with a new configuration.
167    pub fn reconfigure_surface(&mut self, config: wgpu::SurfaceConfiguration) {
168        self.config = config;
169        self.surface.configure(&self.context.device, &self.config);
170    }
171}
172
173impl WindowBackend for WindowContext {
174    type FrameContext = FrameContext;
175
176    fn begin_drawing(&mut self) -> Self::FrameContext {
177        profile_function!();
178
179        let mut configure_needed = false;
180        if let Some(new_size) = self.reconfigure.resize.take() {
181            self.config.width = new_size.width;
182            self.config.height = new_size.height;
183            configure_needed = true;
184        }
185
186        if configure_needed {
187            self.surface.configure(&self.context.device, &self.config);
188        }
189
190        let frame = self.surface.get_current_texture().unwrap();
191        let view = frame
192            .texture
193            .create_view(&wgpu::TextureViewDescriptor::default());
194
195        let encoder = self
196            .context
197            .device
198            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
199                label: Some("Frame Encoder"),
200            });
201
202        FrameContext {
203            surface: Some(Surface {
204                texture: frame,
205                view,
206            }),
207            encoder: Some(encoder),
208            context: self.context,
209            stats: FrameStats::new(),
210            window: self.window.window.clone(),
211            surface_format: self.config.format,
212        }
213    }
214}
215
216/// A renderable window that combines a window with a rendering context.
217pub struct RenderableWindow {
218    pub(crate) context: WindowContext,
219}
220
221impl RenderableWindow {
222    pub fn new(window: Window, context: &'static GraphicsContext) -> Self {
223        Self::new_with_descriptor(window, context, WindowContextDescriptor::default())
224    }
225
226    pub fn new_with_descriptor(
227        window: Window,
228        context: &'static GraphicsContext,
229        descriptor: WindowContextDescriptor,
230    ) -> Self {
231        let context = WindowContext::new(window, context, descriptor);
232        Self { context }
233    }
234
235    pub fn id(&self) -> WindowId {
236        self.context.window.id()
237    }
238
239    pub fn window(&self) -> &Window {
240        &self.context.window
241    }
242
243    pub fn context(&self) -> &WindowContext {
244        &self.context
245    }
246
247    pub fn context_mut(&mut self) -> &mut WindowContext {
248        &mut self.context
249    }
250
251    /// Handle window resize event
252    pub fn resized(&mut self, new_size: Size<u32>) {
253        self.context.resized(new_size);
254    }
255
256    /// Get the inner size of the window.
257    pub fn inner_size(&self) -> PhysicalSize<u32> {
258        self.context.inner_size()
259    }
260
261    pub fn scale_factor(&self) -> f64 {
262        self.window.window.scale_factor()
263    }
264
265    pub fn viewport(&self) -> Viewport {
266        let PhysicalSize { width, height } = self.inner_size();
267        let scale_factor = self.scale_factor();
268
269        Viewport {
270            x: 0.0,
271            y: 0.0,
272            width: width as f32,
273            height: height as f32,
274            scale_factor,
275        }
276    }
277}
278
279impl std::ops::Deref for RenderableWindow {
280    type Target = WindowContext;
281
282    fn deref(&self) -> &Self::Target {
283        &self.context
284    }
285}
286
287impl std::ops::DerefMut for RenderableWindow {
288    fn deref_mut(&mut self) -> &mut Self::Target {
289        &mut self.context
290    }
291}
292
293impl WindowBackend for RenderableWindow {
294    type FrameContext = FrameContext;
295
296    fn begin_drawing(&mut self) -> Self::FrameContext {
297        self.context.begin_drawing()
298    }
299}