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,
},
};
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)?;
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;
}
}