Skip to main content

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    ///
99    pub struct WindowedContext {
100        pub(super) context: Context,
101        surface: Surface<WindowSurface>,
102        glutin_context: glutin::context::PossiblyCurrentContext,
103    }
104
105    impl WindowedContext {
106        /// Creates a new windowed context from a [winit](https://crates.io/crates/winit) window.
107        #[allow(unsafe_code)]
108        pub fn from_winit_window(
109            window: &Window,
110            settings: SurfaceSettings,
111        ) -> Result<Self, WindowError> {
112            if settings.multisamples > 0 && !settings.multisamples.is_power_of_two() {
113                Err(WindowError::InvalidNumberOfMSAASamples)?;
114            }
115            use glutin::prelude::*;
116            use raw_window_handle::*;
117            let raw_display_handle = window.raw_display_handle();
118            let raw_window_handle = window.raw_window_handle();
119
120            // EGL is crossplatform and the official khronos way
121            // but sometimes platforms/drivers may not have it, so we use back up options
122            // where possible. TODO: check whether we can expose these options as
123            // "features", so that users can select the relevant backend they want.
124
125            // try egl and fallback to windows wgl. Windows is the only platform that
126            // *requires* window handle to create display.
127            #[cfg(target_os = "windows")]
128            let preference =
129                glutin::display::DisplayApiPreference::WglThenEgl(Some(raw_window_handle));
130            // try egl and fallback to x11 glx
131            #[cfg(target_os = "linux")]
132            let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
133                winit::platform::x11::register_xlib_error_hook,
134            ));
135            #[cfg(target_os = "macos")]
136            let preference = glutin::display::DisplayApiPreference::Cgl;
137            #[cfg(target_os = "android")]
138            let preference = glutin::display::DisplayApiPreference::Egl;
139
140            let gl_display =
141                unsafe { glutin::display::Display::new(raw_display_handle, preference)? };
142            let swap_interval = if settings.vsync {
143                glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
144            } else {
145                glutin::surface::SwapInterval::DontWait
146            };
147
148            let hardware_acceleration = match settings.hardware_acceleration {
149                crate::HardwareAcceleration::Required => Some(true),
150                crate::HardwareAcceleration::Preferred => None,
151                crate::HardwareAcceleration::Off => Some(false),
152            };
153            let config_template = glutin::config::ConfigTemplateBuilder::new()
154                .prefer_hardware_accelerated(hardware_acceleration)
155                .with_depth_size(settings.depth_buffer);
156            // we don't know if multi sampling option is set. so, check if its more than 0.
157            let config_template = if settings.multisamples > 0 {
158                config_template.with_multisampling(settings.multisamples)
159            } else {
160                config_template
161            };
162            let config_template = config_template
163                .with_stencil_size(settings.stencil_buffer)
164                .compatible_with_native_window(raw_window_handle)
165                .build();
166            // finds all valid configurations supported by this display that match the
167            // config_template this is where we will try to get a "fallback" config if
168            // we are okay with ignoring some native options required by user like multi
169            // sampling, srgb, transparency etc..
170            let config = unsafe {
171                gl_display
172                    .find_configs(config_template)?
173                    .next()
174                    .ok_or(WindowError::SurfaceCreationError)?
175            };
176
177            let context_attributes =
178                glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
179            // for surface creation.
180            let (width, height): (u32, u32) = window.inner_size().into();
181            let width = std::num::NonZeroU32::new(width.max(1)).unwrap();
182            let height = std::num::NonZeroU32::new(height.max(1)).unwrap();
183            let surface_attributes =
184                glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
185                    .build(raw_window_handle, width, height);
186            // start creating the gl objects
187            let gl_context = unsafe { gl_display.create_context(&config, &context_attributes)? };
188
189            let gl_surface =
190                unsafe { gl_display.create_window_surface(&config, &surface_attributes)? };
191            let gl_context = gl_context.make_current(&gl_surface)?;
192            gl_surface.set_swap_interval(&gl_context, swap_interval)?;
193
194            Ok(Self {
195                context: Context::from_gl_context(Arc::new(unsafe {
196                    crate::context::Context::from_loader_function(|s| {
197                        let s = std::ffi::CString::new(s)
198                            .expect("failed to construct C string from string for gl proc address");
199
200                        gl_display.get_proc_address(&s)
201                    })
202                }))?,
203                glutin_context: gl_context,
204                surface: gl_surface,
205            })
206        }
207
208        /// Resizes the context
209        pub fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
210            let width = std::num::NonZeroU32::new(physical_size.width.max(1)).unwrap();
211            let height = std::num::NonZeroU32::new(physical_size.height.max(1)).unwrap();
212            self.surface.resize(&self.glutin_context, width, height);
213        }
214
215        /// Make this context current. Needed when using multiple windows (contexts) on native.
216        pub fn make_current(&self) -> Result<(), WindowError> {
217            Ok(self.glutin_context.make_current(&self.surface)?)
218        }
219
220        /// Swap buffers - should always be called after rendering.
221        pub fn swap_buffers(&self) -> Result<(), WindowError> {
222            Ok(self.surface.swap_buffers(&self.glutin_context)?)
223        }
224
225        /// Enables or disabled vsync.
226        pub fn set_vsync(&self, enabled: bool) -> Result<(), WindowError> {
227            let swap_interval = if enabled {
228                glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
229            } else {
230                glutin::surface::SwapInterval::DontWait
231            };
232            Ok(self
233                .surface
234                .set_swap_interval(&self.glutin_context, swap_interval)?)
235        }
236    }
237}
238
239pub use inner::*;
240
241impl std::ops::Deref for WindowedContext {
242    type Target = Context;
243
244    fn deref(&self) -> &Self::Target {
245        &self.context
246    }
247}