From 0a5ee8fa36635289a4c4c423196cac112cdfe01e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 27 Jul 2025 21:03:18 +0200 Subject: [PATCH] vulkan: implement software rendering --- build/egl.rs | 5 +++ src/gfx_apis.rs | 27 ++++++++++----- src/gfx_apis/gl.rs | 11 +++++-- src/gfx_apis/gl/egl/display.rs | 19 ++++++++--- src/gfx_apis/gl/egl/sys.rs | 2 ++ src/gfx_apis/gl/ext.rs | 26 ++++++++++++++- src/gfx_apis/gl/renderer/context.rs | 7 ++-- src/gfx_apis/vulkan.rs | 15 +++++++-- src/gfx_apis/vulkan/device.rs | 51 ++++++++++++++++++++++++++--- src/video/gbm.rs | 8 ++--- 10 files changed, 140 insertions(+), 31 deletions(-) diff --git a/build/egl.rs b/build/egl.rs index 93275263..7909b80c 100644 --- a/build/egl.rs +++ b/build/egl.rs @@ -108,6 +108,11 @@ fn write_egl_procs(f: &mut W) -> anyhow::Result<()> { ("flags", "EGLint"), ][..], ), + ( + "eglQueryDeviceStringEXT", + "*const c::c_char", + &[("device", "EGLDeviceEXT"), ("name", "EGLint")][..], + ), ]; writeln!(f, "use std::ptr;")?; diff --git a/src/gfx_apis.rs b/src/gfx_apis.rs index 2a393b09..261b0b1b 100644 --- a/src/gfx_apis.rs +++ b/src/gfx_apis.rs @@ -25,13 +25,21 @@ pub fn create_gfx_context( let mut apis = [GfxApi::OpenGl, GfxApi::Vulkan]; apis.sort_by_key(|&a| if a == api { -1 } else { a as i32 }); let mut last_err = None; - for api in apis { - let res = create_gfx_context_(eng, ring, drm, api, caps_thread); - match res { - Ok(_) => return res, - Err(e) => { - log::warn!("Could not create {:?} API: {}", api, ErrorFmt(&e)); - last_err = Some(e); + for software in [false, true] { + for api in apis { + let res = create_gfx_context_(eng, ring, drm, api, caps_thread, software); + match res { + Ok(_) => { + log::info!("Created a {api:?} renderer"); + if software { + log::warn!("Renderer uses software rendering"); + } + return res; + } + Err(e) => { + log::warn!("Could not create {:?} API: {}", api, ErrorFmt(&e)); + last_err = Some(e); + } } } } @@ -44,10 +52,11 @@ fn create_gfx_context_( drm: &Drm, api: GfxApi, caps_thread: Option<&PrCapsThread>, + software: bool, ) -> Result, GfxError> { match api { - GfxApi::OpenGl => gl::create_gfx_context(drm), - GfxApi::Vulkan => vulkan::create_graphics_context(eng, ring, drm, caps_thread), + GfxApi::OpenGl => gl::create_gfx_context(drm, software), + GfxApi::Vulkan => vulkan::create_graphics_context(eng, ring, drm, caps_thread, software), _ => unreachable!(), } } diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index a17eacf8..ff6f7aed 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -111,11 +111,14 @@ pub mod sys { static INIT: Lazy>> = Lazy::new(|| egl::init().map_err(Arc::new)); -pub(super) fn create_gfx_context(drm: &Drm) -> Result, GfxError> { +pub(super) fn create_gfx_context( + drm: &Drm, + software: bool, +) -> Result, GfxError> { if let Err(e) = &*INIT { return Err(GfxError(Box::new(e.clone()))); } - GlRenderContext::from_drm_device(drm) + GlRenderContext::from_drm_device(drm, software) .map(|v| Rc::new(v) as Rc) .map_err(|e| e.into()) } @@ -200,6 +203,10 @@ enum RenderError { AccessFailed(#[source] Box), #[error("OpenGL does not support blend buffers")] NoBlendBuffer, + #[error("Hardware renderer was requested but EGL device is a software renderer")] + NoHardwareRenderer, + #[error("Could not query display device")] + QueryDisplayDevice, } #[derive(Default)] diff --git a/src/gfx_apis/gl/egl/display.rs b/src/gfx_apis/gl/egl/display.rs index 7915fad3..c62ad4c9 100644 --- a/src/gfx_apis/gl/egl/display.rs +++ b/src/gfx_apis/gl/egl/display.rs @@ -5,7 +5,7 @@ use { gfx_apis::gl::{ RenderError, egl::{ - PROCS, + EXTS, PROCS, context::EglContext, image::EglImage, sys::{ @@ -26,9 +26,10 @@ use { }, ext::{ ANDROID_NATIVE_FENCE_SYNC, DisplayExt, EXT_CREATE_CONTEXT_ROBUSTNESS, - EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS, GL_OES_EGL_IMAGE, GL_OES_EGL_IMAGE_EXTERNAL, - GlExt, KHR_FENCE_SYNC, KHR_IMAGE_BASE, KHR_NO_CONFIG_CONTEXT, - KHR_SURFACELESS_CONTEXT, KHR_WAIT_SYNC, MESA_CONFIGLESS_CONTEXT, get_display_ext, + EXT_DEVICE_QUERY, EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS, GL_OES_EGL_IMAGE, + GL_OES_EGL_IMAGE_EXTERNAL, GlExt, KHR_FENCE_SYNC, KHR_IMAGE_BASE, + KHR_NO_CONFIG_CONTEXT, KHR_SURFACELESS_CONTEXT, KHR_WAIT_SYNC, + MESA_CONFIGLESS_CONTEXT, MESA_DEVICE_SOFTWARE, get_device_ext, get_display_ext, get_gl_ext, }, proc::ExtProc, @@ -70,7 +71,10 @@ pub struct EglDisplay { } impl EglDisplay { - pub(in crate::gfx_apis::gl) fn create(drm: &Drm) -> Result, RenderError> { + pub(in crate::gfx_apis::gl) fn create( + drm: &Drm, + software: bool, + ) -> Result, RenderError> { unsafe { let Some(egl) = EGL.as_ref() else { return Err(RenderError::LoadEgl); @@ -108,6 +112,11 @@ impl EglDisplay { if (egl.eglInitialize)(dpy.dpy, &mut major, &mut minor) != EGL_TRUE { return Err(RenderError::Initialize); } + if !software && EXTS.contains(EXT_DEVICE_QUERY) { + if get_device_ext(procs, dpy.dpy)?.contains(MESA_DEVICE_SOFTWARE) { + return Err(RenderError::NoHardwareRenderer); + } + } dpy.exts = get_display_ext(dpy.dpy); if !dpy.exts.intersects(KHR_IMAGE_BASE) { return Err(RenderError::ImageBase); diff --git a/src/gfx_apis/gl/egl/sys.rs b/src/gfx_apis/gl/egl/sys.rs index 8c9a086a..008e542e 100644 --- a/src/gfx_apis/gl/egl/sys.rs +++ b/src/gfx_apis/gl/egl/sys.rs @@ -14,6 +14,7 @@ egl_transparent!(EGLImageKHR); egl_transparent!(EGLContext); egl_transparent!(EGLClientBuffer); egl_transparent!(EGLLabelKHR); +egl_transparent!(EGLDeviceEXT); pub type EGLDEBUGPROCKHR = unsafe extern "C" fn( error: EGLenum, @@ -53,6 +54,7 @@ pub const EGL_PLATFORM_GBM_KHR: EGLint = 0x31D7; pub const EGL_CONTEXT_CLIENT_VERSION: EGLint = 0x3098; pub const EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT: EGLint = 0x3138; pub const EGL_LOSE_CONTEXT_ON_RESET_EXT: EGLint = 0x31BF; +pub const EGL_DEVICE_EXT: EGLint = 0x322C; pub const GL_GUILTY_CONTEXT_RESET_ARB: GLenum = 0x8253; pub const GL_INNOCENT_CONTEXT_RESET_ARB: GLenum = 0x8254; diff --git a/src/gfx_apis/gl/ext.rs b/src/gfx_apis/gl/ext.rs index 27f1812c..6eb56de8 100644 --- a/src/gfx_apis/gl/ext.rs +++ b/src/gfx_apis/gl/ext.rs @@ -3,7 +3,8 @@ use { RenderError, egl::sys::{EGL_EXTENSIONS, EGLDisplay}, gl::sys::GL_EXTENSIONS, - sys::{EGL, GLESV2}, + proc::ExtProc, + sys::{EGL, EGL_DEVICE_EXT, EGL_TRUE, EGLDeviceEXT, GLESV2}, }, ahash::AHashSet, bstr::ByteSlice, @@ -53,6 +54,7 @@ bitflags! { EXT_PLATFORM_BASE = 1 << 1, KHR_PLATFORM_GBM = 1 << 2, KHR_DEBUG = 1 << 3, + EXT_DEVICE_QUERY = 1 << 4, } pub fn get_client_ext() -> ClientExt { @@ -60,6 +62,7 @@ pub fn get_client_ext() -> ClientExt { ("EGL_EXT_platform_base", EXT_PLATFORM_BASE), ("EGL_KHR_platform_gbm", KHR_PLATFORM_GBM), ("EGL_KHR_debug", KHR_DEBUG), + ("EGL_EXT_device_query", EXT_DEVICE_QUERY), ]; match unsafe { get_dpy_extensions(EGLDisplay::none()) } { Some(exts) => get_typed_ext(&exts, EXT_CLIENT_EXTENSION, &map), @@ -127,3 +130,24 @@ pub fn get_gl_ext() -> Result { _ => Ok(GlExt::none()), } } + +bitflags! { + DevExt: u32; + MESA_DEVICE_SOFTWARE = 1 << 0, +} + +pub fn get_device_ext(procs: &ExtProc, dpy: EGLDisplay) -> Result { + let map = [("EGL_MESA_device_software", MESA_DEVICE_SOFTWARE)]; + unsafe { + let mut device = 0; + if procs.eglQueryDisplayAttribEXT(dpy, EGL_DEVICE_EXT, &mut device) != EGL_TRUE { + return Err(RenderError::QueryDisplayDevice); + } + let device = EGLDeviceEXT(device as _); + let ext = procs.eglQueryDeviceStringEXT(device, EGL_EXTENSIONS); + match get_extensions(ext) { + Some(exts) => Ok(get_typed_ext(&exts, DevExt::none(), &map)), + _ => Ok(DevExt::none()), + } + } +} diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 46539731..d4f1ac99 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -102,12 +102,15 @@ impl GlRenderContext { self.ctx.reset_status() } - pub(in crate::gfx_apis::gl) fn from_drm_device(drm: &Drm) -> Result { + pub(in crate::gfx_apis::gl) fn from_drm_device( + drm: &Drm, + software: bool, + ) -> Result { let node = drm .get_render_node()? .ok_or(RenderError::NoRenderNode) .map(Rc::new)?; - let dpy = EglDisplay::create(drm)?; + let dpy = EglDisplay::create(drm, software)?; if !dpy.formats.contains_key(&XRGB8888.drm) { return Err(RenderError::XRGB888); } diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index c54aa949..3088ad28 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -100,6 +100,8 @@ pub enum VulkanError { EnumeratePhysicalDevices(#[source] vk::Result), #[error("Could not find a vulkan device that matches dev_t {0}")] NoDeviceFound(dev_t), + #[error("There is no vulkan software renderer")] + NoSoftwareRenderer, #[error("Could not load image properties")] LoadImageProperties(#[source] vk::Result), #[error("Device does not support rending and texturing from the XRGB8888 format")] @@ -214,6 +216,12 @@ pub enum VulkanError { NonVulkanBuffer, #[error("Mixed vulkan device use")] MixedVulkanDeviceUse, + #[error("Could not allocate GBM BO")] + AllocGbm(#[source] GbmError), + #[error("Could not retrieve file description flags")] + GetFl(#[source] OsError), + #[error("GBM implementation cannot be used with software renderer")] + SoftwareRendererNotUsable, } impl From for GfxError { @@ -230,18 +238,19 @@ pub fn create_graphics_context( ring: &Rc, drm: &Drm, caps_thread: Option<&PrCapsThread>, + software: bool, ) -> Result, GfxError> { let instance = VulkanInstance::new(Level::Info, *VULKAN_VALIDATION)?; let device = 'device: { if let Some(t) = caps_thread { - match unsafe { t.run(|| instance.create_device(drm, true)) } { + match unsafe { t.run(|| instance.create_device(drm, true, software)) } { Ok(d) => break 'device d, Err(e) => { log::warn!("Could not create high-priority device: {}", ErrorFmt(e)); } } } - instance.create_device(drm, false)? + instance.create_device(drm, false, software)? }; let renderer = device.create_renderer(eng, ring)?; Ok(Rc::new(Context(renderer))) @@ -249,7 +258,7 @@ pub fn create_graphics_context( pub fn create_vulkan_allocator(drm: &Drm) -> Result, AllocatorError> { let instance = VulkanInstance::new(Level::Debug, *VULKAN_VALIDATION)?; - let device = instance.create_device(drm, false)?; + let device = instance.create_device(drm, false, false)?; let allocator = device.create_bo_allocator(drm)?; Ok(Rc::new(allocator)) } diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index cd139a56..39d8ca03 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -1,5 +1,6 @@ use { crate::{ + allocator::BufferObject, format::XRGB8888, gfx_apis::vulkan::{ VulkanError, @@ -9,10 +10,11 @@ use { map_extension_properties, }, }, - utils::on_drop::OnDrop, + utils::{bitflags::BitflagsExt, on_drop::OnDrop}, video::{ + dmabuf::DmaBufIds, drm::{Drm, sync_obj::SyncObjCtx}, - gbm::GbmDevice, + gbm::{GBM_BO_USE_RENDERING, GbmDevice}, }, }, ahash::AHashMap, @@ -37,7 +39,7 @@ use { PhysicalDeviceDrmPropertiesEXT, PhysicalDeviceDynamicRenderingFeatures, PhysicalDeviceExternalSemaphoreInfo, PhysicalDeviceProperties, PhysicalDeviceProperties2, PhysicalDeviceSynchronization2Features, - PhysicalDeviceTimelineSemaphoreFeatures, + PhysicalDeviceTimelineSemaphoreFeatures, PhysicalDeviceType, PhysicalDeviceUniformBufferStandardLayoutFeatures, PhysicalDeviceVulkan12Properties, Queue, QueueFamilyProperties2, QueueFlags, QueueGlobalPriorityKHR, }, @@ -49,7 +51,7 @@ use { rc::Rc, sync::Arc, }, - uapi::Ustr, + uapi::{Ustr, c::O_RDWR}, vk::QueueFamilyGlobalPriorityPropertiesKHR, }; @@ -208,6 +210,24 @@ impl VulkanInstance { Err(VulkanError::NoDeviceFound(dev)) } + fn find_software_renderer(&self) -> Result { + let phy_devs = unsafe { self.instance.enumerate_physical_devices() }; + let phy_devs = match phy_devs { + Ok(d) => d, + Err(e) => return Err(VulkanError::EnumeratePhysicalDevices(e)), + }; + for phy_dev in phy_devs { + let props = unsafe { self.instance.get_physical_device_properties(phy_dev) }; + if props.api_version < API_VERSION { + continue; + } + if props.device_type == PhysicalDeviceType::CPU { + return Ok(phy_dev); + } + } + Err(VulkanError::NoSoftwareRenderer) + } + fn find_queues( &self, phy_dev: PhysicalDevice, @@ -316,6 +336,7 @@ impl VulkanInstance { self: &Rc, drm: &Drm, mut high_priority: bool, + software: bool, ) -> Result, VulkanError> { let render_node = drm .get_render_node() @@ -323,7 +344,10 @@ impl VulkanInstance { .ok_or(VulkanError::NoRenderNode) .map(Rc::new)?; let gbm = GbmDevice::new(drm).map_err(VulkanError::Gbm)?; - let phy_dev = self.find_dev(drm)?; + let phy_dev = match software { + true => self.find_software_renderer()?, + false => self.find_dev(drm)?, + }; let extensions = self.get_device_extensions(phy_dev)?; for &ext in REQUIRED_DEVICE_EXTENSIONS { if extensions.not_contains_key(ext) { @@ -440,6 +464,23 @@ impl VulkanInstance { if !supports_xrgb8888 { return Err(VulkanError::XRGB8888); } + if software { + let bo = gbm + .create_bo( + &DmaBufIds::default(), + 1, + 1, + XRGB8888, + formats.get(&XRGB8888.drm).unwrap().modifiers.keys(), + GBM_BO_USE_RENDERING, + ) + .map_err(VulkanError::AllocGbm)?; + let fl = uapi::fcntl_getfl(bo.dmabuf().planes[0].fd.raw()) + .map_err(|e| VulkanError::GetFl(e.into()))?; + if fl.not_contains(O_RDWR) { + return Err(VulkanError::SoftwareRendererNotUsable); + } + } destroy_device.forget(); let external_memory_fd = external_memory_fd::Device::new(&self.instance, &device); let external_semaphore_fd = external_semaphore_fd::Device::new(&self.instance, &device); diff --git a/src/video/gbm.rs b/src/video/gbm.rs index 4d88814b..8d10f486 100644 --- a/src/video/gbm.rs +++ b/src/video/gbm.rs @@ -235,7 +235,7 @@ impl GbmDevice { if modifiers.is_empty() { return Err(GbmError::NoModifier); } - let (modifiers, n_modifiers) = if modifiers == [INVALID_MODIFIER] { + let (modifiers_ptr, n_modifiers) = if modifiers == [INVALID_MODIFIER] { (ptr::null(), 0) } else { usage &= !GBM_BO_USE_LINEAR; @@ -246,7 +246,7 @@ impl GbmDevice { width as _, height as _, format.drm, - modifiers, + modifiers_ptr, n_modifiers, usage, ); @@ -255,8 +255,8 @@ impl GbmDevice { } let bo = BoHolder { bo }; let mut dma = export_bo(dma_buf_ids, bo.bo)?; - if modifiers.is_null() { - dma.modifier = INVALID_MODIFIER; + if let [modifier] = *modifiers { + dma.modifier = modifier; } Ok(GbmBo { bo, dmabuf: dma }) }