Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/anyrender_skia/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ mod vulkan;
pub use image_renderer::SkiaImageRenderer;
pub use scene::{SkiaSceneCache, SkiaScenePainter};
pub use window_renderer::*;

#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
pub use opengl::pick_x11_gl_visual;
159 changes: 130 additions & 29 deletions crates/anyrender_skia/src/opengl.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use std::{ffi::CString, num::NonZeroU32, sync::Arc};

use glutin::config::Config;
use glutin::display::DisplayApiPreference;
use glutin::{
config::{ConfigTemplateBuilder, GetGlConfig, GlConfig},
Expand All @@ -8,6 +11,7 @@ use glutin::{
prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext},
surface::{GlSurface, SurfaceAttributesBuilder, WindowSurface},
};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use skia_safe::{
Surface,
gpu::{
Expand All @@ -18,6 +22,120 @@ use skia_safe::{

use crate::window_renderer::SkiaBackend;

fn build_gl_display(
raw_display_handle: RawDisplayHandle,
_raw_window: Option<RawWindowHandle>,
) -> Display {
unsafe {
Display::new(
raw_display_handle,
#[cfg(any(target_os = "macos", target_os = "ios"))]
DisplayApiPreference::Cgl,
#[cfg(target_os = "windows")]
DisplayApiPreference::Wgl(_raw_window),
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))]
DisplayApiPreference::Egl,
)
.unwrap()
}
}

fn pick_gl_config(display: &Display, raw_window: Option<RawWindowHandle>) -> Option<Config> {
let mut template = ConfigTemplateBuilder::new().with_transparency(true);
if let Some(rwh) = raw_window {
template = template.compatible_with_native_window(rwh);
}
let template = template.build();
unsafe {
display
.find_configs(template)
.ok()?
.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
}
})
}
}

/// Cache of glutin `Display`s keyed by the raw X display/connection pointer, to avoid
/// double-initializing EGL for the same X server connection (once in `pick_x11_gl_visual`
/// and again in `OpenGLBackend::new`).
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
fn display_cache() -> &'static Mutex<HashMap<usize, Display>> {
static CACHE: OnceLock<Mutex<HashMap<usize, Display>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}

#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
fn display_cache_key(raw_display_handle: RawDisplayHandle) -> Option<usize> {
match raw_display_handle {
RawDisplayHandle::Xlib(h) => h.display.map(|p| p.as_ptr() as usize),
RawDisplayHandle::Xcb(h) => h.connection.map(|p| p.as_ptr() as usize),
_ => None,
}
}

#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
fn get_or_build_gl_display(
raw_display_handle: RawDisplayHandle,
raw_window: Option<RawWindowHandle>,
) -> Display {
if let Some(key) = display_cache_key(raw_display_handle) {
let mut cache = display_cache().lock().unwrap();
if let Some(d) = cache.get(&key) {
return d.clone();
}
let display = build_gl_display(raw_display_handle, raw_window);
cache.insert(key, display.clone());
return display;
}
build_gl_display(raw_display_handle, raw_window)
}

/// Pick an X11 visual ID compatible with the OpenGL backend used by [`SkiaWindowRenderer`].
///
/// Apply the returned visual to the winit window with [`WindowAttributesExtX11::with_x11_visual`]
/// before creating the window. This avoids `EGL_BAD_MATCH` at surface creation, which happens
/// when winit's default visual doesn't match any EGL config.
///
/// Returns `None` on non-Xlib/Xcb display handles, if no compatible EGL config can be
/// found, or if the platform's EGL implementation doesn't expose an X11 visual ID for the
/// picked config.
///
/// [`SkiaWindowRenderer`]: crate::SkiaWindowRenderer
/// [`WindowAttributesExtX11::with_x11_visual`]: https://docs.rs/winit/latest/winit/platform/x11/trait.WindowAttributesExtX11.html#tymethod.with_x11_visual
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
pub fn pick_x11_gl_visual(raw_display_handle: RawDisplayHandle) -> Option<u32> {
if !matches!(
raw_display_handle,
RawDisplayHandle::Xlib(_) | RawDisplayHandle::Xcb(_)
) {
return None;
}
let display = get_or_build_gl_display(raw_display_handle, None);
let config = pick_gl_config(&display, None)?;
use glutin::platform::x11::X11GlConfigExt;
config.x11_visual().map(|v| v.visual_id() as u32)
}

pub(crate) struct OpenGLBackend {
surface: Option<Surface>,
gr_context: DirectContext,
Expand All @@ -35,36 +153,19 @@ impl OpenGLBackend {
let raw_display_handle = window.display_handle().unwrap().as_raw();
let raw_window_handle = window.window_handle().unwrap().as_raw();

let gl_display = unsafe {
Display::new(
raw_display_handle,
#[cfg(any(target_os = "macos", target_os = "ios"))]
DisplayApiPreference::Cgl,
#[cfg(target_os = "windows")]
DisplayApiPreference::Wgl(Some(raw_window_handle.clone())),
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))]
DisplayApiPreference::Egl,
)
.unwrap()
};
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
let gl_display = get_or_build_gl_display(raw_display_handle, Some(raw_window_handle));
#[cfg(not(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
)))]
let gl_display = build_gl_display(raw_display_handle, Some(raw_window_handle));

let gl_config_template = ConfigTemplateBuilder::new().with_transparency(true).build();
let gl_config = unsafe {
gl_display
.find_configs(gl_config_template)
.unwrap()
.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
}
})
.unwrap()
};
let gl_config = pick_gl_config(&gl_display, Some(raw_window_handle))
.expect("no GL config found for display");

let gl_context_attrs = ContextAttributesBuilder::new().build(Some(raw_window_handle));
let gl_surface_attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
Expand Down
25 changes: 24 additions & 1 deletion examples/bunnymark/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use anyrender::{PaintScene, WindowRenderer};
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
use anyrender_skia::pick_x11_gl_visual;
use anyrender_skia::{SkiaImageRenderer, SkiaWindowRenderer};
use anyrender_vello::VelloWindowRenderer;
use anyrender_vello_cpu::VelloCpuWindowRenderer;
Expand Down Expand Up @@ -134,7 +139,7 @@ impl App {
RenderState::Suspended(cached_window) => cached_window.clone(),
};
let window = window.take().unwrap_or_else(|| {
let attr = Window::default_attributes()
let mut attr = Window::default_attributes()
.with_inner_size(winit::dpi::LogicalSize::new(
self.logical_width,
self.logical_height,
Expand All @@ -143,6 +148,24 @@ impl App {
.with_title("anyrender + winit demo")
.with_visible(true)
.with_active(true);

// On X11, pre-pick a visual that's compatible with the OpenGL backend's EGL
// configs. winit otherwise inherits the root window's visual via COPY_FROM_PARENT,
// which may have no matching EGL config and causes EGL_BAD_MATCH at surface creation.
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
{
use winit::platform::x11::WindowAttributesExtX11;
use winit::raw_window_handle::HasDisplayHandle;
if let Ok(display_handle) = event_loop.display_handle()
&& let Some(visual_id) = pick_x11_gl_visual(display_handle.as_raw())
{
attr = attr.with_x11_visual(visual_id);
}
}

Arc::new(event_loop.create_window(attr).unwrap())
});
self.scale_factor = window.scale_factor();
Expand Down
25 changes: 24 additions & 1 deletion examples/player/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use anyrender::{NullWindowRenderer, PaintScene, Scene, WindowRenderer};
use anyrender_serialize::SceneArchive;
use anyrender_skia::SkiaWindowRenderer;
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
use anyrender_skia::pick_x11_gl_visual;
use anyrender_vello::VelloWindowRenderer;
use anyrender_vello_cpu::{PixelsWindowRenderer, SoftbufferWindowRenderer, VelloCpuImageRenderer};
use anyrender_vello_hybrid::VelloHybridWindowRenderer;
Expand Down Expand Up @@ -131,12 +136,30 @@ impl App {
RenderState::Suspended(cached_window) => cached_window.clone(),
};
let window = window.take().unwrap_or_else(|| {
let attr = Window::default_attributes()
let mut attr = Window::default_attributes()
.with_inner_size(winit::dpi::LogicalSize::new(self.width, self.height))
.with_resizable(true)
.with_title("anyrender + winit demo")
.with_visible(true)
.with_active(true);

// On X11, pre-pick a visual that's compatible with the OpenGL backend's EGL
// configs. winit otherwise inherits the root window's visual via COPY_FROM_PARENT,
// which may have no matching EGL config and causes EGL_BAD_MATCH at surface creation.
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
{
use winit::platform::x11::WindowAttributesExtX11;
use winit::raw_window_handle::HasDisplayHandle;
if let Ok(display_handle) = event_loop.display_handle()
&& let Some(visual_id) = pick_x11_gl_visual(display_handle.as_raw())
{
attr = attr.with_x11_visual(visual_id);
}
}

Arc::new(event_loop.create_window(attr).unwrap())
});

Expand Down
Loading