#![allow(unused)]
#[cfg(all(feature = "skia-native", unix))]
mod unix_impl {
use std::os::raw::c_void;
use std::ptr;
use glow::HasContext;
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use skia_safe as sk;
use velox_dom::VeloxError;
pub struct SkiaGlContext {
pub egl_display: egl::EGLDisplay,
pub egl_context: egl::EGLContext,
pub egl_surface: egl::EGLSurface,
pub interface: Option<skia_safe::gpu::gl::Interface>,
}
pub struct GlDirectContext {
dctx: skia_safe::gpu::DirectContext,
gl: SkiaGlContext,
}
impl GlDirectContext {
pub fn dctx(&self) -> &skia_safe::gpu::DirectContext {
&self.dctx
}
pub fn dctx_mut(&mut self) -> &mut skia_safe::gpu::DirectContext {
&mut self.dctx
}
pub fn gl(&self) -> &SkiaGlContext {
&self.gl
}
pub fn gl_mut(&mut self) -> &mut SkiaGlContext {
&mut self.gl
}
}
impl SkiaGlContext {
pub fn into_direct_context(self) -> Option<GlDirectContext> {
let iface = match &self.interface {
Some(i) => i,
None => return None,
};
let dctx = skia_safe::gpu::direct_contexts::make_gl(iface, None)?;
Some(GlDirectContext { dctx, gl: self })
}
pub fn make_current(&self) -> Result<(), VeloxError> {
if egl::make_current(
self.egl_display,
self.egl_surface,
self.egl_surface,
self.egl_context,
) {
Ok(())
} else {
Err(VeloxError::Render("egl: make_current failed".into()))
}
}
}
impl Drop for SkiaGlContext {
fn drop(&mut self) {
let _ = egl::make_current(
self.egl_display,
egl::EGL_NO_SURFACE,
egl::EGL_NO_SURFACE,
egl::EGL_NO_CONTEXT,
);
egl::destroy_surface(self.egl_display, self.egl_surface);
egl::destroy_context(self.egl_display, self.egl_context);
egl::terminate(self.egl_display);
}
}
fn choose_egl_config(dpy: egl::EGLDisplay) -> Option<egl::EGLConfig> {
let attribs: &[egl::EGLint] = &[
egl::EGL_RED_SIZE as egl::EGLint,
8,
egl::EGL_GREEN_SIZE as egl::EGLint,
8,
egl::EGL_BLUE_SIZE as egl::EGLint,
8,
egl::EGL_ALPHA_SIZE as egl::EGLint,
8,
egl::EGL_DEPTH_SIZE as egl::EGLint,
24,
egl::EGL_STENCIL_SIZE as egl::EGLint,
8,
egl::EGL_NONE as egl::EGLint,
];
egl::choose_config(dpy, attribs, 1)
}
pub fn create_context_from_winit(
window: &impl HasRawWindowHandle,
) -> Result<SkiaGlContext, String> {
let _raw = window.raw_window_handle();
let display = egl::get_display(egl::EGL_DEFAULT_DISPLAY).ok_or_else(|| {
let msg = "egl: no display".to_string();
log::error!("{}", msg);
msg
})?;
let mut major: egl::EGLint = 0;
let mut minor: egl::EGLint = 0;
if !egl::initialize(display, &mut major, &mut minor) {
log::error!(
"egl: failed to initialize (major={}, minor={})",
major,
minor
);
return Err("egl: failed to initialize".into());
}
let config = choose_egl_config(display).ok_or_else(|| {
let msg = "egl: no config".to_string();
log::error!("{}", msg);
msg
})?;
let ctx_attribs: &[egl::EGLint] = &[
egl::EGL_CONTEXT_CLIENT_VERSION as egl::EGLint,
2,
egl::EGL_NONE as egl::EGLint,
];
let context = egl::create_context(display, config, egl::EGL_NO_CONTEXT, ctx_attribs)
.ok_or_else(|| "egl: failed to create context".to_string())?;
let pbuffer_attribs: &[egl::EGLint] = &[
egl::EGL_WIDTH as egl::EGLint,
1,
egl::EGL_HEIGHT as egl::EGLint,
1,
egl::EGL_NONE as egl::EGLint,
];
let surface = egl::create_pbuffer_surface(display, config, pbuffer_attribs)
.ok_or_else(|| "egl: failed to create pbuffer surface".to_string())?;
if !egl::make_current(display, surface, surface, context) {
egl::destroy_surface(display, surface);
egl::destroy_context(display, context);
egl::terminate(display);
return Err("egl: make_current failed".into());
}
let interface = unsafe {
skia_safe::gpu::gl::Interface::new_load_with(|name: &str| {
let f = egl::get_proc_address(name);
f as *const _
})
};
let iface = match interface {
Some(i) => i,
None => return Err("skia: failed to create GL interface".into()),
};
Ok(SkiaGlContext {
egl_display: display,
egl_context: context,
egl_surface: surface,
interface: Some(iface),
})
}
pub fn create_headless_context() -> Result<SkiaGlContext, String> {
let display = egl::get_display(egl::EGL_DEFAULT_DISPLAY).ok_or_else(|| {
let msg = "egl: no display".to_string();
log::error!("{}", msg);
msg
})?;
let mut major: egl::EGLint = 0;
let mut minor: egl::EGLint = 0;
if !egl::initialize(display, &mut major, &mut minor) {
log::error!(
"egl: failed to initialize (major={}, minor={})",
major,
minor
);
return Err("egl: failed to initialize".into());
}
let config = choose_egl_config(display).ok_or_else(|| {
let msg = "egl: no config".to_string();
log::error!("{}", msg);
msg
})?;
let ctx_attribs: &[egl::EGLint] = &[
egl::EGL_CONTEXT_CLIENT_VERSION as egl::EGLint,
2,
egl::EGL_NONE as egl::EGLint,
];
let context = egl::create_context(display, config, egl::EGL_NO_CONTEXT, ctx_attribs)
.ok_or_else(|| "egl: failed to create context".to_string())?;
let pbuffer_attribs: &[egl::EGLint] = &[
egl::EGL_WIDTH as egl::EGLint,
1,
egl::EGL_HEIGHT as egl::EGLint,
1,
egl::EGL_NONE as egl::EGLint,
];
let surface = egl::create_pbuffer_surface(display, config, pbuffer_attribs)
.ok_or_else(|| "egl: failed to create pbuffer surface".to_string())?;
if !egl::make_current(display, surface, surface, context) {
egl::destroy_surface(display, surface);
egl::destroy_context(display, context);
egl::terminate(display);
return Err("egl: make_current failed".into());
}
let interface = unsafe {
skia_safe::gpu::gl::Interface::new_load_with(|name: &str| {
let f = egl::get_proc_address(name);
f as *const _
})
};
let iface = match interface {
Some(i) => i,
None => return Err("skia: failed to create GL interface".into()),
};
Ok(SkiaGlContext {
egl_display: display,
egl_context: context,
egl_surface: surface,
interface: Some(iface),
})
}
pub fn draw_test_frame() -> Result<(), String> {
let gpu = create_headless_context()?.into_direct_context();
let mut surface = skia_safe::surfaces::raster_n32_premul((64, 64))
.ok_or_else(|| "skia: failed to create raster surface".to_string())?;
let canvas = surface.canvas();
canvas.clear(skia_safe::Color::WHITE);
let mut paint = skia_safe::Paint::default();
paint.set_color(skia_safe::Color::from_argb(255, 0, 128, 255));
paint.set_anti_alias(true);
let r = skia_safe::Rect::from_xywh(8.0, 8.0, 48.0, 48.0);
canvas.draw_rect(r, &paint);
let _ = surface.image_snapshot();
drop(gpu);
Ok(())
}
pub fn draw_gpu_test_frame(width: i32, height: i32) -> Result<(), String> {
let gl_ctx = create_headless_context()?;
let _ = gl_ctx.make_current();
let mut owned = gl_ctx
.into_direct_context()
.ok_or_else(|| "skia: could not create DirectContext".to_string())?;
let mut surface = {
let gl = unsafe {
glow::Context::from_loader_function(|s| egl::get_proc_address(s) as *const _)
};
let fb_binding = unsafe { gl.get_parameter_i32(glow::FRAMEBUFFER_BINDING) } as u32;
let fb_info = skia_safe::gpu::gl::FramebufferInfo {
fboid: fb_binding,
format: glow::RGBA8,
protected: skia_safe::gpu::Protected::No,
};
let backend =
skia_safe::gpu::backend_render_targets::make_gl((width, height), 0, 8, fb_info);
skia_safe::gpu::surfaces::wrap_backend_render_target(
owned.dctx_mut(),
&backend,
skia_safe::gpu::SurfaceOrigin::BottomLeft,
skia_safe::ColorType::RGBA8888,
None,
None,
)
};
if surface.is_none() {
log::warn!("GPU surface creation failed; falling back to raster");
surface = skia_safe::surfaces::raster_n32_premul((width, height));
}
let mut surface = surface.ok_or_else(|| "skia: failed to create surface".to_string())?;
let canvas = surface.canvas();
canvas.clear(skia_safe::Color::WHITE);
let mut paint = skia_safe::Paint::default();
paint.set_color(skia_safe::Color::from_argb(255, 200, 64, 64));
paint.set_anti_alias(true);
let r = skia_safe::Rect::from_xywh(4.0, 4.0, (width - 8) as f32, (height - 8) as f32);
canvas.draw_rect(r, &paint);
let _img = surface.image_snapshot();
owned.dctx_mut().flush_and_submit();
Ok(())
}
#[cfg(test)]
mod tests {
use super::{GlDirectContext, SkiaGlContext};
fn ownership_only_api(ctx: SkiaGlContext) -> Option<GlDirectContext> {
ctx.into_direct_context()
}
#[test]
fn direct_context_can_only_be_obtained_by_consuming_the_gl_context() {
let _f: fn(SkiaGlContext) -> Option<GlDirectContext> = ownership_only_api;
}
}
}
#[cfg(all(feature = "skia-native", unix))]
pub use unix_impl::*;
#[cfg(all(feature = "skia-native", unix))]
pub fn create_direct_context() -> Result<GlDirectContext, String> {
unix_impl::create_headless_context()?
.into_direct_context()
.ok_or_else(|| "skia: could not create DirectContext".to_string())
}
#[cfg(all(feature = "skia-native", not(unix)))]
pub use non_unix_stub::*;
#[cfg(all(feature = "skia-native", not(unix)))]
pub fn create_direct_context() -> Result<GlDirectContext, String> {
Err("skia: GPU path is not implemented for this target (non-unix)".into())
}
#[cfg(all(feature = "skia-native", not(unix)))]
mod non_unix_stub {
use skia_safe as sk;
const UNSUPPORTED: &str = "skia_gl: GPU context is not implemented for this target (non-unix: the EGL/GL path is unix-only)";
pub struct SkiaGlContext {
_private: (),
}
pub struct GlDirectContext {
dctx: sk::gpu::DirectContext,
gl: SkiaGlContext,
}
impl GlDirectContext {
pub fn dctx(&self) -> &sk::gpu::DirectContext {
&self.dctx
}
pub fn dctx_mut(&mut self) -> &mut sk::gpu::DirectContext {
&mut self.dctx
}
pub fn gl(&self) -> &SkiaGlContext {
&self.gl
}
pub fn gl_mut(&mut self) -> &mut SkiaGlContext {
&mut self.gl
}
}
impl SkiaGlContext {
pub fn into_direct_context(self) -> Option<GlDirectContext> {
None
}
pub fn make_current(&self) -> Result<(), velox_dom::VeloxError> {
Err(velox_dom::VeloxError::Render(UNSUPPORTED.into()))
}
}
pub fn create_context_from_winit(
_window: &impl raw_window_handle::HasRawWindowHandle,
) -> Result<SkiaGlContext, String> {
log::error!("{UNSUPPORTED}");
Err(UNSUPPORTED.into())
}
pub fn create_headless_context() -> Result<SkiaGlContext, String> {
Err(UNSUPPORTED.into())
}
pub fn create_context() -> Result<SkiaGlContext, String> {
Err(UNSUPPORTED.into())
}
pub fn draw_test_frame() -> Result<(), String> {
Err(UNSUPPORTED.into())
}
pub fn draw_gpu_test_frame(_width: i32, _height: i32) -> Result<(), String> {
Err(UNSUPPORTED.into())
}
}
#[cfg(all(test, feature = "skia-native", not(unix)))]
mod non_unix_tests {
use super::{SkiaGlContext, create_context, create_context_from_winit};
struct FakeWindow;
impl raw_window_handle::HasRawWindowHandle for FakeWindow {
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
raw_window_handle::RawWindowHandle::UiKit(raw_window_handle::UiKitWindowHandle::new())
}
}
#[test]
fn the_window_path_fails_with_a_message_instead_of_a_context() {
let err = create_context_from_winit(&FakeWindow)
.err()
.expect("a non-unix target must not hand back a GL context");
assert!(
err.contains("non-unix"),
"the error must name the cause, got: {err}"
);
}
#[test]
fn create_context_fails_and_never_reaches_a_direct_context() {
assert!(create_context().is_err());
let ctx = SkiaGlContext { _private: () };
assert!(ctx.into_direct_context().is_none());
}
}
#[cfg(all(feature = "skia-native", unix))]
pub fn create_context() -> Result<SkiaGlContext, String> {
unix_impl::create_headless_context()
}