freya_winit/drivers/
gl.rs

1use std::{
2    ffi::CString,
3    num::NonZeroU32,
4};
5
6use freya_engine::prelude::{
7    ColorType,
8    DirectContext,
9    Format,
10    FramebufferInfo,
11    Interface,
12    Surface as SkiaSurface,
13    SurfaceOrigin,
14    backend_render_targets,
15    direct_contexts,
16    wrap_backend_render_target,
17};
18use gl::{
19    types::*,
20    *,
21};
22use glutin::{
23    config::{
24        ConfigTemplateBuilder,
25        GlConfig,
26    },
27    context::{
28        ContextApi,
29        ContextAttributesBuilder,
30        GlProfile,
31        NotCurrentGlContext,
32        PossiblyCurrentContext,
33    },
34    display::{
35        GetGlDisplay,
36        GlDisplay,
37    },
38    prelude::{
39        GlSurface,
40        PossiblyCurrentGlContext,
41    },
42    surface::{
43        Surface as GlutinSurface,
44        SurfaceAttributesBuilder,
45        SwapInterval,
46        WindowSurface,
47    },
48};
49use glutin_winit::DisplayBuilder;
50use raw_window_handle::HasWindowHandle;
51use winit::{
52    dpi::PhysicalSize,
53    event_loop::ActiveEventLoop,
54    window::{
55        Window,
56        WindowAttributes,
57    },
58};
59
60use crate::config::WindowConfig;
61
62/// Graphics driver using OpenGL.
63pub struct OpenGLDriver {
64    pub(crate) gr_context: DirectContext,
65    pub(crate) gl_surface: GlutinSurface<WindowSurface>,
66    pub(crate) gl_context: PossiblyCurrentContext,
67    pub(crate) fb_info: FramebufferInfo,
68    pub(crate) num_samples: usize,
69    pub(crate) stencil_size: usize,
70    pub(crate) surface: SkiaSurface,
71}
72
73impl Drop for OpenGLDriver {
74    fn drop(&mut self) {
75        self.gr_context.abandon();
76    }
77}
78
79impl OpenGLDriver {
80    pub fn new(
81        event_loop: &ActiveEventLoop,
82        window_attributes: WindowAttributes,
83        window_config: &WindowConfig,
84    ) -> (Self, Window) {
85        let template = ConfigTemplateBuilder::new()
86            .with_alpha_size(8)
87            .with_transparency(window_config.transparent);
88
89        let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes));
90        let (window, gl_config) = display_builder
91            .build(event_loop, template, |configs| {
92                configs
93                    .reduce(|accum, config| {
94                        let transparency_check = config.supports_transparency().unwrap_or(false)
95                            & !accum.supports_transparency().unwrap_or(false);
96
97                        if transparency_check || config.num_samples() < accum.num_samples() {
98                            config
99                        } else {
100                            accum
101                        }
102                    })
103                    .unwrap()
104            })
105            .unwrap();
106
107        let window = window.expect("Could not create window with OpenGL context");
108
109        let window_handle = window.window_handle().unwrap();
110
111        let context_attributes = ContextAttributesBuilder::new()
112            .with_profile(GlProfile::Core)
113            .build(Some(window_handle.as_raw()));
114
115        let fallback_context_attributes = ContextAttributesBuilder::new()
116            .with_profile(GlProfile::Core)
117            .with_context_api(ContextApi::Gles(None))
118            .build(Some(window_handle.as_raw()));
119
120        let not_current_gl_context = unsafe {
121            gl_config
122                .display()
123                .create_context(&gl_config, &context_attributes)
124                .unwrap_or_else(|_| {
125                    gl_config
126                        .display()
127                        .create_context(&gl_config, &fallback_context_attributes)
128                        .expect("failed to create context")
129                })
130        };
131
132        let size = window.inner_size();
133
134        let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
135            window_handle.as_raw(),
136            NonZeroU32::new(size.width).unwrap(),
137            NonZeroU32::new(size.height).unwrap(),
138        );
139
140        let gl_surface = unsafe {
141            gl_config
142                .display()
143                .create_window_surface(&gl_config, &attrs)
144                .expect("Could not create gl window surface")
145        };
146
147        let gl_context = not_current_gl_context
148            .make_current(&gl_surface)
149            .expect("Could not make GL context current when setting up skia renderer");
150
151        // Try setting vsync.
152        gl_surface
153            .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
154            .ok();
155
156        load_with(|s| {
157            gl_config
158                .display()
159                .get_proc_address(CString::new(s).unwrap().as_c_str())
160        });
161        let interface = Interface::new_load_with(|name| {
162            if name == "eglGetCurrentDisplay" {
163                return std::ptr::null();
164            }
165            gl_config
166                .display()
167                .get_proc_address(CString::new(name).unwrap().as_c_str())
168        })
169        .expect("Could not create interface");
170
171        let fb_info = {
172            let mut fboid: GLint = 0;
173            unsafe { GetIntegerv(FRAMEBUFFER_BINDING, &mut fboid) };
174
175            FramebufferInfo {
176                fboid: fboid.try_into().unwrap(),
177                format: Format::RGBA8.into(),
178                ..Default::default()
179            }
180        };
181
182        let num_samples = gl_config.num_samples() as usize;
183        let stencil_size = gl_config.stencil_size() as usize;
184
185        let mut gr_context =
186            direct_contexts::make_gl(interface, None).expect("Could not create direct context");
187
188        let render_target = backend_render_targets::make_gl(
189            (size.width as i32, size.height as i32),
190            num_samples,
191            stencil_size,
192            fb_info,
193        );
194        let surface = wrap_backend_render_target(
195            &mut gr_context,
196            &render_target,
197            SurfaceOrigin::BottomLeft,
198            ColorType::RGBA8888,
199            None,
200            None,
201        )
202        .expect("Could not create skia surface");
203
204        let driver = OpenGLDriver {
205            gl_context,
206            gl_surface,
207            gr_context,
208            num_samples,
209            stencil_size,
210            fb_info,
211            surface,
212        };
213
214        (driver, window)
215    }
216
217    pub fn present(&mut self, window: &Window, render: impl FnOnce(&mut SkiaSurface)) {
218        if !self.gl_context.is_current() {
219            self.gl_context.make_current(&self.gl_surface).unwrap();
220        }
221
222        render(&mut self.surface);
223
224        window.pre_present_notify();
225        self.gr_context.flush_submit_and_sync_cpu();
226        self.gl_surface.swap_buffers(&self.gl_context).unwrap();
227    }
228
229    pub fn resize(&mut self, size: PhysicalSize<u32>) {
230        let render_target = backend_render_targets::make_gl(
231            (size.width as i32, size.height as i32),
232            self.num_samples,
233            self.stencil_size,
234            self.fb_info,
235        );
236        let surface = wrap_backend_render_target(
237            &mut self.gr_context,
238            &render_target,
239            SurfaceOrigin::BottomLeft,
240            ColorType::RGBA8888,
241            None,
242            None,
243        )
244        .expect("Could not create skia surface");
245
246        self.gl_surface.resize(
247            &self.gl_context,
248            NonZeroU32::new(size.width.max(1)).unwrap(),
249            NonZeroU32::new(size.height.max(1)).unwrap(),
250        );
251
252        self.surface = surface;
253    }
254}