Skip to content
Draft
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
170 changes: 163 additions & 7 deletions src/linux_util/src/dmabuf_probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -103,6 +121,7 @@ fn probe(ozone: &str, wayland_egl_dpy: *mut c_void) -> Result<bool, String> {
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<bool, String> {
if unsafe { (egl.bind_api)(egl::OPENGL_ES_API) } != egl::TRUE {
Expand Down Expand Up @@ -167,9 +186,35 @@ fn probe(ozone: &str, wayland_egl_dpy: *mut c_void) -> Result<bool, String> {
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,
Expand Down Expand Up @@ -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<u64> {
let Ok(query) = get_gl::<FnEglQueryDmaBufModifiersExt>(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<bool, String> {
let gen_tex = get_gl::<FnGlGenTextures>(egl, "glGenTextures")?;
let bind_tex = get_gl::<FnGlBindTexture>(egl, "glBindTexture")?;
Expand Down Expand Up @@ -276,22 +370,74 @@ fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result<bool, String>
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<FnGbmBoCreateWithModifiers> = unsafe {
gbm_lib
.get(b"gbm_bo_create_with_modifiers\0")
.ok()
.map(|s: Symbol<FnGbmBoCreateWithModifiers>| *s)
};
let get_modifier: Option<FnGbmBoGetModifier> = unsafe {
gbm_lib
.get(b"gbm_bo_get_modifier\0")
.ok()
.map(|s: Symbol<FnGbmBoGetModifier>| *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);
libc::close(drm_fd);
}
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) };

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<egl::Int> = vec![
egl::WIDTH,
64,
egl::HEIGHT,
Expand All @@ -304,8 +450,16 @@ fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result<bool, String>
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,
Expand All @@ -316,9 +470,11 @@ fn run_gl_test(egl: &egl::Egl, display: egl::EGLDisplay) -> Result<bool, String>
)
};
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;
Expand Down
4 changes: 4 additions & 0 deletions src/linux_util/src/egl_dyn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<extern "system" fn()>;
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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
})
}
Expand Down
Loading