mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-30 23:27:22 +00:00

- This allows creating the Skia GL interface without string allocations - Port the item renderer to use SkImageFilters::Shader instead of SkImageFilter::Paint
296 lines
10 KiB
Rust
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, ¤t_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, ¤t_context, gr_context, size);
|
|
}
|
|
|
|
let skia_canvas = surface.canvas();
|
|
|
|
callback(skia_canvas, gr_context);
|
|
|
|
self.glutin_surface.swap_buffers(¤t_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();
|
|
}
|
|
}
|