1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
/* SPDX-License-Identifier: LGPL-2.1-or-later
Copyright (c) 2022 Purism, SPC
Copyright (c) 2024 DorotaC
*/
/*! Headless (GBM) EGL context.
*/
use crate::egl_ext as ext;
use dma_boom::DmaBuf;
use ext::ExtPlatformBaseInstance;
use gbm::AsRaw;
use khronos_egl as egl;
use std::ffi::c_void;
use std::fs;
use std::fs::File;
use std::ops::Deref;
use std::ptr;
use std::sync::Arc;
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
use super::DmabufImportError;
use super::OwnedDmabufImage;
// TODO: should this be Clone? Is concurrent mutable access to raw display and context bad?
// Currently, Backend needs a copy, while a copy remains in Facade.
/// GBM EGL GPU context
#[derive(Clone, Debug)]
pub struct Egl {
display: egl::Display,
context: egl::Context,
config: egl::Config,
device: Arc<gbm::Device<File>>,
}
impl Egl {
unsafe fn make_current(&self) {
egl::Instance::new(egl::Static)
.make_current(
self.display,
None,
None,
Some(self.context),
)
.unwrap()
}
unsafe fn release_current(&self) {
egl::Instance::new(egl::Static)
.make_current(
self.display,
None,
None,
None,
)
.unwrap()
}
}
/// A shared reference to a GBM EGL GPU context.
#[derive(Clone)]
pub struct ContextRef {
pub egl: Egl,
/// Number of times make_current was called.
// This counts values across threads, even though 2 make_current calls betwen threads are trouble.
// It's because ContextRef itself will move between threads on the C side, and the data structures must survive that.
// It's easier to be synced across threads than to reflect the restriction.
current: Arc<AtomicUsize>,
}
impl ContextRef {
pub fn new() -> Self {
let egl_calls = egl::Instance::new(egl::Static);
let gbm = gbm::Device::new(
// WARNING: Rust is said to always use CLOEXEC, so it's not specified here. It's still needed!
// https://github.com/rust-lang/rust/pull/27971
fs::OpenOptions::new().read(true).write(true).open("/dev/dri/renderD128").unwrap()
).unwrap();
egl_calls.bind_api(egl::OPENGL_API).unwrap();
// Older API versions don't have get_platform_display (TODO: check, this is from memory of Librem 5), so use "_ext" despite the reimplementation.
let display: egl::Display = unsafe { egl_calls.get_platform_display_ext(
ext::mesa_platform_gbm::PLATFORM_GBM_MESA,
gbm.as_raw() as _,
&[egl::ATTRIB_NONE],
)}.unwrap();
egl_calls.initialize(display).unwrap();
// Set context
// TODO: try EGL_KHR_no_config_context
// get an appropriate EGL frame buffer configuration
let configs = {
let mut configs = Vec::with_capacity(32);
egl_calls.choose_config(
display,
&[
egl::BUFFER_SIZE, 32,
egl::DEPTH_SIZE, egl::DONT_CARE,
egl::STENCIL_SIZE, egl::DONT_CARE,
egl::RENDERABLE_TYPE, egl::OPENGL_ES2_BIT,
egl::SURFACE_TYPE, egl::WINDOW_BIT,
egl::NONE,
],
&mut configs,
).unwrap();
configs
};
// Find a config whose native visual ID is the desired GBM format.
let config = configs.into_iter().find(|config|
egl_calls.get_config_attrib(
display,
*config,
egl::NATIVE_VISUAL_ID
).unwrap() == gbm::Format::Argb8888 as i32
).unwrap();
let context = egl_calls
.create_context(
display,
config,
None,
&[
egl::CONTEXT_MAJOR_VERSION, 1,
ext::CONTEXT_FLAGS_KHR, ext::CONTEXT_OPENGL_DEBUG_BIT_KHR,
egl::NONE,
],
).unwrap();
Self {
egl: Egl { context, config, device: Arc::new(gbm), display },
current: Arc::new(AtomicUsize::new(0)),
}
}
/// Only needed for raw::backend.
/// Glium doesn't know that CurrentContext needs to be held in scope.
/// Unsafe because it requires a manual release.
pub unsafe fn force_make_current(&self) {
if self.current.load(atomic::Ordering::SeqCst) == 0 {
self.egl.make_current();
}
}
fn release_current(&self) {
if self.current.fetch_sub(1, atomic::Ordering::SeqCst) == 0 {
unsafe { self.egl.release_current() };
}
}
pub fn get_proc_address(procname: &str) -> *const c_void {
egl::Instance::new(egl::Static)
.get_proc_address(procname)
.map(|a| a as *const c_void)
.unwrap_or(ptr::null())
}
pub fn get_device(&self) -> Arc<gbm::Device<File>>{
self.egl.device.clone()
}
}
impl EglContext for ContextRef {
fn get_display(&self) -> egl::Display {
self.egl.display
}
/// Acquires the context.
/// Relase by letting CurrentContext go out of scope.
/// CAUTION: this will crash if multiple contexts are interleaved in a single thread. (TODO? thread-local storage could help.)
fn make_current<'a>(&'a self) -> CurrentContext<'a> {
if self.current.fetch_add(1, atomic::Ordering::SeqCst) == 1 {
unsafe { self.egl.make_current() };
}
CurrentContext(&self)
}
}
/// Automatically releases the context by going out of scope.
pub struct CurrentContext<'a>(&'a ContextRef);
impl<'a> Deref for CurrentContext<'a> {
type Target = ContextRef;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<'a> Drop for CurrentContext<'a> {
fn drop(&mut self) {
self.0.release_current();
}
}
/// WIP: this is to find out what import_dmabuf actually needs
pub trait EglContext {
fn make_current<'a>(&'a self) -> CurrentContext<'a>;
fn get_display(&self) -> egl::Display;
}
/// If you intend to import a surface as a rendering target, better use ARGB8888.
/// Otherwise the config won't match.
pub fn import_dmabuf(
egl: &impl EglContext,
fd: DmaBuf,
dimensions: (u32, u32),
fourcc: gbm::Format,
) -> Result<OwnedDmabufImage, DmabufImportError> {
let _current = egl.make_current();
Ok(OwnedDmabufImage {
image: unsafe {
super::import_dmabuf_display(egl.get_display(), &fd, dimensions, fourcc)
}?,
dmafd: fd,
})
}