From 64e429b3f484a6bfc73eb0764de939076c0befa5 Mon Sep 17 00:00:00 2001 From: mysticalsoap Date: Thu, 23 Jul 2026 00:21:19 -0400 Subject: [PATCH 1/2] linux_util: negotiate explicit DRM modifiers in dmabuf probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NVIDIA's EGL/GBM stack doesn't support implicit-modifier dmabuf import the way Mesa does, so glEGLImageTargetTexture2DOES failed with GL_INVALID_OPERATION (0x502) and the whole session fell back to software CEF rendering — even though the runtime shared-texture import path (gpu_paint's Vulkan-based dmabuf_import.rs) already negotiates explicit modifiers correctly and is unaffected. Query eglQueryDmaBufModifiersEXT for ARGB8888, filtered to non- external-only entries, and allocate the test GBM buffer via gbm_bo_create_with_modifiers when both the extension and symbol are available, passing the chosen modifier explicitly via EGL_DMA_BUF_PLANE0_MODIFIER_{LO,HI}_EXT. Missing symbols or an empty modifier list fall through to the existing implicit gbm_bo_create path unchanged, so Mesa drivers are unaffected. Verified against RTX 4090 + KWin: the probe now negotiates an explicit modifier (0x300000000606013) and reports GBM -> EGL -> GL import OK. Note: enabling the shared-texture path this way surfaces a separate, still-unresolved failure in CEF's own GPU process (repeated "Unable to initialize SkSurface" in shared_image_representation.cc, blank window) that was previously masked by the software-rendering fallback. See PR description for details — not yet safe to merge on its own. Assisted-by: claude-sonnet-5 --- src/linux_util/src/dmabuf_probe.rs | 144 +++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 7 deletions(-) diff --git a/src/linux_util/src/dmabuf_probe.rs b/src/linux_util/src/dmabuf_probe.rs index 0d610a065..ae58ae52d 100644 --- a/src/linux_util/src/dmabuf_probe.rs +++ b/src/linux_util/src/dmabuf_probe.rs @@ -8,13 +8,16 @@ //! `--ozone-platform=x11` over XWayland). use crate::egl_dyn as egl; -use libloading::Library; +use libloading::{Library, Symbol}; use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void}; use std::os::fd::RawFd; use std::ptr; // ARGB8888 fourcc — pulled from drm_fourcc.h to avoid a libdrm dep. const DRM_FORMAT_ARGB8888: u32 = 0x3432_5241; +// Sentinel meaning "no explicit modifier" — never a real advertised modifier, +// filtered out of eglQueryDmaBufModifiersEXT results defensively. +const DRM_FORMAT_MOD_INVALID: u64 = 0x00ff_ffff_ffff_ffff; const GL_TEXTURE_2D: c_uint = 0x0DE1; const GL_NO_ERROR: c_uint = 0; const GBM_BO_USE_RENDERING: u32 = 0x0002; @@ -25,6 +28,8 @@ const EGL_LINUX_DRM_FOURCC_EXT: egl::Int = 0x3271; const EGL_DMA_BUF_PLANE0_FD_EXT: egl::Int = 0x3272; const EGL_DMA_BUF_PLANE0_OFFSET_EXT: egl::Int = 0x3273; const EGL_DMA_BUF_PLANE0_PITCH_EXT: egl::Int = 0x3274; +const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: egl::Int = 0x3443; +const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: egl::Int = 0x3444; const EGL_DEVICE_EXT: egl::Int = 0x322C; const EGL_DRM_RENDER_NODE_FILE_EXT: egl::Int = 0x3377; @@ -38,6 +43,11 @@ type FnGbmBoCreate = unsafe extern "C" fn(*mut GbmDevice, u32, u32, u32, u32) -> type FnGbmBoDestroy = unsafe extern "C" fn(*mut GbmBo); type FnGbmBoGetFd = unsafe extern "C" fn(*mut GbmBo) -> c_int; type FnGbmBoGetStride = unsafe extern "C" fn(*mut GbmBo) -> u32; +// Optional: only present on libgbm >= 17.1. Missing means the driver only +// exposed the modifier-less API, so we fall back to implicit gbm_bo_create. +type FnGbmBoCreateWithModifiers = + unsafe extern "C" fn(*mut GbmDevice, u32, u32, u32, *const u64, c_uint) -> *mut GbmBo; +type FnGbmBoGetModifier = unsafe extern "C" fn(*mut GbmBo) -> u64; type FnXOpenDisplay = unsafe extern "C" fn(*const c_char) -> *mut X11Display; type FnXCloseDisplay = unsafe extern "C" fn(*mut X11Display) -> c_int; @@ -61,6 +71,14 @@ type FnEglDestroyImageKhr = unsafe extern "C" fn(egl::EGLDisplay, *mut c_void) - type FnEglQueryDisplayAttribExt = unsafe extern "C" fn(egl::EGLDisplay, egl::Int, *mut isize) -> egl::Boolean; type FnEglQueryDeviceStringExt = unsafe extern "C" fn(*mut c_void, egl::Int) -> *const c_char; +type FnEglQueryDmaBufModifiersExt = unsafe extern "C" fn( + egl::EGLDisplay, + egl::Int, + egl::Int, + *mut u64, + *mut egl::Boolean, + *mut egl::Int, +) -> egl::Boolean; /// Returns true if a GBM-allocated ARGB8888 dmabuf can be imported as an EGL /// image and bound to a GL texture on the EGL display CEF will use. The @@ -235,6 +253,56 @@ fn acquire_display( Ok((display, true, Some(owned))) } +/// Modifiers the driver advertises for ARGB8888, filtered to ones usable as +/// a plain sampled 2D texture (drops external-only entries, relevant mainly +/// to planar/YUV formats but checked here for correctness). Empty when +/// `EGL_EXT_image_dma_buf_import_modifiers` isn't available — callers should +/// fall back to implicit-modifier import in that case. +fn query_argb8888_modifiers(egl: &egl::Egl, display: egl::EGLDisplay) -> Vec { + let Ok(query) = get_gl::(egl, "eglQueryDmaBufModifiersEXT") + else { + return Vec::new(); + }; + let fourcc = DRM_FORMAT_ARGB8888 as egl::Int; + let mut count: egl::Int = 0; + let queried = unsafe { + query( + display, + fourcc, + 0, + ptr::null_mut(), + ptr::null_mut(), + &mut count, + ) + }; + if queried != egl::TRUE || count <= 0 { + return Vec::new(); + } + + let mut modifiers = vec![0u64; count as usize]; + let mut external_only = vec![0u32; count as usize]; + let queried = unsafe { + query( + display, + fourcc, + count, + modifiers.as_mut_ptr(), + external_only.as_mut_ptr(), + &mut count, + ) + }; + if queried != egl::TRUE { + return Vec::new(); + } + + modifiers + .into_iter() + .zip(external_only) + .filter(|(m, external)| *external == 0 && *m != DRM_FORMAT_MOD_INVALID) + .map(|(m, _)| m) + .collect() +} + fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result { let gen_tex = get_gl::(egl, "glGenTextures")?; let bind_tex = get_gl::(egl, "glBindTexture")?; @@ -276,7 +344,56 @@ fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result return Err("gbm_create_device failed".into()); } - let bo = unsafe { (gbm.bo_create)(device, 64, 64, DRM_FORMAT_ARGB8888, GBM_BO_USE_RENDERING) }; + // NVIDIA's EGL/GBM stack doesn't support implicit-modifier dmabuf import + // the way Mesa does — glEGLImageTargetTexture2DOES fails with + // GL_INVALID_OPERATION unless we negotiate an explicit one. Missing + // symbols or an empty modifier list both fall through to the pre-existing + // implicit gbm_bo_create path, so Mesa drivers are unaffected. + let create_with_modifiers: Option = unsafe { + gbm_lib + .get(b"gbm_bo_create_with_modifiers\0") + .ok() + .map(|s: Symbol| *s) + }; + let get_modifier: Option = unsafe { + gbm_lib + .get(b"gbm_bo_get_modifier\0") + .ok() + .map(|s: Symbol| *s) + }; + let modifiers = query_argb8888_modifiers(egl, display); + + let (bo, modifier) = match (create_with_modifiers, get_modifier, modifiers.is_empty()) { + (Some(create), Some(get_mod), false) => { + let bo = unsafe { + create( + device, + 64, + 64, + DRM_FORMAT_ARGB8888, + modifiers.as_ptr(), + modifiers.len() as c_uint, + ) + }; + if bo.is_null() { + tracing::warn!( + "dmabuf probe: gbm_bo_create_with_modifiers failed, falling back to implicit modifier" + ); + ( + unsafe { + (gbm.bo_create)(device, 64, 64, DRM_FORMAT_ARGB8888, GBM_BO_USE_RENDERING) + }, + None, + ) + } else { + (bo, Some(unsafe { get_mod(bo) })) + } + } + _ => ( + unsafe { (gbm.bo_create)(device, 64, 64, DRM_FORMAT_ARGB8888, GBM_BO_USE_RENDERING) }, + None, + ), + }; if bo.is_null() { unsafe { (gbm.device_destroy)(device); @@ -284,6 +401,9 @@ fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result } return Err("gbm_bo_create ARGB8888 failed".into()); } + if let Some(m) = modifier { + tracing::info!("dmabuf probe: using explicit modifier 0x{:x}", m); + } let dmabuf_fd = unsafe { (gbm.bo_get_fd)(bo) }; let stride = unsafe { (gbm.bo_get_stride)(bo) }; @@ -291,7 +411,7 @@ fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result let result = if dmabuf_fd < 0 { Err("gbm_bo_get_fd failed".to_string()) } else { - let img_attrs: [egl::Int; 13] = [ + let mut img_attrs: Vec = vec![ egl::WIDTH, 64, egl::HEIGHT, @@ -304,8 +424,16 @@ fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result 0, EGL_DMA_BUF_PLANE0_PITCH_EXT, stride as egl::Int, - egl::NONE, ]; + if let Some(m) = modifier { + img_attrs.extend([ + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + (m & 0xffff_ffff) as egl::Int, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, + (m >> 32) as egl::Int, + ]); + } + img_attrs.push(egl::NONE); let image = unsafe { create_image( display, @@ -316,9 +444,11 @@ fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result ) }; if image.is_null() { - tracing::warn!("dmabuf probe: eglCreateImageKHR failed (0x{:x})", unsafe { - (egl.get_error)() - }); + tracing::warn!( + "dmabuf probe: eglCreateImageKHR failed (0x{:x}, modifier={:?})", + unsafe { (egl.get_error)() }, + modifier + ); Ok(false) } else { let mut tex: c_uint = 0; From 522d20acc4f24a0c136d1fee12f70b4f0a5e12fd Mon Sep 17 00:00:00 2001 From: mysticalsoap Date: Thu, 23 Jul 2026 00:58:23 -0400 Subject: [PATCH 2/2] linux_util: block shared textures on NVIDIA despite working transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The modifier fix in the previous commit is real and correct — NVIDIA can genuinely import a dmabuf via the transport probe. But enabling shared textures on that basis surfaces a separate, independent failure: CEF's OSR compositor can't wrap the imported buffer as a writable render target (SkSurfaces::WrapBackendTexture fails, "Unable to initialize SkSurface" in Chromium's shared_image_representation.cc), producing a blank window every frame instead of a UI. This is not jellium-desktop's bug to fix. Electron hits the identical failure — same file, same message, same trigger (OSR + shared texture), same NVIDIA+Wayland scope, AMD/Mesa and non-OSR unaffected — and closed their own tracking issue as not planned with no root cause or workaround (https://github.com/electron/electron/issues/49247). Both CEF and Electron only wrap Chromium's own GPU-compositing code; neither project controls this code path. Query EGL_VENDOR once the display is available and block shared textures on NVIDIA regardless of what the transport probe reports, trading a working (if slow) software-rendered UI for a correctly understood limitation instead of an unexplained blank window. Verified live on RTX 4090 + KWin: transport still negotiates the modifier and passes, the vendor guard overrides it, zero SkSurface errors, UI renders as before (software path, same as pre-fix baseline). Assisted-by: claude-sonnet-5 --- src/linux_util/src/dmabuf_probe.rs | 36 +++++++++++++++++++++++++----- src/linux_util/src/egl_dyn.rs | 4 ++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/linux_util/src/dmabuf_probe.rs b/src/linux_util/src/dmabuf_probe.rs index ae58ae52d..510a19fa3 100644 --- a/src/linux_util/src/dmabuf_probe.rs +++ b/src/linux_util/src/dmabuf_probe.rs @@ -121,6 +121,7 @@ fn probe(ozone: &str, wayland_egl_dpy: *mut c_void) -> Result { let egl = egl::Egl::load_from(egl_lib).map_err(|e| format!("EGL load failed: {}", e))?; let (display, owns_display, _x11_state) = acquire_display(&egl, ozone, wayland_egl_dpy)?; + let nvidia = is_nvidia_vendor(&egl, display); let result = (|| -> Result { if unsafe { (egl.bind_api)(egl::OPENGL_ES_API) } != egl::TRUE { @@ -185,9 +186,35 @@ fn probe(ozone: &str, wayland_egl_dpy: *mut c_void) -> Result { Ok(false) => tracing::warn!("dmabuf probe: ARGB8888 dmabuf import failed"), Err(e) => tracing::warn!("dmabuf probe: {}", e), } + + // Even when the transport test passes on NVIDIA, CEF's OSR shared-texture + // path fails one layer up: Chromium can't wrap the imported buffer as a + // writable render target ("Unable to initialize SkSurface" in + // shared_image_representation.cc), leaving a blank window. Electron hits + // the same failure and closed it unfixed (electron/electron#49247), so + // stay on the software path regardless of the transport result. + if nvidia { + tracing::warn!( + "dmabuf probe: blocking shared textures on NVIDIA; CEF OSR path broken \ + upstream (electron/electron#49247)" + ); + return Ok(false); + } result } +/// True if the EGL display's driver vendor string identifies NVIDIA's +/// proprietary driver, which advertises `EGL_VENDOR` as "NVIDIA". +fn is_nvidia_vendor(egl: &egl::Egl, display: egl::EGLDisplay) -> bool { + let vendor = unsafe { (egl.query_string)(display, egl::VENDOR) }; + if vendor.is_null() { + return false; + } + unsafe { CStr::from_ptr(vendor) } + .to_str() + .is_ok_and(|s| s.contains("NVIDIA")) +} + struct X11Owned { _lib: Library, dpy: *mut X11Display, @@ -253,11 +280,10 @@ fn acquire_display( Ok((display, true, Some(owned))) } -/// Modifiers the driver advertises for ARGB8888, filtered to ones usable as -/// a plain sampled 2D texture (drops external-only entries, relevant mainly -/// to planar/YUV formats but checked here for correctness). Empty when -/// `EGL_EXT_image_dma_buf_import_modifiers` isn't available — callers should -/// fall back to implicit-modifier import in that case. +/// Modifiers the driver advertises for ARGB8888, dropping external-only +/// entries (not usable as a plain sampled 2D texture). Empty when +/// `EGL_EXT_image_dma_buf_import_modifiers` isn't available — callers fall +/// back to implicit-modifier import in that case. fn query_argb8888_modifiers(egl: &egl::Egl, display: egl::EGLDisplay) -> Vec { let Ok(query) = get_gl::(egl, "eglQueryDmaBufModifiersEXT") else { diff --git a/src/linux_util/src/egl_dyn.rs b/src/linux_util/src/egl_dyn.rs index 9538d04bd..561ae5255 100644 --- a/src/linux_util/src/egl_dyn.rs +++ b/src/linux_util/src/egl_dyn.rs @@ -18,6 +18,7 @@ pub type NativeDisplayType = *mut c_void; pub const TRUE: Boolean = 1; pub const NONE: Int = 0x3038; +pub const VENDOR: Int = 0x3053; pub const WIDTH: Int = 0x3057; pub const HEIGHT: Int = 0x3056; pub const SURFACE_TYPE: Int = 0x3033; @@ -43,6 +44,7 @@ pub type FnDestroySurface = unsafe extern "C" fn(EGLDisplay, EGLSurface) -> Bool pub type FnDestroyContext = unsafe extern "C" fn(EGLDisplay, EGLContext) -> Boolean; pub type FnGetProcAddress = unsafe extern "C" fn(*const c_char) -> Option; pub type FnGetError = unsafe extern "C" fn() -> Int; +pub type FnQueryString = unsafe extern "C" fn(EGLDisplay, Int) -> *const c_char; pub struct Egl { _lib: Library, @@ -58,6 +60,7 @@ pub struct Egl { pub destroy_context: FnDestroyContext, pub get_proc_address_raw: FnGetProcAddress, pub get_error: FnGetError, + pub query_string: FnQueryString, } impl Egl { @@ -87,6 +90,7 @@ impl Egl { destroy_context: get(&lib, b"eglDestroyContext\0")?, get_proc_address_raw: get(&lib, b"eglGetProcAddress\0")?, get_error: get(&lib, b"eglGetError\0")?, + query_string: get(&lib, b"eglQueryString\0")?, _lib: lib, }) }