1use crate::wl;
14use anyhow::{Context as _, Result, anyhow, bail};
15use edgefirst_egl as egl;
16use std::ffi::c_void;
17use std::os::fd::AsRawFd;
18use std::sync::Arc;
19use wayland_client::Connection;
20
21pub(crate) type Egl = egl::Instance<egl::Dynamic<libloading::Library, egl::EGL1_4>>;
22
23pub(crate) type EglImage = *mut c_void;
24pub(crate) const EGL_LINUX_DMA_BUF_EXT: u32 = 0x3270;
25const EGL_WIDTH: i32 = 0x3057;
26const EGL_HEIGHT: i32 = 0x3056;
27const EGL_LINUX_DRM_FOURCC_EXT: i32 = 0x3271;
28const EGL_DMA_BUF_PLANE0_FD_EXT: i32 = 0x3272;
29const EGL_DMA_BUF_PLANE0_OFFSET_EXT: i32 = 0x3273;
30const EGL_DMA_BUF_PLANE0_PITCH_EXT: i32 = 0x3274;
31const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: i32 = 0x3443;
32const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: i32 = 0x3444;
33const EGL_ATTRIB_NONE: i32 = 0x3038;
34pub(crate) const GL_TEXTURE_2D: u32 = 0x0DE1;
35
36type EglCreateImageKhr =
37 unsafe extern "system" fn(*mut c_void, *mut c_void, u32, *mut c_void, *const i32) -> EglImage;
38type EglDestroyImageKhr = unsafe extern "system" fn(*mut c_void, EglImage) -> u32;
39type GlEglImageTargetTexture2dOes = unsafe extern "system" fn(u32, EglImage);
40
41#[derive(Clone, Copy)]
43pub(crate) struct DmabufEgl {
44 pub(crate) display: *mut c_void,
45 pub(crate) create_image: EglCreateImageKhr,
46 pub(crate) destroy_image: EglDestroyImageKhr,
47 pub(crate) image_target: GlEglImageTargetTexture2dOes,
48}
49
50pub(crate) fn load_dmabuf_egl(egl: &Egl, display: egl::Display) -> Option<DmabufEgl> {
53 let create = egl.get_proc_address("eglCreateImageKHR")?;
54 let destroy = egl.get_proc_address("eglDestroyImageKHR")?;
55 let target = egl.get_proc_address("glEGLImageTargetTexture2DOES")?;
56 Some(unsafe {
58 DmabufEgl {
59 display: display.as_ptr(),
60 create_image: std::mem::transmute::<extern "system" fn(), EglCreateImageKhr>(create),
61 destroy_image: std::mem::transmute::<extern "system" fn(), EglDestroyImageKhr>(destroy),
62 image_target: std::mem::transmute::<extern "system" fn(), GlEglImageTargetTexture2dOes>(
63 target,
64 ),
65 }
66 })
67}
68
69pub(crate) fn dmabuf_image_attribs(frame: &wl::DmabufFrame) -> [i32; 17] {
73 [
74 EGL_WIDTH,
75 frame.width as i32,
76 EGL_HEIGHT,
77 frame.height as i32,
78 EGL_LINUX_DRM_FOURCC_EXT,
79 frame.fourcc as i32,
80 EGL_DMA_BUF_PLANE0_FD_EXT,
81 frame.fd.as_raw_fd(),
82 EGL_DMA_BUF_PLANE0_OFFSET_EXT,
83 frame.offset as i32,
84 EGL_DMA_BUF_PLANE0_PITCH_EXT,
85 frame.stride as i32,
86 EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
87 (frame.modifier & 0xffff_ffff) as i32,
88 EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
89 (frame.modifier >> 32) as i32,
90 EGL_ATTRIB_NONE,
91 ]
92}
93
94pub struct GpuReadback {
103 _conn: Connection,
105 egl: Egl,
106 display: egl::Display,
107 surface: egl::Surface,
108 context: egl::Context,
109 gl: Arc<glow::Context>,
110 dmabuf_egl: Option<DmabufEgl>,
111}
112
113impl GpuReadback {
114 pub fn new() -> Result<Self> {
117 let conn = Connection::connect_to_env().context("Wayland connection")?;
118 let lib = unsafe { egl::DynamicInstance::<egl::EGL1_4>::load_required() }
119 .map_err(|e| anyhow!("libEGL not found: {e}"))?;
120 let egl: Egl = lib;
121
122 let display_ptr = conn.backend().display_ptr() as *mut c_void;
123 let display = unsafe { egl.get_display(display_ptr) }.context("eglGetDisplay")?;
124 egl.initialize(display).context("eglInitialize")?;
125 egl.bind_api(egl::OPENGL_ES_API).context("eglBindAPI")?;
126
127 let attribs = [
128 egl::SURFACE_TYPE,
129 egl::PBUFFER_BIT,
130 egl::RENDERABLE_TYPE,
131 egl::OPENGL_ES2_BIT,
132 egl::RED_SIZE,
133 8,
134 egl::GREEN_SIZE,
135 8,
136 egl::BLUE_SIZE,
137 8,
138 egl::ALPHA_SIZE,
139 8,
140 egl::NONE,
141 ];
142 let config = egl
143 .choose_first_config(display, &attribs)
144 .context("eglChooseConfig")?
145 .context("no EGL pbuffer config")?;
146
147 let ctx_attribs = [egl::CONTEXT_CLIENT_VERSION, 3, egl::NONE];
148 let context = egl
149 .create_context(display, config, None, &ctx_attribs)
150 .or_else(|_| {
151 let a = [egl::CONTEXT_CLIENT_VERSION, 2, egl::NONE];
152 egl.create_context(display, config, None, &a)
153 })
154 .context("eglCreateContext")?;
155
156 let pb_attribs = [egl::WIDTH, 1, egl::HEIGHT, 1, egl::NONE];
157 let surface = egl
158 .create_pbuffer_surface(display, config, &pb_attribs)
159 .context("eglCreatePbufferSurface")?;
160 egl.make_current(display, Some(surface), Some(surface), Some(context))
161 .context("eglMakeCurrent")?;
162
163 let gl = unsafe {
164 glow::Context::from_loader_function(|s| {
165 egl.get_proc_address(s)
166 .map_or(std::ptr::null(), |p| p as *const _)
167 })
168 };
169 let dmabuf_egl = load_dmabuf_egl(&egl, display);
170
171 Ok(GpuReadback {
172 _conn: conn,
173 egl,
174 display,
175 surface,
176 context,
177 gl: Arc::new(gl),
178 dmabuf_egl,
179 })
180 }
181
182 pub fn readback(&mut self, frame: wl::DmabufFrame) -> Result<wl::CapturedImage> {
186 let egl = self
187 .dmabuf_egl
188 .context("EGL dma-buf import unavailable (driver)")?;
189 let (w, h) = (frame.width, frame.height);
190 if w == 0 || h == 0 {
191 bail!("dimensions de readback nulles");
192 }
193
194 self.egl
195 .make_current(
196 self.display,
197 Some(self.surface),
198 Some(self.surface),
199 Some(self.context),
200 )
201 .context("eglMakeCurrent")?;
202
203 let attribs = dmabuf_image_attribs(&frame);
204 let image = unsafe {
205 (egl.create_image)(
206 egl.display,
207 std::ptr::null_mut(),
208 EGL_LINUX_DMA_BUF_EXT,
209 std::ptr::null_mut(),
210 attribs.as_ptr(),
211 )
212 };
213 if image.is_null() {
214 bail!("eglCreateImageKHR failed");
215 }
216
217 let read = self.read_image_to_rgba(&egl, image, w, h);
220 unsafe { (egl.destroy_image)(egl.display, image) };
221 let mut rgba = read?;
222
223 for px in rgba.chunks_exact_mut(4) {
224 px[3] = 255;
225 }
226 Ok(wl::CapturedImage {
227 width: w,
228 height: h,
229 rgba,
230 })
231 }
232
233 fn read_image_to_rgba(
237 &self,
238 egl: &DmabufEgl,
239 image: EglImage,
240 w: u32,
241 h: u32,
242 ) -> Result<Vec<u8>> {
243 use glow::HasContext as _;
244 unsafe {
245 let tex = self
246 .gl
247 .create_texture()
248 .map_err(|e| anyhow!("glGenTextures: {e}"))?;
249 self.gl.bind_texture(GL_TEXTURE_2D, Some(tex));
250 (egl.image_target)(GL_TEXTURE_2D, image);
251
252 let fbo = self.gl.create_framebuffer().map_err(|e| {
253 self.gl.delete_texture(tex);
254 anyhow!("glGenFramebuffers: {e}")
255 })?;
256 self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
257 self.gl.framebuffer_texture_2d(
258 glow::FRAMEBUFFER,
259 glow::COLOR_ATTACHMENT0,
260 GL_TEXTURE_2D,
261 Some(tex),
262 0,
263 );
264
265 let status = self.gl.check_framebuffer_status(glow::FRAMEBUFFER);
266 let result = if status == glow::FRAMEBUFFER_COMPLETE {
267 let mut buf = vec![0u8; w as usize * h as usize * 4];
268 self.gl.read_pixels(
269 0,
270 0,
271 w as i32,
272 h as i32,
273 glow::RGBA,
274 glow::UNSIGNED_BYTE,
275 glow::PixelPackData::Slice(Some(&mut buf)),
276 );
277 Ok(buf)
278 } else {
279 Err(anyhow!("FBO de readback incomplet (0x{status:x})"))
280 };
281
282 self.gl.bind_framebuffer(glow::FRAMEBUFFER, None);
283 self.gl.delete_framebuffer(fbo);
284 self.gl.bind_texture(GL_TEXTURE_2D, None);
285 self.gl.delete_texture(tex);
286 result
287 }
288 }
289}