slint/internal/renderers/skia/opengl_surface.rs
Simon Hausmann c14ec0fb9c Update Skia
- This allows creating the Skia GL interface without string allocations
- Port the item renderer to use SkImageFilters::Shader instead of
  SkImageFilter::Paint
2023-03-21 09:36:39 +01:00

296 lines
10 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
use std::cell::RefCell;
use glutin::{
config::GetGlConfig,
context::{ContextApi, ContextAttributesBuilder},
display::GetGlDisplay,
prelude::*,
surface::{SurfaceAttributesBuilder, WindowSurface},
};
use i_slint_core::api::GraphicsAPI;
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
pub struct OpenGLSurface {
fb_info: skia_safe::gpu::gl::FramebufferInfo,
surface: RefCell<skia_safe::Surface>,
gr_context: RefCell<skia_safe::gpu::DirectContext>,
glutin_context: glutin::context::PossiblyCurrentContext,
glutin_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
}
impl super::Surface for OpenGLSurface {
const SUPPORTS_GRAPHICS_API: bool = true;
fn new(
window: &dyn raw_window_handle::HasRawWindowHandle,
display: &dyn raw_window_handle::HasRawDisplayHandle,
size: PhysicalWindowSize,
) -> Self {
let (current_glutin_context, glutin_surface) = Self::init_glutin(window, display, size);
let fb_info = {
use glow::HasContext;
let gl = unsafe {
glow::Context::from_loader_function_cstr(|name| {
current_glutin_context.display().get_proc_address(name) as *const _
})
};
let fboid = unsafe { gl.get_parameter_i32(glow::FRAMEBUFFER_BINDING) };
skia_safe::gpu::gl::FramebufferInfo {
fboid: fboid.try_into().unwrap(),
format: skia_safe::gpu::gl::Format::RGBA8.into(),
}
};
let gl_interface = skia_safe::gpu::gl::Interface::new_load_with_cstr(|name| {
current_glutin_context.display().get_proc_address(name) as *const _
});
let mut gr_context = skia_safe::gpu::DirectContext::new_gl(gl_interface, None).unwrap();
let surface =
Self::create_internal_surface(fb_info, &current_glutin_context, &mut gr_context, size)
.into();
Self {
fb_info,
surface,
gr_context: RefCell::new(gr_context),
glutin_context: current_glutin_context,
glutin_surface,
}
}
fn name(&self) -> &'static str {
"opengl"
}
fn with_graphics_api(&self, callback: impl FnOnce(GraphicsAPI<'_>)) {
let api = GraphicsAPI::NativeOpenGL {
get_proc_address: &|name| {
self.glutin_context.display().get_proc_address(name) as *const _
},
};
callback(api)
}
fn with_active_surface(&self, callback: impl FnOnce()) {
self.ensure_context_current();
callback();
}
fn render(
&self,
size: PhysicalWindowSize,
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
) {
let width = size.width;
let height = size.height;
self.ensure_context_current();
let current_context = &self.glutin_context;
let gr_context = &mut self.gr_context.borrow_mut();
let mut surface = self.surface.borrow_mut();
if width != surface.width() as u32 || height != surface.height() as u32 {
*surface =
Self::create_internal_surface(self.fb_info, &current_context, gr_context, size);
}
let skia_canvas = surface.canvas();
callback(skia_canvas, gr_context);
self.glutin_surface.swap_buffers(&current_context).unwrap();
}
fn resize_event(&self, size: PhysicalWindowSize) {
self.ensure_context_current();
self.glutin_surface.resize(
&self.glutin_context,
size.width.try_into().unwrap(),
size.height.try_into().unwrap(),
);
}
fn bits_per_pixel(&self) -> u8 {
let config = self.glutin_context.config();
let rgb_bits = match config.color_buffer_type() {
Some(glutin::config::ColorBufferType::Rgb { r_size, g_size, b_size }) => {
r_size + g_size + b_size
}
_ => panic!("unsupported color buffer used with Skia OpenGL renderer"),
};
rgb_bits + config.alpha_size()
}
}
impl OpenGLSurface {
fn init_glutin(
_window: &dyn raw_window_handle::HasRawWindowHandle,
_display: &dyn raw_window_handle::HasRawDisplayHandle,
_size: PhysicalWindowSize,
) -> (
glutin::context::PossiblyCurrentContext,
glutin::surface::Surface<glutin::surface::WindowSurface>,
) {
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let prefs = [glutin::display::DisplayApiPreference::Cgl];
} else if #[cfg(all(feature = "x11", not(target_family = "windows")))] {
let prefs = [glutin::display::DisplayApiPreference::Egl, glutin::display::DisplayApiPreference::Glx(Box::new(winit::platform::x11::register_xlib_error_hook))];
} else if #[cfg(not(target_family = "windows"))] {
let prefs = [glutin::display::DisplayApiPreference::Egl];
} else {
let prefs = [glutin::display::DisplayApiPreference::EglThenWgl(Some(_window.raw_window_handle()))];
}
}
let width: std::num::NonZeroU32 =
_size.width.try_into().expect("new context called with zero width window");
let height: std::num::NonZeroU32 =
_size.height.try_into().expect("new context called with zero height window");
let try_create_surface =
|display_api_preference| -> Result<(_, _), Box<dyn std::error::Error>> {
let gl_display = unsafe {
glutin::display::Display::new(
_display.raw_display_handle(),
display_api_preference,
)?
};
let config_template_builder = glutin::config::ConfigTemplateBuilder::new();
// Upstream advises to use this only on Windows.
#[cfg(target_family = "windows")]
let config_template_builder = config_template_builder
.compatible_with_native_window(_window.raw_window_handle());
let config_template = config_template_builder.build();
let config = unsafe {
gl_display
.find_configs(config_template)?
.reduce(|accum, config| {
let transparency_check =
config.supports_transparency().unwrap_or(false)
& !accum.supports_transparency().unwrap_or(false);
if transparency_check || config.num_samples() < accum.num_samples() {
config
} else {
accum
}
})
.ok_or("Unable to find suitable GL config")?
};
let gles_context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::Gles(Some(glutin::context::Version {
major: 2,
minor: 0,
})))
.build(Some(_window.raw_window_handle()));
let fallback_context_attributes =
ContextAttributesBuilder::new().build(Some(_window.raw_window_handle()));
let not_current_gl_context = unsafe {
gl_display.create_context(&config, &gles_context_attributes).or_else(|_| {
gl_display.create_context(&config, &fallback_context_attributes)
})?
};
let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
_window.raw_window_handle(),
width,
height,
);
let surface = unsafe { config.display().create_window_surface(&config, &attrs)? };
Ok((surface, not_current_gl_context))
};
let num_prefs = prefs.len();
let (surface, not_current_gl_context) = prefs
.into_iter()
.enumerate()
.find_map(|(i, pref)| {
let is_last = i == num_prefs - 1;
match try_create_surface(pref) {
Ok(result) => Some(result),
Err(glutin_error) => {
if is_last {
panic!("Glutin error creating GL surface: {}", glutin_error);
}
None
}
}
})
.unwrap();
#[cfg(target_os = "macos")]
if let raw_window_handle::RawWindowHandle::AppKit(raw_window_handle::AppKitWindowHandle {
ns_view,
..
}) = _window.raw_window_handle()
{
use cocoa::appkit::NSView;
let view_id: cocoa::base::id = ns_view as *const _ as *mut _;
unsafe {
NSView::setLayerContentsPlacement(view_id, cocoa::appkit::NSViewLayerContentsPlacement::NSViewLayerContentsPlacementTopLeft)
}
}
(not_current_gl_context.make_current(&surface).unwrap(), surface)
}
fn create_internal_surface(
fb_info: skia_safe::gpu::gl::FramebufferInfo,
gl_context: &glutin::context::PossiblyCurrentContext,
gr_context: &mut skia_safe::gpu::DirectContext,
size: PhysicalWindowSize,
) -> skia_safe::Surface {
let config = gl_context.config();
let backend_render_target = skia_safe::gpu::BackendRenderTarget::new_gl(
(size.width.try_into().unwrap(), size.height.try_into().unwrap()),
Some(config.num_samples() as _),
config.stencil_size() as _,
fb_info,
);
let surface = skia_safe::Surface::from_backend_render_target(
gr_context,
&backend_render_target,
skia_safe::gpu::SurfaceOrigin::BottomLeft,
skia_safe::ColorType::RGBA8888,
None,
None,
)
.unwrap();
surface
}
fn ensure_context_current(&self) {
if !self.glutin_context.is_current() {
self.glutin_context.make_current(&self.glutin_surface).unwrap();
}
}
}
impl Drop for OpenGLSurface {
fn drop(&mut self) {
// Make sure that the context is current before Skia calls glDelete***
self.ensure_context_current();
}
}