use crate::gl::{
DmabufEgl, EGL_LINUX_DMA_BUF_EXT, Egl, EglImage, GL_TEXTURE_2D, dmabuf_image_attribs,
load_dmabuf_egl,
};
use crate::wl;
use edgefirst_egl as egl;
use std::collections::HashMap;
use std::ffi::c_void;
use std::sync::Arc;
use wayland_client::{Connection, Proxy, protocol::wl_surface::WlSurface};
pub trait DmabufImporter {
fn import(
&mut self,
key: &str,
frame: wl::DmabufFrame,
) -> Option<(egui::TextureId, egui::Vec2)>;
fn forget(&mut self, key: &str);
}
const GL_TEXTURE_SWIZZLE_A: u32 = 0x8E45;
const GL_ONE: i32 = 1;
struct NativeTex {
image: EglImage,
tex: glow::Texture,
id: egui::TextureId,
size: egui::Vec2,
}
struct HostImporter<'a> {
egl: Option<DmabufEgl>,
gl: Arc<glow::Context>,
painter: &'a mut egui_glow::Painter,
cache: &'a mut HashMap<String, NativeTex>,
}
impl DmabufImporter for HostImporter<'_> {
fn import(
&mut self,
key: &str,
frame: wl::DmabufFrame,
) -> Option<(egui::TextureId, egui::Vec2)> {
use glow::HasContext as _;
let egl = self.egl?;
let size = egui::vec2(frame.width as f32, frame.height as f32);
let attribs = dmabuf_image_attribs(&frame);
let image = unsafe {
(egl.create_image)(
egl.display,
std::ptr::null_mut(),
EGL_LINUX_DMA_BUF_EXT,
std::ptr::null_mut(),
attribs.as_ptr(),
)
};
if image.is_null() {
return None;
}
let ckey = key.to_string();
if let Some(nt) = self.cache.get_mut(&ckey) {
unsafe {
self.gl.bind_texture(GL_TEXTURE_2D, Some(nt.tex));
(egl.image_target)(GL_TEXTURE_2D, image);
self.gl.bind_texture(GL_TEXTURE_2D, None);
(egl.destroy_image)(egl.display, nt.image);
}
nt.image = image;
nt.size = size;
return Some((nt.id, nt.size));
}
let tex = unsafe {
let t = self.gl.create_texture().ok()?;
self.gl.bind_texture(GL_TEXTURE_2D, Some(t));
let lin = glow::LINEAR as i32;
let clamp = glow::CLAMP_TO_EDGE as i32;
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, glow::TEXTURE_MIN_FILTER, lin);
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, glow::TEXTURE_MAG_FILTER, lin);
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, glow::TEXTURE_WRAP_S, clamp);
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, glow::TEXTURE_WRAP_T, clamp);
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
(egl.image_target)(GL_TEXTURE_2D, image);
self.gl.bind_texture(GL_TEXTURE_2D, None);
t
};
let id = self.painter.register_native_texture(tex);
self.cache.insert(
ckey,
NativeTex {
image,
tex,
id,
size,
},
);
Some((id, size))
}
fn forget(&mut self, key: &str) {
use glow::HasContext as _;
let Some(egl) = self.egl else { return };
if let Some(nt) = self.cache.remove(key) {
self.painter.free_texture(nt.id);
unsafe {
self.gl.delete_texture(nt.tex);
(egl.destroy_image)(egl.display, nt.image);
}
}
}
}
pub struct Gpu {
egl: Egl,
display: egl::Display,
surface: egl::Surface,
context: egl::Context,
egl_window: wayland_egl::WlEglSurface,
painter: egui_glow::Painter,
dmabuf_egl: Option<DmabufEgl>,
dmabuf_tex: HashMap<String, NativeTex>,
}
impl Gpu {
pub fn new(conn: &Connection, surface: &WlSurface, pw: i32, ph: i32) -> Gpu {
let lib = unsafe { egl::DynamicInstance::<egl::EGL1_4>::load_required() }
.expect("libEGL not found");
let egl: Egl = lib;
let display_ptr = conn.backend().display_ptr() as *mut c_void;
let display = unsafe { egl.get_display(display_ptr).expect("eglGetDisplay") };
egl.initialize(display).expect("eglInitialize");
egl.bind_api(egl::OPENGL_ES_API).expect("eglBindAPI");
let attribs = [
egl::SURFACE_TYPE,
egl::WINDOW_BIT,
egl::RENDERABLE_TYPE,
egl::OPENGL_ES2_BIT,
egl::RED_SIZE,
8,
egl::GREEN_SIZE,
8,
egl::BLUE_SIZE,
8,
egl::ALPHA_SIZE,
8,
egl::NONE,
];
let config = egl
.choose_first_config(display, &attribs)
.expect("eglChooseConfig")
.expect("no EGL config with alpha");
let ctx_attribs = [egl::CONTEXT_CLIENT_VERSION, 3, egl::NONE];
let context = egl
.create_context(display, config, None, &ctx_attribs)
.or_else(|_| {
let a = [egl::CONTEXT_CLIENT_VERSION, 2, egl::NONE];
egl.create_context(display, config, None, &a)
})
.expect("eglCreateContext");
let egl_window = wayland_egl::WlEglSurface::new(surface.id(), pw, ph).expect("wl_egl");
let egl_surface = unsafe {
egl.create_window_surface(
display,
config,
egl_window.ptr() as egl::NativeWindowType,
None,
)
.expect("eglCreateWindowSurface")
};
egl.make_current(display, Some(egl_surface), Some(egl_surface), Some(context))
.expect("eglMakeCurrent");
let gl = unsafe {
glow::Context::from_loader_function(|s| {
egl.get_proc_address(s)
.map_or(std::ptr::null(), |p| p as *const _)
})
};
let painter = egui_glow::Painter::new(Arc::new(gl), "", None, false).expect("egui_glow");
let dmabuf_egl = load_dmabuf_egl(&egl, display);
if dmabuf_egl.is_none() {
eprintln!("wlr-capture: EGL dma-buf import unavailable (GPU display disabled)");
}
Gpu {
egl,
display,
surface: egl_surface,
context,
egl_window,
painter,
dmabuf_egl,
dmabuf_tex: HashMap::new(),
}
}
pub fn resize(&self, pw: i32, ph: i32) {
self.egl_window.resize(pw, ph, 0, 0);
}
pub fn render(
&mut self,
egui_ctx: &egui::Context,
mut raw_input: egui::RawInput,
ppp: f32,
size_px: (u32, u32),
backdrop: [f32; 4],
mut run_ui: impl FnMut(&mut egui::Ui, &mut dyn DmabufImporter),
) {
raw_input
.viewports
.entry(egui::ViewportId::ROOT)
.or_default()
.native_pixels_per_point = Some(ppp);
let (pw, ph) = size_px;
self.egl
.make_current(
self.display,
Some(self.surface),
Some(self.surface),
Some(self.context),
)
.ok();
let (prims, textures_delta) = {
let gl = self.painter.gl().clone();
let mut importer = HostImporter {
egl: self.dmabuf_egl,
gl,
painter: &mut self.painter,
cache: &mut self.dmabuf_tex,
};
let full = egui_ctx.run_ui(raw_input, |ui| run_ui(ui, &mut importer));
(egui_ctx.tessellate(full.shapes, ppp), full.textures_delta)
};
unsafe {
use glow::HasContext as _;
let gl = self.painter.gl();
gl.viewport(0, 0, pw as i32, ph as i32);
let [r, g, b, a] = backdrop;
gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT);
}
self.painter
.paint_and_update_textures([pw, ph], ppp, &prims, &textures_delta);
self.egl.swap_buffers(self.display, self.surface).ok();
}
}