freya-winit 0.4.1

Winit renderer for Freya
Documentation
use std::{
    ffi::CString,
    num::NonZeroU32,
};

use freya_engine::prelude::{
    ColorType,
    DirectContext,
    Format,
    FramebufferInfo,
    Interface,
    Surface as SkiaSurface,
    SurfaceOrigin,
    backend_render_targets,
    direct_contexts,
    wrap_backend_render_target,
};
use gl::{
    types::*,
    *,
};
use glutin::{
    config::{
        ConfigTemplateBuilder,
        GlConfig,
    },
    context::{
        ContextApi,
        ContextAttributesBuilder,
        GlProfile,
        NotCurrentGlContext,
        PossiblyCurrentContext,
    },
    display::{
        GetGlDisplay,
        GlDisplay,
    },
    prelude::{
        GlSurface,
        PossiblyCurrentGlContext,
    },
    surface::{
        Surface as GlutinSurface,
        SurfaceAttributesBuilder,
        SwapInterval,
        WindowSurface,
    },
};
use glutin_winit::DisplayBuilder;
use raw_window_handle::HasWindowHandle;
use winit::{
    dpi::PhysicalSize,
    event_loop::ActiveEventLoop,
    window::{
        Window,
        WindowAttributes,
    },
};

/// Graphics driver using OpenGL.
pub struct OpenGLDriver {
    pub(crate) gr_context: DirectContext,
    pub(crate) gl_surface: GlutinSurface<WindowSurface>,
    pub(crate) gl_context: PossiblyCurrentContext,
    pub(crate) fb_info: FramebufferInfo,
    pub(crate) num_samples: usize,
    pub(crate) stencil_size: usize,
    pub(crate) surface: SkiaSurface,
}

impl Drop for OpenGLDriver {
    fn drop(&mut self) {
        self.gr_context.abandon();
    }
}

impl OpenGLDriver {
    pub fn new(
        event_loop: &ActiveEventLoop,
        window_attributes: WindowAttributes,
        gpu_resource_cache_limit: usize,
    ) -> Result<(Self, Window), Box<dyn std::error::Error>> {
        let transparent = window_attributes.transparent;
        let template = ConfigTemplateBuilder::new()
            .with_alpha_size(8)
            .with_transparency(window_attributes.transparent);

        let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes));
        let (window, gl_config) = display_builder.build(event_loop, template, |configs| {
            configs
                .reduce(|accum, config| {
                    let transparency_check = transparent
                        && config.supports_transparency().unwrap_or(false)
                        && !accum.supports_transparency().unwrap_or(false);

                    if transparency_check || config.num_samples() < accum.num_samples() {
                        config
                    } else {
                        accum
                    }
                })
                .expect("at least one OpenGL config")
        })?;

        let window = window.ok_or("OpenGL display builder returned no window")?;

        let window_handle = window.window_handle()?;

        let context_attributes = ContextAttributesBuilder::new()
            .with_profile(GlProfile::Core)
            .build(Some(window_handle.as_raw()));

        let fallback_context_attributes = ContextAttributesBuilder::new()
            .with_profile(GlProfile::Core)
            .with_context_api(ContextApi::Gles(None))
            .build(Some(window_handle.as_raw()));

        let not_current_gl_context = unsafe {
            match gl_config
                .display()
                .create_context(&gl_config, &context_attributes)
            {
                Ok(ctx) => ctx,
                Err(_) => gl_config
                    .display()
                    .create_context(&gl_config, &fallback_context_attributes)?,
            }
        };

        let size = window.inner_size();

        let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
            window_handle.as_raw(),
            NonZeroU32::new(size.width).ok_or("OpenGL window has zero width")?,
            NonZeroU32::new(size.height).ok_or("OpenGL window has zero height")?,
        );

        let gl_surface = unsafe {
            gl_config
                .display()
                .create_window_surface(&gl_config, &attrs)?
        };

        let gl_context = not_current_gl_context.make_current(&gl_surface)?;

        // Try setting vsync.
        gl_surface
            .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
            .ok();

        load_with(|s| {
            gl_config
                .display()
                .get_proc_address(CString::new(s).unwrap().as_c_str())
        });
        let interface = Interface::new_load_with(|name| {
            if name == "eglGetCurrentDisplay" {
                return std::ptr::null();
            }
            gl_config
                .display()
                .get_proc_address(CString::new(name).unwrap().as_c_str())
        })
        .ok_or("could not create OpenGL interface")?;

        let fb_info = {
            let mut fboid: GLint = 0;
            unsafe { GetIntegerv(FRAMEBUFFER_BINDING, &mut fboid) };

            FramebufferInfo {
                fboid: fboid.try_into()?,
                format: Format::RGBA8.into(),
                ..Default::default()
            }
        };

        let num_samples = gl_config.num_samples() as usize;
        let stencil_size = gl_config.stencil_size() as usize;

        let mut gr_context = direct_contexts::make_gl(interface, None)
            .ok_or("could not create OpenGL direct context")?;

        gr_context.set_resource_cache_limit(gpu_resource_cache_limit);

        let render_target = backend_render_targets::make_gl(
            (size.width as i32, size.height as i32),
            num_samples,
            stencil_size,
            fb_info,
        );
        let surface = wrap_backend_render_target(
            &mut gr_context,
            &render_target,
            SurfaceOrigin::BottomLeft,
            ColorType::RGBA8888,
            None,
            None,
        )
        .ok_or("could not create OpenGL skia surface")?;

        let driver = OpenGLDriver {
            gl_context,
            gl_surface,
            gr_context,
            num_samples,
            stencil_size,
            fb_info,
            surface,
        };

        Ok((driver, window))
    }

    pub fn present(&mut self, window: &Window, render: impl FnOnce(&mut SkiaSurface)) {
        if !self.gl_context.is_current() {
            self.gl_context.make_current(&self.gl_surface).unwrap();
        }

        render(&mut self.surface);

        window.pre_present_notify();
        self.gr_context.flush_submit_and_sync_cpu();
        if let Err(error) = self.gl_surface.swap_buffers(&self.gl_context) {
            tracing::error!("Failed to swap buffers: {:?}", error);
        }
    }

    pub fn resize(&mut self, size: PhysicalSize<u32>) {
        let render_target = backend_render_targets::make_gl(
            (size.width as i32, size.height as i32),
            self.num_samples,
            self.stencil_size,
            self.fb_info,
        );
        let surface = wrap_backend_render_target(
            &mut self.gr_context,
            &render_target,
            SurfaceOrigin::BottomLeft,
            ColorType::RGBA8888,
            None,
            None,
        )
        .expect("Could not create skia surface");

        self.gl_surface.resize(
            &self.gl_context,
            NonZeroU32::new(size.width.max(1)).unwrap(),
            NonZeroU32::new(size.height.max(1)).unwrap(),
        );

        self.surface = surface;
    }
}