three_d/window/winit_window/
windowed_context.rs

1use crate::Context;
2use crate::SurfaceSettings;
3use crate::WindowError;
4use std::sync::Arc;
5use winit::window::Window;
6
7#[cfg(target_arch = "wasm32")]
8mod inner {
9    use crate::HardwareAcceleration;
10    use serde::{Deserialize, Serialize};
11    use wasm_bindgen::JsCast;
12    use winit::platform::web::WindowExtWebSys;
13
14    use super::*;
15    #[allow(non_snake_case)]
16    #[derive(Serialize, Deserialize)]
17    struct ContextOpt {
18        pub antialias: bool,
19        pub depth: bool,
20        pub stencil: bool,
21        pub willReadFrequently: bool,
22        pub alpha: bool,
23    }
24
25    /// A context used for rendering
26    pub struct WindowedContext {
27        pub(super) context: Context,
28    }
29
30    impl WindowedContext {
31        /// Creates a new context from a [winit] window.
32        pub fn from_winit_window(
33            window: &Window,
34            settings: SurfaceSettings,
35        ) -> Result<Self, WindowError> {
36            let canvas = window.canvas();
37
38            // get webgl context and verify extensions
39            let webgl_context = canvas
40                .get_context_with_context_options(
41                    "webgl2",
42                    &serde_wasm_bindgen::to_value(&ContextOpt {
43                        antialias: settings.multisamples > 0,
44                        depth: settings.depth_buffer > 0,
45                        stencil: settings.stencil_buffer > 0,
46                        willReadFrequently: match settings.hardware_acceleration {
47                            HardwareAcceleration::Required => false,
48                            HardwareAcceleration::Preferred => false,
49                            HardwareAcceleration::Off => true,
50                        },
51                        alpha: false,
52                    })
53                    .unwrap(),
54                )
55                .map_err(|e| WindowError::WebGL2NotSupported(format!(": {:?}", e)))?
56                .ok_or(WindowError::WebGL2NotSupported("".to_string()))?
57                .dyn_into::<web_sys::WebGl2RenderingContext>()
58                .map_err(|e| WindowError::WebGL2NotSupported(format!(": {:?}", e)))?;
59            webgl_context
60                .get_extension("EXT_color_buffer_float")
61                .map_err(|e| WindowError::ColorBufferFloatNotSupported(format!("{:?}", e)))?;
62            webgl_context
63                .get_extension("OES_texture_float_linear")
64                .map_err(|e| WindowError::OESTextureFloatNotSupported(format!(": {:?}", e)))?;
65            webgl_context
66                .get_extension("OES_texture_half_float_linear")
67                .map_err(|e| WindowError::OESTextureFloatNotSupported(format!(": {:?}", e)))?;
68
69            Ok(Self {
70                context: Context::from_gl_context(Arc::new(
71                    crate::context::Context::from_webgl2_context(webgl_context),
72                ))?,
73            })
74        }
75
76        /// Resizes the context
77        pub fn resize(&self, _physical_size: winit::dpi::PhysicalSize<u32>) {}
78
79        /// Make this context current. Needed when using multiple windows (contexts) on native.
80        pub fn make_current(&self) -> Result<(), WindowError> {
81            Ok(())
82        }
83
84        /// Swap buffers - should always be called after rendering.
85        pub fn swap_buffers(&self) -> Result<(), WindowError> {
86            Ok(())
87        }
88    }
89}
90
91#[cfg(not(target_arch = "wasm32"))]
92mod inner {
93    use glutin::{prelude::PossiblyCurrentContextGlSurfaceAccessor, surface::*};
94
95    use super::*;
96    ///
97    /// A windowed graphics context, ie. a graphics context that is associated with a window.
98    /// For a graphics context that is not associated with a window, see [HeadlessContext](crate::HeadlessContext).
99    ///
100    pub struct WindowedContext {
101        pub(super) context: Context,
102        surface: Surface<WindowSurface>,
103        glutin_context: glutin::context::PossiblyCurrentContext,
104    }
105
106    impl WindowedContext {
107        /// Creates a new windowed context from a [winit](https://crates.io/crates/winit) window.
108        #[allow(unsafe_code)]
109        pub fn from_winit_window(
110            window: &Window,
111            settings: SurfaceSettings,
112        ) -> Result<Self, WindowError> {
113            if settings.multisamples > 0 && !settings.multisamples.is_power_of_two() {
114                Err(WindowError::InvalidNumberOfMSAASamples)?;
115            }
116            use glutin::prelude::*;
117            use raw_window_handle::*;
118            let raw_display_handle = window.raw_display_handle();
119            let raw_window_handle = window.raw_window_handle();
120
121            // EGL is crossplatform and the official khronos way
122            // but sometimes platforms/drivers may not have it, so we use back up options
123            // where possible. TODO: check whether we can expose these options as
124            // "features", so that users can select the relevant backend they want.
125
126            // try egl and fallback to windows wgl. Windows is the only platform that
127            // *requires* window handle to create display.
128            #[cfg(target_os = "windows")]
129            let preference =
130                glutin::display::DisplayApiPreference::WglThenEgl(Some(raw_window_handle));
131            // try egl and fallback to x11 glx
132            #[cfg(target_os = "linux")]
133            let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
134                winit::platform::x11::register_xlib_error_hook,
135            ));
136            #[cfg(target_os = "macos")]
137            let preference = glutin::display::DisplayApiPreference::Cgl;
138            #[cfg(target_os = "android")]
139            let preference = glutin::display::DisplayApiPreference::Egl;
140
141            let gl_display =
142                unsafe { glutin::display::Display::new(raw_display_handle, preference)? };
143            let swap_interval = if settings.vsync {
144                glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
145            } else {
146                glutin::surface::SwapInterval::DontWait
147            };
148
149            let hardware_acceleration = match settings.hardware_acceleration {
150                crate::HardwareAcceleration::Required => Some(true),
151                crate::HardwareAcceleration::Preferred => None,
152                crate::HardwareAcceleration::Off => Some(false),
153            };
154            let config_template = glutin::config::ConfigTemplateBuilder::new()
155                .prefer_hardware_accelerated(hardware_acceleration)
156                .with_depth_size(settings.depth_buffer);
157            // we don't know if multi sampling option is set. so, check if its more than 0.
158            let config_template = if settings.multisamples > 0 {
159                config_template.with_multisampling(settings.multisamples)
160            } else {
161                config_template
162            };
163            let config_template = config_template
164                .with_stencil_size(settings.stencil_buffer)
165                .compatible_with_native_window(raw_window_handle)
166                .build();
167            // finds all valid configurations supported by this display that match the
168            // config_template this is where we will try to get a "fallback" config if
169            // we are okay with ignoring some native options required by user like multi
170            // sampling, srgb, transparency etc..
171            let config = unsafe {
172                gl_display
173                    .find_configs(config_template)?
174                    .next()
175                    .ok_or(WindowError::SurfaceCreationError)?
176            };
177
178            let context_attributes =
179                glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
180            // for surface creation.
181            let (width, height): (u32, u32) = window.inner_size().into();
182            let width = std::num::NonZeroU32::new(width.max(1)).unwrap();
183            let height = std::num::NonZeroU32::new(height.max(1)).unwrap();
184            let surface_attributes =
185                glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
186                    .build(raw_window_handle, width, height);
187            // start creating the gl objects
188            let gl_context = unsafe { gl_display.create_context(&config, &context_attributes)? };
189
190            let gl_surface =
191                unsafe { gl_display.create_window_surface(&config, &surface_attributes)? };
192            let gl_context = gl_context.make_current(&gl_surface)?;
193            gl_surface.set_swap_interval(&gl_context, swap_interval)?;
194
195            Ok(Self {
196                context: Context::from_gl_context(Arc::new(unsafe {
197                    crate::context::Context::from_loader_function(|s| {
198                        let s = std::ffi::CString::new(s)
199                            .expect("failed to construct C string from string for gl proc address");
200
201                        gl_display.get_proc_address(&s)
202                    })
203                }))?,
204                glutin_context: gl_context,
205                surface: gl_surface,
206            })
207        }
208
209        /// Resizes the context
210        pub fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
211            let width = std::num::NonZeroU32::new(physical_size.width.max(1)).unwrap();
212            let height = std::num::NonZeroU32::new(physical_size.height.max(1)).unwrap();
213            self.surface.resize(&self.glutin_context, width, height);
214        }
215
216        /// Make this context current. Needed when using multiple windows (contexts) on native.
217        pub fn make_current(&self) -> Result<(), WindowError> {
218            Ok(self.glutin_context.make_current(&self.surface)?)
219        }
220
221        /// Swap buffers - should always be called after rendering.
222        pub fn swap_buffers(&self) -> Result<(), WindowError> {
223            Ok(self.surface.swap_buffers(&self.glutin_context)?)
224        }
225
226        /// Enables or disabled vsync.
227        pub fn set_vsync(&self, enabled: bool) -> Result<(), WindowError> {
228            let swap_interval = if enabled {
229                glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
230            } else {
231                glutin::surface::SwapInterval::DontWait
232            };
233            Ok(self
234                .surface
235                .set_swap_interval(&self.glutin_context, swap_interval)?)
236        }
237    }
238}
239
240pub use inner::*;
241
242impl std::ops::Deref for WindowedContext {
243    type Target = Context;
244
245    fn deref(&self) -> &Self::Target {
246        &self.context
247    }
248}