diff --git a/src/linux_util/src/dmabuf_probe.rs b/src/linux_util/src/dmabuf_probe.rs index 0d610a065..510a19fa3 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 @@ -103,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 { @@ -167,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, @@ -235,6 +280,55 @@ fn acquire_display( Ok((display, true, Some(owned))) } +/// 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 { + 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 +370,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 +427,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 +437,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 +450,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 +470,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; 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, }) }