diff --git a/Cargo.lock b/Cargo.lock index e87a5900..36d1c769 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "futures" version = "0.3.19" @@ -229,6 +238,9 @@ dependencies = [ "log", "num-derive", "num-traits", + "once_cell", + "rand", + "renderdoc", "repc", "thiserror", "uapi", @@ -317,6 +329,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro2" version = "1.0.36" @@ -335,6 +353,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + [[package]] name = "regex" version = "1.5.4" @@ -358,6 +416,27 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "renderdoc" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42e14087d51efd3b42eb341e37b6f320af2b0750519ea849cb68bb7289643ed" +dependencies = [ + "bitflags", + "float-cmp", + "libloading", + "once_cell", + "renderdoc-sys", + "winapi", + "wio", +] + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + [[package]] name = "repc" version = "0.1.1" @@ -500,6 +579,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "xcb-dl" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 3d6b242b..6a2e862b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,9 @@ xcb-dl-util = { version = "0.2.0", features = ["xcb_shm", "xcb_xinput", "xcb_xkb libloading = "0.7.2" bstr = "0.2.17" isnt = "0.1.0" +once_cell = "1.9.0" +rand = "0.8.4" +renderdoc = "0.10.1" [build-dependencies] repc = "0.1.1" diff --git a/build.rs b/build.rs index d1ee26be..539db96c 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,5 @@ use repc::layout::{Type, TypeVariant}; +use std::fmt::Write as FmtWrite; use std::fs::{File, OpenOptions}; use std::io::BufWriter; use std::io::Write; @@ -58,6 +59,144 @@ fn write_ty(f: &mut W, vals: &[u32], ty: &str) -> anyhow::Result<()> { Ok(()) } +fn write_egl_procs(f: &mut W) -> anyhow::Result<()> { + let map = [ + ( + "eglGetPlatformDisplayEXT", + "EGLDisplay", + &[ + ("platform", "EGLenum"), + ("native_display", "*mut u8"), + ("attrib_list", "*const EGLint"), + ][..], + ), + ( + "eglCreateImageKHR", + "EGLImageKHR", + &[ + ("dpy", "EGLDisplay"), + ("ctx", "EGLContext"), + ("target", "EGLenum"), + ("buffer", "EGLClientBuffer"), + ("attrib_list", "*const EGLint"), + ][..], + ), + ( + "eglDestroyImageKHR", + "EGLBoolean", + &[("dpy", "EGLDisplay"), ("image", "EGLImageKHR")][..], + ), + ( + "eglQueryDmaBufFormatsEXT", + "EGLBoolean", + &[ + ("dpy", "EGLDisplay"), + ("max_formats", "EGLint"), + ("formats", "*mut EGLint"), + ("num_formats", "*mut EGLint"), + ][..], + ), + ( + "eglQueryDmaBufModifiersEXT", + "EGLBoolean", + &[ + ("dpy", "EGLDisplay"), + ("format", "EGLint"), + ("max_modifiers", "EGLint"), + ("modifiers", "*mut EGLuint64KHR"), + ("external_only", "*mut EGLBoolean"), + ("num_modifiers", "*mut EGLint"), + ][..], + ), + ( + "eglDebugMessageControlKHR", + "EGLint", + &[ + ("callback", "EGLDEBUGPROCKHR"), + ("attrib_list", "*const EGLAttrib"), + ][..], + ), + ( + "eglQueryDisplayAttribEXT", + "EGLBoolean", + &[ + ("dpy", "EGLDisplay"), + ("attribute", "EGLint"), + ("value", "*mut EGLAttrib"), + ][..], + ), + ( + "eglQueryDeviceStringEXT", + "*const c::c_char", + &[("device", "EGLDeviceEXT"), ("name", "EGLint")][..], + ), + ( + "eglQueryDevicesEXT", + "EGLBoolean", + &[ + ("max_devices", "EGLint"), + ("devices", "*mut EGLDeviceEXT"), + ("num_devices", "*mut EGLint"), + ][..], + ), + ( + "glEGLImageTargetRenderbufferStorageOES", + "()", + &[("target", "GLenum"), ("image", "GLeglImageOES")][..], + ), + ]; + + writeln!(f, "use std::ptr;")?; + writeln!(f, "use super::sys::*;")?; + writeln!(f)?; + writeln!(f, "#[derive(Copy, Clone, Debug)]")?; + writeln!(f, "pub struct ExtProc {{")?; + for (name, _, _) in map.iter().copied() { + writeln!(f, " {}: *mut u8,", name)?; + } + writeln!(f, "}}")?; + writeln!(f)?; + writeln!(f, "impl ExtProc {{")?; + writeln!(f, " pub fn load() -> Self {{")?; + writeln!(f, " Self {{")?; + for (name, _, _) in map.iter().copied() { + writeln!( + f, + " {}: unsafe {{ eglGetProcAddress(\"{}\\0\".as_ptr() as _) }},", + name, name + )?; + } + writeln!(f, " }}")?; + writeln!(f, " }}")?; + for (name, ret, args) in map.iter().copied() { + let mut args_names = String::new(); + let mut args_full = String::new(); + let mut args_tys = String::new(); + for (name, ty) in args.iter().copied() { + write!(args_full, "{}: {}, ", name, ty)?; + write!(args_names, "{}, ", name)?; + write!(args_tys, "{}, ", ty)?; + } + writeln!(f)?; + writeln!( + f, + " pub(super) unsafe fn {}(&self, {}) -> {} {{", + name, args_full, ret + )?; + writeln!(f, " if self.{}.is_null() {{", name)?; + writeln!(f, " panic!(\"Could not load `{}`\");", name)?; + writeln!(f, " }}")?; + writeln!( + f, + " ptr::read(&self.{} as *const *mut u8 as *const unsafe extern fn({}) -> {})({})", + name, args_tys, ret, args_names + )?; + writeln!(f, " }}")?; + } + writeln!(f, "}}")?; + Ok(()) +} + fn main() -> anyhow::Result<()> { let mut f = open("pixman_tys.rs")?; write_ty(&mut f, pixman::FORMATS, "PixmanFormat")?; @@ -79,6 +218,9 @@ fn main() -> anyhow::Result<()> { )?; write_ty(&mut f, xkbcommon::XKB_KEY_DIRECTION, "xkb_key_direction")?; + let mut f = open("egl_procs.rs")?; + write_egl_procs(&mut f)?; + println!("cargo:rerun-if-changed=build.rs"); Ok(()) } diff --git a/src/backends/xorg/mod.rs b/src/backends/xorg/mod.rs index 852fbbe0..c6b27054 100644 --- a/src/backends/xorg/mod.rs +++ b/src/backends/xorg/mod.rs @@ -1,9 +1,18 @@ use crate::backend::{ BackendEvent, KeyState, Output, OutputId, ScrollAxis, Seat, SeatEvent, SeatId, }; +use crate::drm::drm::{Drm, DrmError}; +use crate::drm::gbm::{GbmDevice, GbmError, GBM_BO_USE_RENDERING}; +use crate::drm::{ModifiedFormat, INVALID_MODIFIER}; +use crate::egl::{EglContext, EglImage}; use crate::event_loop::{EventLoopDispatcher, EventLoopId}; use crate::fixed::Fixed; +use crate::format::XRGB8888; +use crate::gles2::gl::{GlFrameBuffer, GlRenderBuffer, GlTexture}; +use crate::gles2::sys::{glBindFramebuffer, glFlush, glViewport, GL_FRAMEBUFFER}; +use crate::gles2::GlesError; use crate::pixman::{Image, PixmanError}; +use crate::render::gles::{GlesRenderer, RENDERDOC}; use crate::render::pixman::PixmanRenderer; use crate::render::Renderer; use crate::servermem::{ServerMem, ServerMemError}; @@ -12,8 +21,10 @@ use crate::utils::clonecell::CloneCell; use crate::utils::copyhashmap::CopyHashMap; use crate::utils::ptr_ext::PtrExt; use crate::wheel::{WheelDispatcher, WheelId}; -use crate::{pixman, EventLoopError, State, WheelError}; +use crate::{gles2, pixman, EventLoopError, NumCell, State, WheelError}; +use gles2::egl; use isnt::std_1::primitive::IsntConstPtrExt; +use rand::Rng; use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::error::Error; @@ -21,7 +32,7 @@ use std::ops::Deref; use std::ptr; use std::rc::Rc; use thiserror::Error; -use uapi::c; +use uapi::{c, OwnedFd}; use xcb_dl::{ffi, Xcb, XcbDri3, XcbPresent, XcbShm, XcbXinput, XcbXkb}; use xcb_dl_util::error::{XcbError, XcbErrorParser}; use xcb_dl_util::xcb_box::XcbBox; @@ -30,6 +41,20 @@ use xcb_dl_util::xcb_box::XcbBox; pub enum XorgBackendError { #[error("The xcb connection is in an error state")] ErrorEvent, + #[error("The drm subsystem returned an error")] + DrmError(#[from] DrmError), + #[error("The gbm subsystem returned an error")] + GbmError(#[from] GbmError), + #[error("Could not find an EGL device that matches the display's DRM device")] + MissingEglDevice, + #[error("Could not import a dma-buf")] + ImportBuffer(#[source] XcbError), + #[error("The EGL device does not support the XRGB8888 format")] + XRGB8888, + #[error("Could not create an EGL context")] + CreateEgl(#[source] Box), + #[error(transparent)] + EglError(#[from] GlesError), #[error("Could not select input events")] CannotSelectInputEvents(#[source] XcbError), #[error("Could not select present events")] @@ -73,6 +98,7 @@ struct XcbCon { input_opcode: u8, present_opcode: u8, xkb: Box, + screen: ffi::xcb_screen_t, c: *mut ffi::xcb_connection_t, errors: XcbErrorParser, } @@ -85,15 +111,18 @@ impl XcbCon { let input = Box::new(XcbXinput::load_loose()?); let xkb = Box::new(XcbXkb::load_loose()?); let dri = Box::new(XcbDri3::load_loose()?); + let present = Box::new(XcbPresent::load_loose()?); let c = xcb.xcb_connect(ptr::null(), ptr::null_mut()); let errors = XcbErrorParser::new(&xcb, c); let mut con = Self { + screen: *xcb.xcb_setup_roots_iterator(xcb.xcb_get_setup(c)).data, xcb, shm, input, dri, + present, input_opcode: 0, present_opcode: 0, xkb, @@ -192,6 +221,28 @@ pub struct XorgBackend { outputs: CopyHashMap>, seats: CopyHashMap>, mouse_seats: CopyHashMap>, + ctx: Rc, + renderer: GlesRenderer, + gbm: GbmDevice, + r: Cell, + g: Cell, + b: Cell, +} + +fn get_drm(con: &XcbCon) -> Result { + unsafe { + let mut err = ptr::null_mut(); + let res = con.dri.xcb_dri3_open_reply( + con.c, + con.dri.xcb_dri3_open(con.c, con.screen.root, 0), + &mut err, + ); + let mut res = con.check(res, err)?; + assert!(res.nfd == 1); + let fd = *con.dri.xcb_dri3_open_reply_fds(con.c, &mut *res); + let fd = OwnedFd::new(fd); + Ok(Drm::new(fd.raw(), true)?) + } } impl XorgBackend { @@ -199,6 +250,26 @@ impl XorgBackend { unsafe { let con = XcbCon::new()?; + let drm = get_drm(&con)?; + let res = (|| { + let egl_dev = match egl::find_drm_device(&drm)? { + Some(d) => d, + None => return Err(XorgBackendError::MissingEglDevice), + }; + let dpy = egl_dev.create_display()?; + if !dpy.formats.contains_key(&XRGB8888.drm) { + return Err(XorgBackendError::XRGB8888); + } + let ctx = dpy.create_context()?; + Ok(ctx) + })(); + let ctx = match res { + Ok(r) => r, + Err(e) => return Err(XorgBackendError::CreateEgl(Box::new(e))), + }; + let renderer = ctx.with_current(|| GlesRenderer::new(&ctx))?; + let gbm = GbmDevice::new(&drm)?; + let fd = con.xcb.xcb_get_file_descriptor(con.c); let wheel_id = state.wheel.id(); @@ -211,6 +282,12 @@ impl XorgBackend { outputs: Default::default(), seats: Default::default(), mouse_seats: Default::default(), + ctx: ctx.clone(), + renderer, + gbm, + r: Cell::new(0.0), + g: Cell::new(0.0), + b: Cell::new(0.0), }); { @@ -226,7 +303,7 @@ impl XorgBackend { } } - state.wheel.periodic(wheel_id, 16_667, slf.clone())?; + // state.wheel.periodic(wheel_id, 16_667, slf.clone())?; // state.wheel.periodic(wheel_id, 1000_000, slf.clone())?; state.el.insert(slf.id, Some(fd), c::EPOLLIN, slf.clone())?; @@ -234,10 +311,69 @@ impl XorgBackend { slf.query_devices(ffi::XCB_INPUT_DEVICE_ALL_MASTER as _)?; slf.handle_events()?; + state.egl.set(Some(ctx.clone())); + Ok(slf) } } + fn create_images( + &self, + window: ffi::xcb_window_t, + width: i32, + height: i32, + ) -> Result<[XorgImage; 2], XorgBackendError> { + let format = ModifiedFormat { + format: XRGB8888, + modifier: INVALID_MODIFIER, + }; + let mut images = [None, None]; + for i in 0..2 { + let bo = self + .gbm + .create_bo(width, height, &format, GBM_BO_USE_RENDERING)?; + let dma = bo.dma(); + assert!(dma.planes.len() == 1); + let plane = dma.planes.first().unwrap(); + let size = plane.stride * dma.height as u32; + let fd = uapi::fcntl_dupfd_cloexec(plane.fd.raw(), 0).unwrap(); + let fb = if RENDERDOC { + unsafe { GlTexture::new(&self.ctx, XRGB8888, width, height)?.to_framebuffer()? } + } else { + let egl_img = self.ctx.dpy.import_dmabuf(dma)?; + let rb = GlRenderBuffer::from_image(&egl_img, &self.ctx)?; + rb.create_framebuffer()? + }; + let pixmap = unsafe { + let pixmap = self.con.xcb.xcb_generate_id(self.con.c); + let cookie = self.con.dri.xcb_dri3_pixmap_from_buffer_checked( + self.con.c, + pixmap, + window, + size, + dma.width as _, + dma.height as _, + plane.stride as _, + 24, + 32, + fd.unwrap(), + ); + if let Err(e) = self.con.check_cookie(cookie) { + return Err(XorgBackendError::ImportBuffer(e)); + } + pixmap + }; + images[i] = Some(XorgImage { + pixmap: Cell::new(pixmap), + fb: CloneCell::new(fb), + idle: Cell::new(true), + render_on_idle: Cell::new(false), + last_serial: Cell::new(0), + }); + } + Ok([images[0].take().unwrap(), images[1].take().unwrap()]) + } + fn add_output(self: &Rc) -> Result<(), XorgBackendError> { unsafe { let con = &self.con; @@ -247,6 +383,8 @@ impl XorgBackend { .data .deref(); let window_id = con.xcb.xcb_generate_id(con.c); + const WIDTH: i32 = 800; + const HEIGHT: i32 = 600; { let cookie = con.xcb.xcb_create_window_checked( con.c, @@ -255,8 +393,8 @@ impl XorgBackend { screen.root, 0, 0, - 800, - 600, + WIDTH as _, + HEIGHT as _, 0, ffi::XCB_WINDOW_CLASS_INPUT_OUTPUT as _, 0, @@ -267,6 +405,7 @@ impl XorgBackend { return Err(XorgBackendError::CreateWindow(e)); } } + let images = self.create_images(window_id, WIDTH, HEIGHT).unwrap(); let output = Rc::new(XorgOutput { id: self.state.output_ids.next(), backend: self.clone(), @@ -274,8 +413,12 @@ impl XorgBackend { removed: Cell::new(false), width: Cell::new(0), height: Cell::new(0), + serial: Default::default(), + next_msc: Cell::new(0), + next_image: Default::default(), image: RefCell::new(None), cb: CloneCell::new(None), + images, }); { let class = "i4\0i4\0"; @@ -356,6 +499,7 @@ impl XorgBackend { self.state .backend_events .push(BackendEvent::NewOutput(output.clone())); + self.present(&output); } Ok(()) } @@ -495,14 +639,104 @@ impl XorgBackend { let event = unsafe { (event as *const _ as *const ffi::xcb_present_complete_notify_event_t).deref() }; - let output = match self.outputs.get(&event.window) { + let window = event.window; + let output = match self.outputs.get(&window) { Some(o) => o, _ => return Ok(()), }; output.next_msc.set(event.msc + 1); + let image = &output.images[output.next_image.get() % output.images.len()]; + if image.idle.get() { + self.present(&output); + } else { + image.render_on_idle.set(true); + } Ok(()) } + fn handle_present_idle( + self: &Rc, + event: &ffi::xcb_ge_generic_event_t, + ) -> Result<(), XorgBackendError> { + let event = + unsafe { (event as *const _ as *const ffi::xcb_present_idle_notify_event_t).deref() }; + let output = match self.outputs.get(&event.window) { + Some(o) => o, + _ => return Ok(()), + }; + for image in &output.images { + if image.last_serial.get() == event.serial { + image.idle.set(true); + if image.render_on_idle.replace(false) { + self.present(&output); + } + } + } + Ok(()) + } + + fn present(&self, output: &Rc) { + // { + // let clients = self.state.clients.clients.borrow(); + // for client in clients.values() { + // let s = client.data.objects.surfaces.lock(); + // for s in s.values() { + // let mut fr = s.frame_requests.borrow_mut(); + // for cb in fr.drain(..) { + // s.client.dispatch_frame_requests.push(cb); + // } + // } + // } + // return; + // } + + let image = &output.images[output.next_image.fetch_add(1) % output.images.len()]; + let serial = output.serial.fetch_add(1); + + if let Some(node) = self.state.root.outputs.get(&output.id) { + let fb = image.fb.get(); + let mut renderer = self.renderer.render_fb(&fb); + self.ctx + .with_current(|| unsafe { + fb.bind(); + glViewport(0, 0, fb.width, fb.height); + // fb.clear(0.0, 0.0, 0.0, 1.0); + renderer.render_output(&node); + glFlush(); + Ok(()) + }) + .unwrap(); + } + + unsafe { + let cookie = self.con.present.xcb_present_pixmap_checked( + self.con.c, + output.window, + image.pixmap.get(), + serial, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + output.next_msc.get(), + 1, + 0, + 0, + ptr::null(), + ); + if let Err(e) = self.con.check_cookie(cookie) { + log::error!("Could not present image: {:?}", e); + return; + } + } + image.idle.set(false); + image.last_serial.set(serial); + } + fn handle_input_event( self: &Rc, event: &ffi::xcb_ge_generic_event_t, @@ -567,6 +801,12 @@ impl XorgBackend { event: &ffi::xcb_ge_generic_event_t, state: KeyState, ) -> Result<(), XorgBackendError> { + if state == KeyState::Pressed { + let mut rng = rand::thread_rng(); + self.r.set(rng.gen_range(0.0..1.0)); + self.g.set(rng.gen_range(0.0..1.0)); + self.b.set(rng.gen_range(0.0..1.0)); + } let event = unsafe { (event as *const _ as *const ffi::xcb_input_key_press_event_t).deref() }; if let Some(seat) = self.seats.get(&event.deviceid) { @@ -626,7 +866,10 @@ impl XorgBackend { event: &ffi::xcb_ge_generic_event_t, ) -> Result<(), XorgBackendError> { let event = unsafe { (event as *const _ as *const ffi::xcb_input_motion_event_t).deref() }; - let (win, seat) = match (self.outputs.get(&event.event), self.mouse_seats.get(&event.deviceid)) { + let (win, seat) = match ( + self.outputs.get(&event.event), + self.mouse_seats.get(&event.deviceid), + ) { (Some(a), Some(b)) => (a, b), _ => return Ok(()), }; @@ -639,6 +882,7 @@ impl XorgBackend { } fn handle_destroy(&self, event: &ffi::xcb_generic_event_t) -> Result<(), XorgBackendError> { + self.state.el.stop(); let event = unsafe { (event as *const _ as *const ffi::xcb_destroy_notify_event_t).deref() }; let output = match self.outputs.remove(&event.event) { @@ -658,56 +902,67 @@ impl XorgBackend { Some(o) => o, _ => return Ok(()), }; - let width = event.width as u32; - let height = event.height as u32; + let width = event.width as i32; + let height = event.height as i32; let mut changed = false; - changed |= output.width.replace(width as i32) != width as i32; - changed |= output.height.replace(height as i32) != height as i32; + changed |= output.width.replace(width) != width; + changed |= output.height.replace(height) != height; if changed { - let shm = Rc::new(ServerMem::new((width * height * 4) as usize)?); - let fd = shm.fd(); - let image = Image::new(shm, pixman::X8R8G8B8, width, height, width * 4)?; - *output.image.borrow_mut() = Some(image); unsafe { - let fd = match uapi::fcntl_dupfd_cloexec(fd, 0) { - Ok(fd) => fd, - Err(e) => return Err(XorgBackendError::DupfdFailed(e.into())), - }; - let shmseg = self.con.xcb.xcb_generate_id(self.con.c); - let cookie = - self.con - .shm - .xcb_shm_attach_fd_checked(self.con.c, shmseg, fd.unwrap(), 0); - self.con.check_cookie(cookie)?; - let pixmap = self.con.xcb.xcb_generate_id(self.con.c); - let cookie = self.con.shm.xcb_shm_create_pixmap( - self.con.c, - pixmap, - output.window, - width as _, - height as _, - 24, - shmseg, - 0, - ); - self.con.check_cookie(cookie)?; - let cookie = self.con.xcb.xcb_change_window_attributes_checked( - self.con.c, - output.window, - ffi::XCB_CW_BACK_PIXMAP, - &pixmap as *const _ as _, - ); - self.con.check_cookie(cookie)?; - self.con.xcb.xcb_free_pixmap(self.con.c, pixmap); - self.con.shm.xcb_shm_detach(self.con.c, shmseg); + let images = self.create_images(output.window, width, height).unwrap(); + for (new, old) in images.iter().zip(output.images.iter()) { + unsafe { + self.con.xcb.xcb_free_pixmap(self.con.c, old.pixmap.get()); + } + old.fb.set(new.fb.get()); + old.pixmap.set(new.pixmap.get()); + } } + // let shm = Rc::new(ServerMem::new((width * height * 4) as usize)?); + // let fd = shm.fd(); + // let image = Image::new(shm, pixman::X8R8G8B8, width, height, width * 4)?; + // *output.image.borrow_mut() = Some(image); + // unsafe { + // let fd = match uapi::fcntl_dupfd_cloexec(fd, 0) { + // Ok(fd) => fd, + // Err(e) => return Err(XorgBackendError::DupfdFailed(e.into())), + // }; + // let shmseg = self.con.xcb.xcb_generate_id(self.con.c); + // let cookie = + // self.con + // .shm + // .xcb_shm_attach_fd_checked(self.con.c, shmseg, fd.unwrap(), 0); + // self.con.check_cookie(cookie)?; + // let pixmap = self.con.xcb.xcb_generate_id(self.con.c); + // let cookie = self.con.shm.xcb_shm_create_pixmap( + // self.con.c, + // pixmap, + // output.window, + // width as _, + // height as _, + // 24, + // shmseg, + // 0, + // ); + // self.con.check_cookie(cookie)?; + // let cookie = self.con.xcb.xcb_change_window_attributes_checked( + // self.con.c, + // output.window, + // ffi::XCB_CW_BACK_PIXMAP, + // &pixmap as *const _ as _, + // ); + // self.con.check_cookie(cookie)?; + // self.con.xcb.xcb_free_pixmap(self.con.c, pixmap); + // self.con.shm.xcb_shm_detach(self.con.c, shmseg); + // } output.changed(); - self.render()?; + // self.render()?; } Ok(()) } fn render(&self) -> Result<(), XorgBackendError> { + return Ok(()); let outputs = self.outputs.lock(); for output in outputs.values() { let image = output.image.borrow(); @@ -774,15 +1029,20 @@ struct XorgOutput { removed: Cell, width: Cell, height: Cell, + serial: NumCell, next_msc: Cell, - next_image: Cell, - // images: [XorgImage; 2], + next_image: NumCell, + images: [XorgImage; 2], image: RefCell>>>, cb: CloneCell>>, } struct XorgImage { - + pixmap: Cell, + fb: CloneCell>, + idle: Cell, + render_on_idle: Cell, + last_serial: Cell, } impl Drop for XorgOutput { diff --git a/src/drm/dma.rs b/src/drm/dma.rs new file mode 100644 index 00000000..5aa1a388 --- /dev/null +++ b/src/drm/dma.rs @@ -0,0 +1,16 @@ +use crate::format::Format; +use uapi::OwnedFd; + +pub struct DmaBufPlane { + pub offset: u32, + pub stride: u32, + pub fd: OwnedFd, +} + +pub struct DmaBuf { + pub width: i32, + pub height: i32, + pub format: &'static Format, + pub modifier: u64, + pub planes: Vec, +} diff --git a/src/drm/drm.rs b/src/drm/drm.rs new file mode 100644 index 00000000..48bd1710 --- /dev/null +++ b/src/drm/drm.rs @@ -0,0 +1,395 @@ +use crate::utils::bitflags::BitflagsExt; +use crate::utils::debug_fn::debug_fn; +use crate::utils::ptr_ext::PtrExt; +use bstr::ByteSlice; +use std::ffi::{CStr, CString}; +use std::fmt::{Debug, Formatter}; +use std::ptr; +use thiserror::Error; +use uapi::c::c_char; +use uapi::{c, Errno, OwnedFd, Ustring}; + +#[derive(Debug, Error)] +pub enum DrmError { + #[error("Could not create a lease")] + CreateLeaseError(#[source] std::io::Error), + #[error("Could not reopen a node")] + ReopenNode(#[source] std::io::Error), + #[error("Could not retrieve the render node name")] + RenderNodeName, + #[error("Could not retrieve the device node name")] + DeviceNodeName, + #[error("Could not retrieve device")] + GetDevice(#[source] std::io::Error), +} + +const DRM_NODE_PRIMARY: c::c_int = 0; +const DRM_NODE_CONTROL: c::c_int = 1; +const DRM_NODE_RENDER: c::c_int = 2; +const DRM_NODE_MAX: c::c_int = 3; + +const DRM_BUS_PCI: c::c_int = 0; +const DRM_BUS_USB: c::c_int = 1; +const DRM_BUS_PLATFORM: c::c_int = 2; +const DRM_BUS_HOST1X: c::c_int = 3; + +#[link(name = "drm")] +extern "C" { + fn drmIsMaster(fd: c::c_int) -> c::c_int; + fn drmModeCreateLease( + fd: c::c_int, + o: *const u32, + num_objects: c::c_int, + flags: c::c_int, + lessee_id: *mut u32, + ) -> c::c_int; + fn drmGetNodeTypeFromFd(fd: c::c_int) -> c::c_int; + fn drmGetRenderDeviceNameFromFd(fd: c::c_int) -> *mut c::c_char; + fn drmGetDeviceNameFromFd2(fd: c::c_int) -> *mut c::c_char; + fn drmFreeDevice(device: *mut *mut drmDevice); + fn drmGetDevice(fd: c::c_int, device: *mut *mut drmDevice) -> c::c_int; +} + +fn render_node_name(fd: c::c_int) -> Result { + unsafe { + let name = drmGetRenderDeviceNameFromFd(fd); + if name.is_null() { + Err(DrmError::RenderNodeName) + } else { + Ok(CString::from_raw(name).into()) + } + } +} + +fn device_node_name(fd: c::c_int) -> Result { + unsafe { + let name = drmGetDeviceNameFromFd2(fd); + if name.is_null() { + Err(DrmError::DeviceNodeName) + } else { + Ok(CString::from_raw(name).into()) + } + } +} + +fn reopen(fd: c::c_int, allow_downgrade: bool) -> Result { + unsafe { + if drmIsMaster(fd) != 0 { + let mut lessee = 0; + let lease_fd = drmModeCreateLease(fd, ptr::null(), 0, c::O_CLOEXEC, &mut lessee); + if lease_fd >= 0 { + return Ok(OwnedFd::new(lease_fd)); + } + } + let path = if drmGetNodeTypeFromFd(fd) == DRM_NODE_RENDER { + uapi::format_ustr!("/proc/self/fd/{}", fd) + } else if allow_downgrade { + render_node_name(fd)? + } else { + device_node_name(fd)? + }; + match uapi::open(&path, c::O_RDWR | c::O_CLOEXEC, 0) { + Ok(f) => Ok(f), + Err(e) => Err(DrmError::ReopenNode(e.into())), + } + } +} + +pub struct Drm { + fd: OwnedFd, +} + +impl Drm { + pub fn new(fd: c::c_int, allow_downgrade: bool) -> Result { + Ok(Self { + fd: reopen(fd, allow_downgrade)?, + }) + } + + pub fn raw(&self) -> c::c_int { + self.fd.raw() + } + + pub fn dup_unprivileged(&self) -> Result { + Self::new(self.fd.raw(), true) + } + + pub fn get_device(&self) -> Result { + unsafe { + let mut dev = ptr::null_mut(); + if drmGetDevice(self.fd.raw(), &mut dev) < 0 { + return Err(DrmError::GetDevice(Errno::default().into())); + } + Ok(DrmDevice { dev }) + } + } +} + +#[repr(C)] +struct drmPciBusInfo { + domain: u16, + bus: u8, + dev: u8, + func: u8, +} + +#[repr(C)] +struct drmUsbBusInfo { + bus: u8, + dev: u8, +} + +const DRM_PLATFORM_DEVICE_NAME_LEN: usize = 512; + +#[repr(C)] +struct drmPlatformBusInfo { + fullname: [c::c_char; DRM_PLATFORM_DEVICE_NAME_LEN], +} + +const DRM_HOST1X_DEVICE_NAME_LEN: usize = 512; + +#[repr(C)] +struct drmHost1xBusInfo { + fullname: [c::c_char; DRM_HOST1X_DEVICE_NAME_LEN], +} + +#[repr(C)] +union businfo { + pci: *mut drmPciBusInfo, + usb: *mut drmUsbBusInfo, + platform: *mut drmPlatformBusInfo, + host1x: *mut drmHost1xBusInfo, +} + +#[repr(C)] +struct drmPciDeviceInfo { + vendor_id: u16, + device_id: u16, + subvendor_id: u16, + subdevice_id: u16, + revision_id: u8, +} + +#[repr(C)] +struct drmUsbDeviceInfo { + vendor: u16, + product: u16, +} + +#[repr(C)] +struct drmPlatformDeviceInfo { + compatible: *mut *mut c::c_char, +} + +#[repr(C)] +struct drmHost1xDeviceInfo { + compatible: *mut *mut c::c_char, +} + +#[repr(C)] +union deviceinfo { + pci: *mut drmPciDeviceInfo, + usb: *mut drmUsbDeviceInfo, + platform: *mut drmPlatformDeviceInfo, + host1x: *mut drmHost1xDeviceInfo, +} + +#[repr(C)] +struct drmDevice { + nodes: *mut *mut c::c_char, + available_nodes: c::c_int, + bustype: c::c_int, + businfo: businfo, + deviceinfo: deviceinfo, +} + +pub struct DrmDevice { + dev: *mut drmDevice, +} + +impl Drop for DrmDevice { + fn drop(&mut self) { + unsafe { + drmFreeDevice(&mut self.dev); + } + } +} + +impl DrmDevice { + pub fn nodes<'a>(&'a self) -> impl Iterator + 'a { + struct Iter<'a> { + next: usize, + dev: &'a DrmDevice, + } + impl<'a> Iterator for Iter<'a> { + type Item = (c::c_int, &'a CStr); + + fn next(&mut self) -> Option { + unsafe { + let dev = self.dev.dev.deref(); + while self.next < DRM_NODE_MAX as _ { + let idx = self.next; + self.next += 1; + if dev.available_nodes.contains(1 << idx) { + return Some((idx as _, CStr::from_ptr(*dev.nodes.add(idx)))); + } + } + None + } + } + } + Iter { next: 0, dev: self } + } +} + +impl Debug for DrmDevice { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + struct StrStr<'a> { + v: &'a [*mut c::c_char], + } + impl Debug for StrStr<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut list = f.debug_list(); + for &v in self.v { + if v.is_null() { + list.entry(&v); + } else { + unsafe { + list.entry(&CStr::from_ptr(v)); + } + } + } + list.finish() + } + } + impl<'a> StrStr<'a> { + fn from_nt(nt: *const *mut c_char) -> Self { + unsafe { + let mut num = 0; + let mut tmp = nt; + while !tmp.deref().is_null() { + num += 1; + tmp = tmp.add(1); + } + Self { + v: std::slice::from_raw_parts(nt, num), + } + } + } + } + let mut ds = f.debug_struct("DrmDevice"); + unsafe { + let dev = self.dev.deref(); + let nodes = std::slice::from_raw_parts(dev.nodes, DRM_NODE_MAX as _); + ds.field( + "available_nodes", + &debug_fn(|f| write!(f, "0b{:b}", dev.available_nodes)), + ); + ds.field("nodes", &StrStr { v: nodes }); + ds.field("bustype", &dev.bustype); + match dev.bustype { + DRM_BUS_PCI => { + ds.field( + "businfo", + &debug_fn(|f| { + let pci = dev.businfo.pci.deref(); + f.debug_struct("drmPciBusInfo") + .field("domain", &pci.domain) + .field("bus", &pci.bus) + .field("dev", &pci.dev) + .field("func", &pci.func) + .finish() + }), + ); + ds.field( + "deviceinfo", + &debug_fn(|f| { + let pci = dev.deviceinfo.pci.deref(); + f.debug_struct("drmPciDeviceInfo") + .field("vendor_id", &pci.vendor_id) + .field("device_id", &pci.device_id) + .field("subvendor_id", &pci.subvendor_id) + .field("subdevice_id", &pci.subdevice_id) + .field("revision_id", &pci.revision_id) + .finish() + }), + ); + } + DRM_BUS_USB => { + ds.field( + "businfo", + &debug_fn(|f| { + let usb = dev.businfo.usb.deref(); + f.debug_struct("drmUsbBusInfo") + .field("bus", &usb.bus) + .field("dev", &usb.dev) + .finish() + }), + ); + ds.field( + "deviceinfo", + &debug_fn(|f| { + let usb = dev.deviceinfo.usb.deref(); + f.debug_struct("drmUsbDeviceInfo") + .field("vendor", &usb.vendor) + .field("product", &usb.product) + .finish() + }), + ); + } + DRM_BUS_PLATFORM => { + ds.field( + "businfo", + &debug_fn(|f| { + let platform = dev.businfo.platform.deref(); + f.debug_struct("drmPlatformBusInfo") + .field( + "fullname", + &CStr::from_ptr(platform.fullname.as_ptr()) + .to_bytes() + .as_bstr(), + ) + .finish() + }), + ); + ds.field( + "deviceinfo", + &debug_fn(|f| { + let platform = dev.deviceinfo.platform.deref(); + f.debug_struct("drmPlatformDeviceInfo") + .field("compatible", &StrStr::from_nt(platform.compatible)) + .finish() + }), + ); + } + DRM_BUS_HOST1X => { + ds.field( + "businfo", + &debug_fn(|f| { + let host1x = dev.businfo.host1x.deref(); + f.debug_struct("drmHost1xBusInfo") + .field( + "fullname", + &CStr::from_ptr(host1x.fullname.as_ptr()) + .to_bytes() + .as_bstr(), + ) + .finish() + }), + ); + ds.field( + "deviceinfo", + &debug_fn(|f| { + let host1x = dev.deviceinfo.host1x.deref(); + f.debug_struct("drmHost1xDeviceInfo") + .field("compatible", &StrStr::from_nt(host1x.compatible)) + .finish() + }), + ); + } + _ => {} + } + ds.finish() + } + } +} diff --git a/src/drm/gbm.rs b/src/drm/gbm.rs new file mode 100644 index 00000000..f061eb78 --- /dev/null +++ b/src/drm/gbm.rs @@ -0,0 +1,169 @@ +use crate::drm::dma::{DmaBuf, DmaBufPlane}; +use crate::drm::drm::{Drm, DrmError}; +use crate::drm::{ModifiedFormat, INVALID_MODIFIER}; +use crate::format::formats; +use std::ptr; +use thiserror::Error; +use uapi::{c, OwnedFd}; + +#[derive(Debug, Error)] +pub enum GbmError { + #[error("The drm subsystem returned an error")] + Drm(#[from] DrmError), + #[error("Cloud not create a gbm device")] + CreateDevice, + #[error("Cloud not create a gbm buffer")] + CreateBo, + #[error("gbm buffer has an unknown format")] + UnknownFormat, + #[error("Could not retrieve a drm-buf fd")] + DrmFd, +} + +type Device = u8; +type Bo = u8; + +pub const GBM_BO_USE_SCANOUT: u32 = 1 << 0; +pub const GBM_BO_USE_CURSOR: u32 = 1 << 1; +pub const GBM_BO_USE_RENDERING: u32 = 1 << 2; +pub const GBM_BO_USE_WRITE: u32 = 1 << 3; +pub const GBM_BO_USE_LINEAR: u32 = 1 << 4; +pub const GBM_BO_USE_PROTECTED: u32 = 1 << 5; + +#[link(name = "gbm")] +extern "C" { + fn gbm_create_device(fd: c::c_int) -> *mut Device; + fn gbm_device_destroy(dev: *mut Device); + + fn gbm_bo_create_with_modifiers2( + dev: *mut Device, + width: u32, + height: u32, + format: u32, + modifiers: *const u64, + count: c::c_uint, + flags: u32, + ) -> *mut Bo; + fn gbm_bo_destroy(bo: *mut Bo); + fn gbm_bo_get_plane_count(bo: *mut Bo) -> c::c_int; + fn gbm_bo_get_width(bo: *mut Bo) -> u32; + fn gbm_bo_get_height(bo: *mut Bo) -> u32; + fn gbm_bo_get_stride(bo: *mut Bo) -> u32; + fn gbm_bo_get_modifier(bo: *mut Bo) -> u64; + fn gbm_bo_get_stride_for_plane(bo: *mut Bo, plane: c::c_int) -> u32; + fn gbm_bo_get_fd_for_plane(bo: *mut Bo, plane: c::c_int) -> c::c_int; + fn gbm_bo_get_offset(bo: *mut Bo, plane: c::c_int) -> u32; + fn gbm_bo_get_format(bo: *mut Bo) -> u32; + fn gbm_bo_get_bpp(bo: *mut Bo) -> u32; +} + +pub struct GbmDevice { + drm: Drm, + dev: *mut Device, +} + +struct BoHolder { + bo: *mut Bo, +} + +pub struct GbmBo { + bo: BoHolder, + dma: DmaBuf, +} + +unsafe fn export_bo(bo: *mut Bo) -> Result { + Ok(DmaBuf { + width: gbm_bo_get_width(bo) as _, + height: gbm_bo_get_height(bo) as _, + modifier: gbm_bo_get_modifier(bo), + format: { + let format = gbm_bo_get_format(bo); + match formats().get(&format).copied() { + Some(f) => f, + _ => return Err(GbmError::UnknownFormat), + } + }, + planes: { + let mut planes = vec![]; + for plane in 0..gbm_bo_get_plane_count(bo) { + let offset = gbm_bo_get_offset(bo, plane); + let stride = gbm_bo_get_stride_for_plane(bo, plane); + let fd = gbm_bo_get_fd_for_plane(bo, plane); + if fd < 0 { + return Err(GbmError::DrmFd); + } + planes.push(DmaBufPlane { + offset, + stride, + fd: OwnedFd::new(fd), + }) + } + planes + }, + }) +} + +impl GbmDevice { + pub fn new(drm: &Drm) -> Result { + let drm = drm.dup_unprivileged()?; + let dev = unsafe { gbm_create_device(drm.raw()) }; + if dev.is_null() { + Err(GbmError::CreateDevice) + } else { + Ok(Self { drm, dev }) + } + } + + pub fn create_bo( + &self, + width: i32, + height: i32, + format: &ModifiedFormat, + usage: u32, + ) -> Result { + unsafe { + let (modifiers, n_modifiers) = if format.modifier == INVALID_MODIFIER { + (ptr::null(), 0) + } else { + (&format.modifier as _, 1) + }; + let bo = gbm_bo_create_with_modifiers2( + self.dev, + width as _, + height as _, + format.format.drm, + modifiers, + n_modifiers, + usage, + ); + if bo.is_null() { + return Err(GbmError::CreateBo); + } + let bo = BoHolder { bo }; + let dma = export_bo(bo.bo)?; + Ok(GbmBo { bo, dma }) + } + } +} + +impl Drop for GbmDevice { + fn drop(&mut self) { + unsafe { + gbm_device_destroy(self.dev); + } + } +} + +impl GbmBo { + pub fn dma(&self) -> &DmaBuf { + &self.dma + } +} + +impl Drop for BoHolder { + fn drop(&mut self) { + unsafe { + gbm_bo_destroy(self.bo); + } + } +} diff --git a/src/drm/mod.rs b/src/drm/mod.rs new file mode 100644 index 00000000..9c9f4234 --- /dev/null +++ b/src/drm/mod.rs @@ -0,0 +1,14 @@ +use crate::format::Format; + +pub mod dma; +pub mod drm; +pub mod gbm; + +pub type Modifier = u64; + +pub const INVALID_MODIFIER: Modifier = 0x00ff_ffff_ffff_ffff; + +pub struct ModifiedFormat { + pub format: &'static Format, + pub modifier: Modifier, +} diff --git a/src/format.rs b/src/format.rs index 11f5d049..4786e49b 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,20 +1,29 @@ +use crate::gles2::sys::{GLint, GL_BGRA_EXT, GL_UNSIGNED_BYTE}; use crate::pixman; use ahash::AHashMap; +use once_cell::sync::Lazy; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Format { - pub id: u32, pub name: &'static str, pub bpp: u32, - pub pixman: pixman::Format, + pub pixman: Option, + pub gl_format: GLint, + pub gl_type: GLint, + pub drm: u32, + pub wl_id: Option, } -pub fn formats() -> AHashMap { +static FORMATS_MAP: Lazy> = Lazy::new(|| { let mut map = AHashMap::new(); for format in FORMATS { - assert!(map.insert(format.id, format).is_none()); + assert!(map.insert(format.drm, format).is_none()); } map +}); + +pub fn formats() -> &'static AHashMap { + &*FORMATS_MAP } #[allow(dead_code)] @@ -22,18 +31,41 @@ const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 { (a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24) } -static FORMATS: &[Format] = &[ +const ARGB8888_ID: u32 = 0; +const ARGB8888_DRM: u32 = fourcc_code('A', 'R', '2', '4'); + +const XRGB8888_ID: u32 = 1; +const XRGB8888_DRM: u32 = fourcc_code('X', 'R', '2', '4'); + +pub fn map_wayland_format_id(id: u32) -> u32 { + match id { + ARGB8888_ID => ARGB8888_DRM, + XRGB8888_ID => XRGB8888_DRM, + _ => id, + } +} + +pub static ARGB8888: &Format = &FORMATS[0]; +pub static XRGB8888: &Format = &FORMATS[1]; + +pub static FORMATS: &[Format] = &[ Format { - id: 0, name: "argb8888", bpp: 4, - pixman: pixman::A8R8G8B8, + pixman: Some(pixman::A8R8G8B8), + gl_format: GL_BGRA_EXT, + gl_type: GL_UNSIGNED_BYTE, + drm: ARGB8888_DRM, + wl_id: Some(ARGB8888_ID), }, Format { - id: 1, name: "xrgb8888", bpp: 4, - pixman: pixman::X8R8G8B8, + pixman: Some(pixman::X8R8G8B8), + gl_format: GL_BGRA_EXT, + gl_type: GL_UNSIGNED_BYTE, + drm: XRGB8888_DRM, + wl_id: Some(XRGB8888_ID), }, // Format { // id: fourcc_code('C', '8', ' ', ' '), diff --git a/src/gles2/egl.rs b/src/gles2/egl.rs new file mode 100644 index 00000000..3ed3e1a6 --- /dev/null +++ b/src/gles2/egl.rs @@ -0,0 +1,487 @@ +use super::ext::{get_client_ext, ClientExt}; +use super::ext_proc::ExtProc; +use super::sys::{ + eglBindAPI, EGLAttrib, EGLLabelKHR, EGLenum, EGLint, EGL_BAD_ACCESS, EGL_BAD_ALLOC, + EGL_BAD_ATTRIBUTE, EGL_BAD_CONFIG, EGL_BAD_CONTEXT, EGL_BAD_CURRENT_SURFACE, + EGL_BAD_DEVICE_EXT, EGL_BAD_DISPLAY, EGL_BAD_MATCH, EGL_BAD_NATIVE_PIXMAP, + EGL_BAD_NATIVE_WINDOW, EGL_BAD_PARAMETER, EGL_BAD_SURFACE, EGL_CONTEXT_LOST, + EGL_DEBUG_MSG_CRITICAL_KHR, EGL_DEBUG_MSG_ERROR_KHR, EGL_DEBUG_MSG_INFO_KHR, + EGL_DEBUG_MSG_WARN_KHR, EGL_NONE, EGL_NOT_INITIALIZED, EGL_OPENGL_ES_API, EGL_SUCCESS, + EGL_TRUE, +}; +use super::GlesError; +use crate::drm::dma::DmaBuf; +use crate::drm::drm::Drm; +use crate::drm::INVALID_MODIFIER; +use crate::format::{formats, Format}; +use crate::gles2::ext::{ + get_device_ext, get_display_ext, get_gl_ext, DeviceExt, DisplayExt, GlExt, +}; +use crate::gles2::gl::GlTexture; +use crate::gles2::sys::{ + eglCreateContext, eglDestroyContext, eglInitialize, eglMakeCurrent, eglTerminate, + glBindFramebuffer, glBindRenderbuffer, glBindTexture, glCheckFramebufferStatus, glClear, + glClearColor, glDeleteFramebuffers, glDeleteRenderbuffers, glFlush, glFramebufferRenderbuffer, + glGenFramebuffers, glGenRenderbuffers, glGenTextures, glPixelStorei, glTexImage2D, + glTexParameteri, EGLClientBuffer, EGLConfig, EGLContext, EGLDeviceEXT, EGLDisplay, EGLImageKHR, + EGLSurface, GLeglImageOES, GLint, GLuint, EGL_CONTEXT_CLIENT_VERSION, + EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, + EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DRM_DEVICE_FILE_EXT, EGL_FALSE, EGL_HEIGHT, + EGL_IMAGE_PRESERVED_KHR, EGL_LINUX_DMA_BUF_EXT, EGL_LINUX_DRM_FOURCC_EXT, + EGL_PLATFORM_DEVICE_EXT, EGL_WIDTH, GL_CLAMP_TO_EDGE, GL_COLOR_ATTACHMENT0, + GL_COLOR_BUFFER_BIT, GL_FRAMEBUFFER, GL_FRAMEBUFFER_COMPLETE, GL_RENDERBUFFER, GL_TEXTURE_2D, + GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_UNPACK_ROW_LENGTH_EXT, +}; +use crate::rect::Rect; +use ahash::AHashMap; +use bstr::ByteSlice; +use log::Level; +use once_cell::sync::Lazy; +use std::cell::Cell; +use std::ffi::CStr; +use std::ptr; +use std::rc::Rc; +use uapi::c; + +#[thread_local] +pub(super) static PROCS: Lazy = Lazy::new(|| ExtProc::load()); + +#[thread_local] +pub(super) static EXTS: Lazy = Lazy::new(|| get_client_ext()); + +pub fn init() -> Result<(), GlesError> { + if !EXTS.contains(ClientExt::EXT_PLATFORM_BASE) { + return Err(GlesError::ExtPlatformBase); + } + if !EXTS.device_query() { + return Err(GlesError::DeviceQuery); + } + if !EXTS.device_enumeration() { + return Err(GlesError::DeviceEnumeration); + } + if EXTS.contains(ClientExt::KHR_DEBUG) { + let attrib: &[EGLAttrib] = &[ + EGL_DEBUG_MSG_CRITICAL_KHR as _, + EGL_TRUE as _, + EGL_DEBUG_MSG_ERROR_KHR as _, + EGL_TRUE as _, + EGL_DEBUG_MSG_WARN_KHR as _, + EGL_TRUE as _, + EGL_DEBUG_MSG_INFO_KHR as _, + EGL_TRUE as _, + EGL_NONE as _, + ]; + unsafe { + PROCS.eglDebugMessageControlKHR(egl_log, attrib.as_ptr()); + } + } + if unsafe { eglBindAPI(EGL_OPENGL_ES_API) } != EGL_TRUE { + return Err(GlesError::BindFailed); + } + Ok(()) +} + +pub fn find_drm_device(drm: &Drm) -> Result, GlesError> { + let drm_dev = drm.get_device()?; + for device in query_devices()? { + if device.exts.contains(DeviceExt::EXT_DEVICE_DRM) { + let device_file = device.query_string(EGL_DRM_DEVICE_FILE_EXT)?; + for (_, name) in drm_dev.nodes() { + if device_file == name { + return Ok(Some(device)); + } + } + } + } + Ok(None) +} + +pub fn query_devices() -> Result, GlesError> { + if !EXTS.device_enumeration() { + return Err(GlesError::DeviceEnumeration); + } + unsafe { + let mut devices = vec![]; + let mut num_devices = 0; + let res = PROCS.eglQueryDevicesEXT(num_devices, ptr::null_mut(), &mut num_devices); + if res != EGL_TRUE { + return Err(GlesError::QueryDevices); + } + devices.reserve_exact(num_devices as usize); + let res = PROCS.eglQueryDevicesEXT(num_devices, devices.as_mut_ptr(), &mut num_devices); + if res != EGL_TRUE { + return Err(GlesError::QueryDevices); + } + devices.set_len(num_devices as usize); + Ok(devices + .into_iter() + .map(|d| EglDevice { + exts: get_device_ext(d), + dev: d, + }) + .collect()) + } +} + +#[derive(Debug, Copy, Clone)] +pub struct EglDevice { + pub exts: DeviceExt, + dev: EGLDeviceEXT, +} + +#[derive(Debug, Clone)] +pub struct EglDisplay { + pub exts: DisplayExt, + pub formats: AHashMap, + dev: EglDevice, + dpy: EGLDisplay, +} + +#[derive(Debug, Clone)] +pub struct EglContext { + pub dpy: Rc, + pub ext: GlExt, + ctx: EGLContext, + + pub tex_prog: GLuint, + pub tex_tex: GLint, + pub tex_texcoord: GLint, + pub tex_pos: GLint, +} + +impl EglDisplay { + pub fn create_context(self: &Rc) -> Result, GlesError> { + let attrib = [EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE]; + unsafe { + let ctx = eglCreateContext( + self.dpy, + EGLConfig::none(), + EGLContext::none(), + attrib.as_ptr(), + ); + if ctx.is_none() { + return Err(GlesError::CreateContext); + } + let mut ctx = EglContext { + dpy: self.clone(), + ext: GlExt::empty(), + ctx, + tex_prog: 0, + tex_tex: 0, + tex_texcoord: 0, + tex_pos: 0, + }; + ctx.ext = ctx.with_current(|| Ok(get_gl_ext()))?; + // if !ctx.ext.contains(GlExt::GL_OES_EGL_IMAGE) { + // return Err(GlesError::OesEglImage); + // } + Ok(Rc::new(ctx)) + } + } + + pub fn import_dmabuf(self: &Rc, buf: &DmaBuf) -> Result, GlesError> { + struct PlaneKey { + fd: EGLint, + offset: EGLint, + pitch: EGLint, + mod_lo: EGLint, + mod_hi: EGLint, + } + const PLANE_KEYS: [PlaneKey; 4] = [ + PlaneKey { + fd: EGL_DMA_BUF_PLANE0_FD_EXT, + offset: EGL_DMA_BUF_PLANE0_OFFSET_EXT, + pitch: EGL_DMA_BUF_PLANE0_PITCH_EXT, + mod_lo: EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + mod_hi: EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, + }, + PlaneKey { + fd: EGL_DMA_BUF_PLANE1_FD_EXT, + offset: EGL_DMA_BUF_PLANE1_OFFSET_EXT, + pitch: EGL_DMA_BUF_PLANE1_PITCH_EXT, + mod_lo: EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + mod_hi: EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, + }, + PlaneKey { + fd: EGL_DMA_BUF_PLANE2_FD_EXT, + offset: EGL_DMA_BUF_PLANE2_OFFSET_EXT, + pitch: EGL_DMA_BUF_PLANE2_PITCH_EXT, + mod_lo: EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + mod_hi: EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, + }, + PlaneKey { + fd: EGL_DMA_BUF_PLANE3_FD_EXT, + offset: EGL_DMA_BUF_PLANE3_OFFSET_EXT, + pitch: EGL_DMA_BUF_PLANE3_PITCH_EXT, + mod_lo: EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, + mod_hi: EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, + }, + ]; + + let mut attribs = vec![]; + attribs.extend_from_slice(&[EGL_WIDTH, buf.width]); + attribs.extend_from_slice(&[EGL_HEIGHT, buf.height]); + attribs.extend_from_slice(&[EGL_LINUX_DRM_FOURCC_EXT, buf.format.drm as _]); + attribs.extend_from_slice(&[EGL_IMAGE_PRESERVED_KHR, EGL_TRUE as _]); + for (key, plane) in PLANE_KEYS.iter().zip(buf.planes.iter()) { + attribs.extend_from_slice(&[key.fd, plane.fd.raw()]); + attribs.extend_from_slice(&[key.pitch, plane.stride as _]); + attribs.extend_from_slice(&[key.offset, plane.offset as _]); + if buf.modifier != INVALID_MODIFIER { + attribs.extend_from_slice(&[key.mod_lo, buf.modifier as i32]); + attribs.extend_from_slice(&[key.mod_hi, (buf.modifier >> 32) as i32]); + } + } + attribs.push(EGL_NONE); + let img = unsafe { + PROCS.eglCreateImageKHR( + self.dpy, + EGLContext::none(), + EGL_LINUX_DMA_BUF_EXT as _, + EGLClientBuffer::none(), + attribs.as_ptr(), + ) + }; + if img.is_none() { + return Err(GlesError::CreateImage); + } + Ok(Rc::new(EglImage { + dpy: self.clone(), + img, + width: buf.width, + height: buf.height, + })) + } +} + +pub struct EglImage { + dpy: Rc, + pub(super) img: EGLImageKHR, + pub width: i32, + pub height: i32, +} + +impl Drop for EglImage { + fn drop(&mut self) { + unsafe { + if PROCS.eglDestroyImageKHR(self.dpy.dpy, self.img) == EGL_FALSE { + log::warn!("`eglDestroyImageKHR` failed"); + } + } + } +} + +impl Drop for EglContext { + fn drop(&mut self) { + unsafe { + if eglDestroyContext(self.dpy.dpy, self.ctx) != EGL_TRUE { + log::warn!("`eglDestroyContext` failed"); + } + } + } +} + +#[thread_local] +static mut CURRENT: EGLContext = EGLContext::none(); + +impl EglContext { + #[inline] + pub fn with_current Result>( + &self, + f: F, + ) -> Result { + unsafe { + if CURRENT == self.ctx { + return f(); + } + self.with_current_slow(f) + } + } + + #[cold] + unsafe fn with_current_slow Result>( + &self, + f: F, + ) -> Result { + if eglMakeCurrent( + self.dpy.dpy, + EGLSurface::none(), + EGLSurface::none(), + self.ctx, + ) == EGL_FALSE + { + return Err(GlesError::MakeCurrent); + } + let prev = CURRENT; + CURRENT = self.ctx; + let res = f(); + if eglMakeCurrent(self.dpy.dpy, EGLSurface::none(), EGLSurface::none(), prev) == EGL_FALSE { + panic!("Could not restore EGLContext"); + } + CURRENT = prev; + res + } +} + +impl Drop for EglDisplay { + fn drop(&mut self) { + unsafe { + if eglTerminate(self.dpy) != EGL_TRUE { + log::warn!("`eglTerminate` failed"); + } + } + } +} + +impl EglDevice { + pub fn query_string(&self, name: EGLint) -> Result<&'static CStr, GlesError> { + unsafe { + let res = PROCS.eglQueryDeviceStringEXT(self.dev, name); + if res.is_null() { + return Err(GlesError::DeviceQueryString); + } + Ok(CStr::from_ptr(res)) + } + } + + pub fn create_display(&self) -> Result, GlesError> { + unsafe { + let dpy = PROCS.eglGetPlatformDisplayEXT( + EGL_PLATFORM_DEVICE_EXT as _, + self.dev.0, + ptr::null(), + ); + if dpy.is_none() { + return Err(GlesError::GetDisplay); + } + let mut dpy = EglDisplay { + exts: DisplayExt::empty(), + formats: AHashMap::new(), + dev: *self, + dpy, + }; + let mut major = 0; + let mut minor = 0; + if eglInitialize(dpy.dpy, &mut major, &mut minor) != EGL_TRUE { + return Err(GlesError::Initialize); + } + dpy.exts = get_display_ext(dpy.dpy); + if !dpy.exts.intersects(DisplayExt::KHR_IMAGE_BASE) { + return Err(GlesError::ImageBase); + } + if !dpy + .exts + .intersects(DisplayExt::EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS) + { + return Err(GlesError::DmaBufImport); + } + if !dpy + .exts + .intersects(DisplayExt::KHR_NO_CONFIG_CONTEXT | DisplayExt::MESA_CONFIGLESS_CONTEXT) + { + return Err(GlesError::ConfiglessContext); + } + if !dpy.exts.intersects(DisplayExt::KHR_SURFACELESS_CONTEXT) { + return Err(GlesError::SurfacelessContext); + } + dpy.formats = query_formats(dpy.dpy)?; + + Ok(Rc::new(dpy)) + } + } +} + +unsafe fn query_formats(dpy: EGLDisplay) -> Result, GlesError> { + let mut vec = vec![]; + let mut num = 0; + let res = PROCS.eglQueryDmaBufFormatsEXT(dpy, num, ptr::null_mut(), &mut num); + if res != EGL_TRUE { + return Err(GlesError::QueryDmaBufFormats); + } + vec.reserve_exact(num as usize); + let res = PROCS.eglQueryDmaBufFormatsEXT(dpy, num, vec.as_mut_ptr(), &mut num); + if res != EGL_TRUE { + return Err(GlesError::QueryDmaBufFormats); + } + vec.set_len(num as usize); + let mut res = AHashMap::new(); + let formats = formats(); + for fmt in vec { + if let Some(format) = formats.get(&(fmt as u32)) { + res.insert(format.drm, *format); + } + } + Ok(res) +} + +unsafe extern "C" fn egl_log( + error: EGLenum, + command: *const c::c_char, + message_type: EGLint, + _thread_label: EGLLabelKHR, + _object_label: EGLLabelKHR, + message: *const c::c_char, +) { + let level = match message_type { + EGL_DEBUG_MSG_CRITICAL_KHR => Level::Error, + EGL_DEBUG_MSG_ERROR_KHR => Level::Error, + EGL_DEBUG_MSG_WARN_KHR => Level::Warn, + EGL_DEBUG_MSG_INFO_KHR => Level::Info, + _ => Level::Warn, + }; + let command = if !command.is_null() { + CStr::from_ptr(command).to_bytes() + } else { + b"none" + }; + let message = if !message.is_null() { + CStr::from_ptr(message).to_bytes() + } else { + b"none" + }; + let err_name = error_name(error); + log::log!( + level, + "EGL: command: {}, error: {} (0x{:x}), message: {}", + command.as_bstr(), + err_name, + error, + message.as_bstr() + ); +} + +fn error_name(error: EGLenum) -> &'static str { + macro_rules! en { + ($($name:ident,)*) => { + match error as _ { + $($name => stringify!($name),)* + _ => "unknown", + } + } + } + en! { + EGL_SUCCESS, + EGL_NOT_INITIALIZED, + EGL_BAD_ACCESS, + EGL_BAD_ALLOC, + EGL_BAD_ATTRIBUTE, + EGL_BAD_CONTEXT, + EGL_BAD_CONFIG, + EGL_BAD_CURRENT_SURFACE, + EGL_BAD_DISPLAY, + EGL_BAD_DEVICE_EXT, + EGL_BAD_SURFACE, + EGL_BAD_MATCH, + EGL_BAD_PARAMETER, + EGL_BAD_NATIVE_PIXMAP, + EGL_BAD_NATIVE_WINDOW, + EGL_CONTEXT_LOST, + } +} diff --git a/src/gles2/ext.rs b/src/gles2/ext.rs new file mode 100644 index 00000000..b961dd9f --- /dev/null +++ b/src/gles2/ext.rs @@ -0,0 +1,172 @@ +use crate::gles2::egl::PROCS; +use crate::gles2::sys::{ + eglQueryString, glGetString, EGLDeviceEXT, EGLDisplay, EGL_EXTENSIONS, GL_EXTENSIONS, +}; +use ahash::AHashSet; +use bstr::ByteSlice; +use std::ffi::CStr; +use std::ops::BitOrAssign; +use std::str; +use uapi::c; + +unsafe fn get_extensions(ext: *const c::c_char) -> Option> { + if ext.is_null() { + return None; + } + let mut res = AHashSet::new(); + let ext = CStr::from_ptr(ext).to_bytes(); + for part in ext.split_str(" ") { + let name = part.trim(); + if name.len() > 0 { + if let Ok(s) = str::from_utf8(name) { + res.insert(s.to_string()); + } + } + } + Some(res) +} + +unsafe fn get_dpy_extensions(dpy: EGLDisplay) -> Option> { + let ext = eglQueryString(dpy, EGL_EXTENSIONS); + get_extensions(ext) +} + +fn get_typed_ext(exts: &AHashSet, mut base: T, map: &[(&str, T)]) -> T +where + T: BitOrAssign + Copy, +{ + for (name, ext) in map.iter().copied() { + if exts.contains(name) { + base |= ext; + } + } + base +} + +bitflags::bitflags! { + pub struct ClientExt: u32 { + const EXT_CLIENT_EXTENSION = 1 << 0; + const EXT_PLATFORM_BASE = 1 << 1; + const KHR_PLATFORM_GBM = 1 << 2; + const EXT_PLATFORM_DEVICE = 1 << 3; + const EXT_DEVICE_BASE = 1 << 4; + const EXT_DEVICE_ENUMERATION = 1 << 5; + const EXT_DEVICE_QUERY = 1 << 6; + const KHR_DEBUG = 1 << 7; + } +} + +impl ClientExt { + pub fn device_enumeration(self) -> bool { + self.intersects(Self::EXT_DEVICE_BASE | Self::EXT_DEVICE_ENUMERATION) + } + + pub fn device_query(self) -> bool { + self.intersects(Self::EXT_DEVICE_BASE | Self::EXT_DEVICE_QUERY) + } +} + +pub fn get_client_ext() -> ClientExt { + let map = [ + ("EGL_EXT_platform_base", ClientExt::EXT_PLATFORM_BASE), + ("EGL_KHR_platform_gbm", ClientExt::KHR_PLATFORM_GBM), + ("EGL_EXT_platform_device", ClientExt::EXT_PLATFORM_DEVICE), + ("EGL_EXT_device_base", ClientExt::EXT_DEVICE_BASE), + ( + "EGL_EXT_device_enumeration", + ClientExt::EXT_DEVICE_ENUMERATION, + ), + ("EGL_EXT_device_query", ClientExt::EXT_DEVICE_QUERY), + ("EGL_KHR_debug", ClientExt::KHR_DEBUG), + ]; + match unsafe { get_dpy_extensions(EGLDisplay::none()) } { + Some(exts) => get_typed_ext(&exts, ClientExt::EXT_CLIENT_EXTENSION, &map), + _ => ClientExt::empty(), + } +} + +bitflags::bitflags! { + pub struct DisplayExt: u32 { + const KHR_IMAGE_BASE = 1 << 0; + const EXT_IMAGE_DMA_BUF_IMPORT = 1 << 1; + const EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS = 1 << 2; + const KHR_NO_CONFIG_CONTEXT = 1 << 3; + const MESA_CONFIGLESS_CONTEXT = 1 << 4; + const KHR_SURFACELESS_CONTEXT = 1 << 5; + const IMG_CONTEXT_PRIORITY = 1 << 6; + } +} + +pub(super) unsafe fn get_display_ext(dpy: EGLDisplay) -> DisplayExt { + let map = [ + ("EGL_KHR_image_base", DisplayExt::KHR_IMAGE_BASE), + ( + "EGL_EXT_image_dma_buf_import", + DisplayExt::EXT_IMAGE_DMA_BUF_IMPORT, + ), + ( + "EGL_EXT_image_dma_buf_import_modifiers", + DisplayExt::EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS, + ), + ( + "EGL_KHR_no_config_context", + DisplayExt::KHR_NO_CONFIG_CONTEXT, + ), + ( + "EGL_MESA_configless_context", + DisplayExt::MESA_CONFIGLESS_CONTEXT, + ), + ( + "EGL_KHR_surfaceless_context", + DisplayExt::KHR_SURFACELESS_CONTEXT, + ), + ("EGL_IMG_context_priority", DisplayExt::IMG_CONTEXT_PRIORITY), + ]; + match get_dpy_extensions(dpy) { + Some(exts) => get_typed_ext(&exts, DisplayExt::empty(), &map), + _ => DisplayExt::empty(), + } +} + +bitflags::bitflags! { + pub struct DeviceExt: u32 { + const MESA_DEVICE_SOFTWARE = 1 << 0; + const EXT_DEVICE_PERSISTENT_ID = 1 << 1; + const EXT_DEVICE_DRM = 1 << 2; + const EXT_DEVICE_DRM_RENDER_NODE = 1 << 3; + } +} + +pub(super) unsafe fn get_device_ext(dev: EGLDeviceEXT) -> DeviceExt { + let map = [ + ("EGL_MESA_device_software", DeviceExt::MESA_DEVICE_SOFTWARE), + ( + "EGL_EXT_device_persistent_id", + DeviceExt::EXT_DEVICE_PERSISTENT_ID, + ), + ("EGL_EXT_device_drm", DeviceExt::EXT_DEVICE_DRM), + ( + "EGL_EXT_device_drm_render_node", + DeviceExt::EXT_DEVICE_DRM_RENDER_NODE, + ), + ]; + let ext = PROCS.eglQueryDeviceStringEXT(dev, EGL_EXTENSIONS); + match get_extensions(ext) { + Some(exts) => get_typed_ext(&exts, DeviceExt::empty(), &map), + _ => DeviceExt::empty(), + } +} + +bitflags::bitflags! { + pub struct GlExt: u32 { + const GL_OES_EGL_IMAGE = 1 << 0; + } +} + +pub fn get_gl_ext() -> GlExt { + let map = [("GL_OES_EGL_image", GlExt::GL_OES_EGL_IMAGE)]; + match unsafe { get_extensions(glGetString(GL_EXTENSIONS) as _) } { + Some(exts) => get_typed_ext(&exts, GlExt::empty(), &map), + _ => GlExt::empty(), + } +} diff --git a/src/gles2/ext_proc.rs b/src/gles2/ext_proc.rs new file mode 100644 index 00000000..1929131c --- /dev/null +++ b/src/gles2/ext_proc.rs @@ -0,0 +1,3 @@ +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/egl_procs.rs")); diff --git a/src/gles2/gl.rs b/src/gles2/gl.rs new file mode 100644 index 00000000..38431dc4 --- /dev/null +++ b/src/gles2/gl.rs @@ -0,0 +1,363 @@ +use crate::egl::{EglContext, EglImage, PROCS}; +use crate::format::Format; +use crate::gles2::sys::{ + glAttachShader, glBindFramebuffer, glBindRenderbuffer, glBindTexture, glCheckFramebufferStatus, + glClear, glClearColor, glCompileShader, glCreateProgram, glCreateShader, glDeleteFramebuffers, + glDeleteProgram, glDeleteRenderbuffers, glDeleteShader, glDeleteTextures, glDetachShader, + glDisable, glEnable, glFramebufferRenderbuffer, glFramebufferTexture2D, glGenFramebuffers, + glGenRenderbuffers, glGenTextures, glGetAttribLocation, glGetProgramiv, glGetShaderiv, + glGetUniformLocation, glLinkProgram, glPixelStorei, glScissor, glShaderSource, glTexImage2D, + glTexParameteri, glUseProgram, EGLContext, GLeglImageOES, GLenum, GLint, GLuint, + GL_CLAMP_TO_EDGE, GL_COLOR_ATTACHMENT0, GL_COLOR_BUFFER_BIT, GL_COMPILE_STATUS, GL_FALSE, + GL_FRAMEBUFFER, GL_FRAMEBUFFER_COMPLETE, GL_LINEAR, GL_LINK_STATUS, GL_RENDERBUFFER, + GL_SCISSOR_TEST, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, + GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_UNPACK_ROW_LENGTH_EXT, +}; +use crate::gles2::GlesError; +use crate::rect::Rect; +use crate::utils::ptr_ext::PtrExt; +use std::cell::Cell; +use std::ptr; +use std::rc::Rc; +use uapi::{ustr, Ustr}; + +pub struct GlTexture { + pub(super) ctx: Rc, + pub tex: GLuint, + pub width: i32, + pub height: i32, +} + +impl GlTexture { + pub fn new( + ctx: &Rc, + format: &'static Format, + width: i32, + height: i32, + ) -> Result, GlesError> { + let tex = ctx.with_current(|| unsafe { + let mut tex = 0; + glGenTextures(1, &mut tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D( + GL_TEXTURE_2D, + 0, + format.gl_format, + width, + height, + 0, + format.gl_format as _, + format.gl_type as _, + ptr::null(), + ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + Ok(tex) + })?; + Ok(Rc::new(GlTexture { + ctx: ctx.clone(), + tex, + width, + height, + })) + } + + pub unsafe fn to_framebuffer(self: &Rc) -> Result, GlesError> { + self.ctx.with_current(|| unsafe { + let mut fbo = 0; + glGenFramebuffers(1, &mut fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + self.tex, + 0, + ); + let fb = GlFrameBuffer { + _rb: None, + _tex: Some(self.clone()), + ctx: self.ctx.clone(), + fbo, + width: self.width, + height: self.height, + }; + let status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if status != GL_FRAMEBUFFER_COMPLETE { + return Err(GlesError::CreateFramebuffer); + } + Ok(Rc::new(fb)) + }) + } + + pub fn import_texture( + ctx: &Rc, + data: &[Cell], + format: &'static Format, + width: i32, + height: i32, + stride: i32, + ) -> Result, GlesError> { + if (stride * height) as usize > data.len() { + return Err(GlesError::SmallImageBuffer); + } + let tex = ctx.with_current(|| unsafe { + let mut tex = 0; + glGenTextures(1, &mut tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format.bpp as GLint); + glTexImage2D( + GL_TEXTURE_2D, + 0, + format.gl_format, + width, + height, + 0, + format.gl_format as _, + format.gl_type as _, + data.as_ptr() as _, + ); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + glBindTexture(GL_TEXTURE_2D, 0); + Ok(tex) + })?; + Ok(Rc::new(GlTexture { + ctx: ctx.clone(), + tex, + width, + height, + })) + } +} + +impl Drop for GlTexture { + fn drop(&mut self) { + unsafe { + self.ctx.with_current(|| { + glDeleteTextures(1, &self.tex); + Ok(()) + }); + } + } +} + +pub fn with_scissor T>(scissor: &Rect, f: F) -> T { + return f(); + + #[thread_local] + static mut SCISSOR: *const Rect = ptr::null(); + + unsafe { + let prev = SCISSOR; + if prev.is_null() { + glEnable(GL_SCISSOR_TEST); + } + glScissor( + scissor.x1(), + scissor.x2(), + scissor.width(), + scissor.height(), + ); + SCISSOR = scissor; + let res = f(); + if prev.is_null() { + glDisable(GL_SCISSOR_TEST); + } else { + let prev = prev.deref(); + glScissor(prev.x1(), prev.x2(), prev.width(), prev.height()); + } + SCISSOR = prev; + res + } +} + +pub struct GlShader { + _ctx: Rc, + shader: GLuint, +} + +impl GlShader { + pub unsafe fn compile(ctx: &Rc, ty: GLenum, src: &str) -> Result { + let shader = glCreateShader(ty); + let res = GlShader { + _ctx: ctx.clone(), + shader, + }; + let len = src.len() as _; + glShaderSource(shader, 1, &(src.as_ptr() as _), &len); + glCompileShader(shader); + + let mut ok = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &mut ok); + if ok == GL_FALSE as _ { + return Err(GlesError::ShaderCompileFailed); + } + Ok(res) + } +} + +impl Drop for GlShader { + fn drop(&mut self) { + unsafe { + self._ctx.with_current(|| { + glDeleteShader(self.shader); + Ok(()) + }); + } + } +} + +pub struct GlProgram { + _ctx: Rc, + prog: GLuint, +} + +impl GlProgram { + pub unsafe fn link(vert: &GlShader, frag: &GlShader) -> Result { + let res = GlProgram { + _ctx: vert._ctx.clone(), + prog: glCreateProgram(), + }; + glAttachShader(res.prog, vert.shader); + glAttachShader(res.prog, frag.shader); + glLinkProgram(res.prog); + glDetachShader(res.prog, vert.shader); + glDetachShader(res.prog, frag.shader); + + let mut ok = 0; + glGetProgramiv(res.prog, GL_LINK_STATUS, &mut ok); + if ok == GL_FALSE as _ { + return Err(GlesError::ProgramLink); + } + + Ok(res) + } + + pub unsafe fn get_uniform_location(&self, name: &Ustr) -> GLint { + glGetUniformLocation(self.prog, name.as_ptr() as _) + } + + pub unsafe fn get_attrib_location(&self, name: &Ustr) -> GLint { + glGetAttribLocation(self.prog, name.as_ptr() as _) + } + + pub unsafe fn use_(&self) { + glUseProgram(self.prog); + } +} + +impl Drop for GlProgram { + fn drop(&mut self) { + unsafe { + self._ctx.with_current(|| { + glDeleteProgram(self.prog); + Ok(()) + }); + } + } +} + +pub struct GlRenderBuffer { + pub img: Rc, + pub ctx: Rc, + rbo: GLuint, +} + +impl GlRenderBuffer { + pub fn from_image( + img: &Rc, + ctx: &Rc, + ) -> Result, GlesError> { + ctx.with_current(|| unsafe { + let mut rbo = 0; + glGenRenderbuffers(1, &mut rbo); + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + PROCS.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, GLeglImageOES(img.img.0)); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + Ok(Rc::new(GlRenderBuffer { + img: img.clone(), + ctx: ctx.clone(), + rbo, + })) + }) + } + + pub fn create_framebuffer(self: &Rc) -> Result, GlesError> { + self.ctx.with_current(|| unsafe { + let mut fbo = 0; + glGenFramebuffers(1, &mut fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, + self.rbo, + ); + let status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + let fb = GlFrameBuffer { + _rb: Some(self.clone()), + _tex: None, + ctx: self.ctx.clone(), + fbo, + width: self.img.width, + height: self.img.height, + }; + if status != GL_FRAMEBUFFER_COMPLETE { + return Err(GlesError::CreateFramebuffer); + } + Ok(Rc::new(fb)) + }) + } +} + +impl Drop for GlRenderBuffer { + fn drop(&mut self) { + self.ctx.with_current(|| { + unsafe { + glDeleteRenderbuffers(1, &self.rbo); + } + Ok(()) + }); + } +} + +pub struct GlFrameBuffer { + pub _rb: Option>, + pub _tex: Option>, + pub ctx: Rc, + pub width: i32, + pub height: i32, + fbo: GLuint, +} + +impl GlFrameBuffer { + pub unsafe fn bind(&self) { + glBindFramebuffer(GL_FRAMEBUFFER, self.fbo); + } + + pub fn clear(&self, r: f32, g: f32, b: f32, a: f32) -> Result<(), GlesError> { + self.ctx.with_current(|| unsafe { + glBindFramebuffer(GL_FRAMEBUFFER, self.fbo); + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT); + // glFlush(); + Ok(()) + }) + } +} + +impl Drop for GlFrameBuffer { + fn drop(&mut self) { + self.ctx.with_current(|| { + unsafe { + glDeleteFramebuffers(1, &self.fbo); + } + Ok(()) + }); + } +} diff --git a/src/gles2/mod.rs b/src/gles2/mod.rs new file mode 100644 index 00000000..3bfdd783 --- /dev/null +++ b/src/gles2/mod.rs @@ -0,0 +1,58 @@ +pub mod egl; +pub mod ext; +mod ext_proc; +pub mod gl; +pub mod sys; + +use crate::drm::drm::DrmError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum GlesError { + #[error("EGL library does not support `EGL_EXT_platform_base`")] + ExtPlatformBase, + #[error("Could not compile a shader")] + ShaderCompileFailed, + #[error("Could not link a program")] + ProgramLink, + #[error("Could not bind to `EGL_OPENGL_ES_API`")] + BindFailed, + #[error("EGL library does not support device enumeration")] + DeviceEnumeration, + #[error("EGL library does not support device querying")] + DeviceQuery, + #[error("`eglQueryDeviceStringEXT` failed")] + DeviceQueryString, + #[error("`eglCreateContext` failed")] + CreateContext, + #[error("`eglMakeCurrent` failed")] + MakeCurrent, + #[error("`eglCreateImageKHR` failed")] + CreateImage, + #[error("Image buffer is too small")] + SmallImageBuffer, + #[error("Binding a renderbuffer to a framebuffer failed")] + CreateFramebuffer, + #[error("`eglQueryDevicesEXT` failed")] + QueryDevices, + #[error("`eglGetPlatformDisplayEXT` failed")] + GetDisplay, + #[error("`eglInitialize` failed")] + Initialize, + #[error("EGL display does not support `EGL_EXT_image_dma_buf_import_modifiers`")] + DmaBufImport, + #[error("GLES driver does not support `GL_OES_EGL_image`")] + OesEglImage, + #[error("EGL display does not support `EGL_KHR_image_base`")] + ImageBase, + #[error( + "EGL display does not support `EGL_KHR_no_config_context` or `EGL_MESA_configless_context`" + )] + ConfiglessContext, + #[error("EGL display does not support `EGL_KHR_surfaceless_context`")] + SurfacelessContext, + #[error("`eglQueryDmaBufFormatsEXT` failed")] + QueryDmaBufFormats, + #[error(transparent)] + DrmError(#[from] DrmError), +} diff --git a/src/gles2/sys.rs b/src/gles2/sys.rs new file mode 100644 index 00000000..41b4c88e --- /dev/null +++ b/src/gles2/sys.rs @@ -0,0 +1,256 @@ +pub use uapi::c; + +macro_rules! egl_transparent { + ($name:ident) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + #[repr(transparent)] + pub struct $name(pub *mut u8); + + impl $name { + #[allow(dead_code)] + pub const fn none() -> Self { + Self(std::ptr::null_mut()) + } + + #[allow(dead_code)] + pub fn is_none(self) -> bool { + self.0.is_null() + } + } + }; +} + +pub type EGLint = i32; +pub type EGLenum = c::c_uint; +pub type EGLBoolean = c::c_uint; +pub type EGLuint64KHR = u64; +pub type EGLAttrib = isize; + +egl_transparent!(EGLDisplay); +egl_transparent!(EGLSurface); +egl_transparent!(EGLConfig); +egl_transparent!(EGLImageKHR); +egl_transparent!(EGLContext); +egl_transparent!(EGLClientBuffer); +egl_transparent!(EGLLabelKHR); +egl_transparent!(EGLDeviceEXT); + +pub type EGLDEBUGPROCKHR = unsafe extern "C" fn( + error: EGLenum, + command: *const c::c_char, + message_type: EGLint, + thread_label: EGLLabelKHR, + object_label: EGLLabelKHR, + message: *const c::c_char, +); + +pub const EGL_EXTENSIONS: EGLint = 0x3055; +pub const EGL_DEBUG_MSG_CRITICAL_KHR: EGLint = 0x33B9; +pub const EGL_DEBUG_MSG_ERROR_KHR: EGLint = 0x33BA; +pub const EGL_DEBUG_MSG_WARN_KHR: EGLint = 0x33BB; +pub const EGL_DEBUG_MSG_INFO_KHR: EGLint = 0x33BC; +pub const EGL_TRUE: EGLBoolean = 1; +pub const EGL_FALSE: EGLBoolean = 0; +pub const EGL_NONE: EGLint = 0x3038; +pub const EGL_SUCCESS: EGLint = 0x3000; +pub const EGL_NOT_INITIALIZED: EGLint = 0x3001; +pub const EGL_BAD_ACCESS: EGLint = 0x3002; +pub const EGL_BAD_ALLOC: EGLint = 0x3003; +pub const EGL_BAD_ATTRIBUTE: EGLint = 0x3004; +pub const EGL_BAD_CONFIG: EGLint = 0x3005; +pub const EGL_BAD_CONTEXT: EGLint = 0x3006; +pub const EGL_BAD_CURRENT_SURFACE: EGLint = 0x3007; +pub const EGL_BAD_DISPLAY: EGLint = 0x3008; +pub const EGL_BAD_MATCH: EGLint = 0x3009; +pub const EGL_BAD_NATIVE_PIXMAP: EGLint = 0x300A; +pub const EGL_BAD_NATIVE_WINDOW: EGLint = 0x300B; +pub const EGL_BAD_PARAMETER: EGLint = 0x300C; +pub const EGL_BAD_SURFACE: EGLint = 0x300D; +pub const EGL_CONTEXT_LOST: EGLint = 0x300E; +pub const EGL_BAD_DEVICE_EXT: EGLint = 0x322B; +pub const EGL_OPENGL_ES_API: EGLenum = 0x30A0; +pub const EGL_DRM_DEVICE_FILE_EXT: EGLint = 0x3233; +pub const EGL_PLATFORM_DEVICE_EXT: EGLint = 0x313F; +pub const EGL_CONTEXT_CLIENT_VERSION: EGLint = 0x3098; + +pub const EGL_WIDTH: EGLint = 0x3057; +pub const EGL_HEIGHT: EGLint = 0x3056; +pub const EGL_LINUX_DRM_FOURCC_EXT: EGLint = 0x3271; +pub const EGL_DMA_BUF_PLANE0_FD_EXT: EGLint = 0x3272; +pub const EGL_DMA_BUF_PLANE0_OFFSET_EXT: EGLint = 0x3273; +pub const EGL_DMA_BUF_PLANE0_PITCH_EXT: EGLint = 0x3274; +pub const EGL_DMA_BUF_PLANE1_FD_EXT: EGLint = 0x3275; +pub const EGL_DMA_BUF_PLANE1_OFFSET_EXT: EGLint = 0x3276; +pub const EGL_DMA_BUF_PLANE1_PITCH_EXT: EGLint = 0x3277; +pub const EGL_DMA_BUF_PLANE2_FD_EXT: EGLint = 0x3278; +pub const EGL_DMA_BUF_PLANE2_OFFSET_EXT: EGLint = 0x3279; +pub const EGL_DMA_BUF_PLANE2_PITCH_EXT: EGLint = 0x327A; +pub const EGL_DMA_BUF_PLANE3_FD_EXT: EGLint = 0x3440; +pub const EGL_DMA_BUF_PLANE3_OFFSET_EXT: EGLint = 0x3441; +pub const EGL_DMA_BUF_PLANE3_PITCH_EXT: EGLint = 0x3442; +pub const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: EGLint = 0x3443; +pub const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: EGLint = 0x3444; +pub const EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT: EGLint = 0x3445; +pub const EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT: EGLint = 0x3446; +pub const EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT: EGLint = 0x3447; +pub const EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT: EGLint = 0x3448; +pub const EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT: EGLint = 0x3449; +pub const EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT: EGLint = 0x344A; +pub const EGL_IMAGE_PRESERVED_KHR: EGLint = 0x30D2; +pub const EGL_LINUX_DMA_BUF_EXT: EGLint = 0x3270; + +pub type GLenum = c::c_uint; +pub type GLbitfield = c::c_uint; +pub type GLfloat = f32; +pub type GLubyte = u8; +pub type GLsizei = c::c_int; +pub type GLuint = c::c_uint; +pub type GLint = c::c_int; +pub type GLchar = c::c_char; +pub type GLboolean = c::c_uchar; + +egl_transparent!(GLeglImageOES); + +pub const GL_EXTENSIONS: GLenum = 0x1F03; +pub const GL_RENDERBUFFER: GLenum = 0x8D41; +pub const GL_FRAMEBUFFER: GLenum = 0x8D40; +pub const GL_COLOR_ATTACHMENT0: GLenum = 0x8CE0; +pub const GL_FRAMEBUFFER_COMPLETE: GLenum = 0x8CD5; + +pub const GL_COLOR_BUFFER_BIT: GLbitfield = 0x00004000; +pub const GL_TEXTURE_2D: GLenum = 0x0DE1; +pub const GL_TEXTURE_WRAP_S: GLenum = 0x2802; +pub const GL_TEXTURE_WRAP_T: GLenum = 0x2803; +pub const GL_CLAMP_TO_EDGE: GLint = 0x812F; +pub const GL_UNPACK_ROW_LENGTH_EXT: GLenum = 0x0CF2; + +pub const GL_BGRA_EXT: GLint = 0x80E1; +pub const GL_UNSIGNED_BYTE: GLint = 0x1401; +pub const GL_SCISSOR_TEST: GLenum = 0x0C11; + +pub const GL_COMPILE_STATUS: GLenum = 0x8B81; +pub const GL_LINK_STATUS: GLenum = 0x8B82; + +pub const GL_FALSE: GLboolean = 0; +pub const GL_FRAGMENT_SHADER: GLenum = 0x8B30; +pub const GL_VERTEX_SHADER: GLenum = 0x8B31; + +pub const GL_TEXTURE0: GLenum = 0x84C0; +pub const GL_TEXTURE_MIN_FILTER: GLenum = 0x2801; +pub const GL_TEXTURE_MAG_FILTER: GLenum = 0x2800; +pub const GL_LINEAR: GLint = 0x2601; +pub const GL_FLOAT: GLenum = 0x1406; + +pub const GL_TRIANGLE_STRIP: GLenum = 0x0005; + +#[link(name = "GLESv2")] +extern "C" { + pub fn glGetString(name: GLenum) -> *const u8; + pub fn glGenRenderbuffers(n: GLsizei, renderbuffers: *mut GLuint); + pub fn glDeleteRenderbuffers(n: GLsizei, renderbuffers: *const GLuint); + pub fn glBindRenderbuffer(target: GLenum, renderbuffer: GLuint); + pub fn glGenFramebuffers(n: GLsizei, framebuffers: *mut GLuint); + pub fn glDeleteFramebuffers(n: GLsizei, framebuffers: *const GLuint); + pub fn glBindFramebuffer(target: GLenum, framebuffer: GLuint); + pub fn glFramebufferRenderbuffer( + target: GLenum, + attachment: GLenum, + renderbuffertarget: GLenum, + renderbuffer: GLuint, + ); + pub fn glFramebufferTexture2D( + target: GLenum, + attachment: GLenum, + textarget: GLenum, + texture: GLenum, + level: GLint, + ); + pub fn glCheckFramebufferStatus(target: GLenum) -> GLenum; + pub fn glClear(mask: GLbitfield); + pub fn glClearColor(red: GLfloat, green: GLfloat, blue: GLfloat, alpha: GLfloat); + pub fn glFlush(); + + pub fn glGenTextures(n: GLsizei, textures: *mut GLuint); + pub fn glDeleteTextures(n: GLsizei, textures: *const GLuint); + pub fn glBindTexture(target: GLenum, texture: GLuint); + pub fn glTexParameteri(target: GLenum, pname: GLenum, param: GLint); + + pub fn glPixelStorei(pname: GLenum, param: GLint); + + pub fn glTexImage2D( + target: GLenum, + level: GLint, + internalformat: GLint, + width: GLsizei, + height: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + pixels: *const c::c_void, + ); + + pub fn glScissor(x: GLint, y: GLint, width: GLsizei, height: GLsizei); + pub fn glEnable(cap: GLenum); + pub fn glDisable(cap: GLenum); + pub fn glViewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei); + + pub fn glCreateShader(ty: GLenum) -> GLuint; + pub fn glDeleteShader(shader: GLuint); + pub fn glShaderSource( + shader: GLuint, + count: GLsizei, + string: *const *const GLchar, + length: *const GLint, + ); + pub fn glCompileShader(shader: GLuint); + pub fn glGetShaderiv(shader: GLuint, pname: GLenum, params: *mut GLint); + + pub fn glCreateProgram() -> GLuint; + pub fn glDeleteProgram(prog: GLuint); + pub fn glAttachShader(prog: GLuint, shader: GLuint); + pub fn glDetachShader(prog: GLuint, shader: GLuint); + pub fn glLinkProgram(prog: GLuint); + pub fn glGetProgramiv(program: GLuint, pname: GLenum, params: *mut GLint); + pub fn glUseProgram(program: GLuint); + + pub fn glGetUniformLocation(prog: GLuint, name: *const GLchar) -> GLint; + pub fn glGetAttribLocation(prog: GLuint, name: *const GLchar) -> GLint; + pub fn glUniform1i(location: GLint, v0: GLint); + pub fn glUniform1f(location: GLint, v0: GLfloat); + pub fn glVertexAttribPointer( + index: GLuint, + size: GLint, + ty: GLenum, + normalized: GLboolean, + stride: GLsizei, + pointer: *const u8, + ); + + pub fn glActiveTexture(texture: GLuint); + + pub fn glEnableVertexAttribArray(idx: GLuint); + pub fn glDisableVertexAttribArray(idx: GLuint); + pub fn glDrawArrays(mode: GLenum, first: GLint, count: GLsizei); +} + +#[link(name = "EGL")] +extern "C" { + pub fn eglQueryString(dpy: EGLDisplay, name: EGLint) -> *const c::c_char; + pub fn eglGetProcAddress(procname: *const c::c_char) -> *mut u8; + pub fn eglBindAPI(api: EGLenum) -> EGLBoolean; + pub fn eglTerminate(dpy: EGLDisplay) -> EGLBoolean; + pub fn eglInitialize(dpy: EGLDisplay, major: *mut EGLint, minor: *mut EGLint) -> EGLBoolean; + pub fn eglCreateContext( + dpy: EGLDisplay, + config: EGLConfig, + share_context: EGLContext, + attrib_list: *const EGLint, + ) -> EGLContext; + pub fn eglDestroyContext(dpy: EGLDisplay, ctx: EGLContext) -> EGLBoolean; + pub fn eglMakeCurrent( + dpy: EGLDisplay, + draw: EGLSurface, + read: EGLSurface, + ctx: EGLContext, + ) -> EGLBoolean; +} diff --git a/src/ifs/wl_buffer/mod.rs b/src/ifs/wl_buffer/mod.rs index 0821016c..cc9cd70b 100644 --- a/src/ifs/wl_buffer/mod.rs +++ b/src/ifs/wl_buffer/mod.rs @@ -3,11 +3,13 @@ mod types; use crate::client::{Client, DynEventFormatter}; use crate::clientmem::{ClientMem, ClientMemOffset}; use crate::format::Format; +use crate::gles2::gl::GlTexture; use crate::ifs::wl_surface::{WlSurface, WlSurfaceId}; use crate::object::{Interface, Object, ObjectId}; use crate::pixman; use crate::rect::Rect; use crate::utils::buffd::MsgParser; +use crate::utils::clonecell::CloneCell; use crate::utils::copyhashmap::CopyHashMap; use std::rc::Rc; pub use types::*; @@ -23,10 +25,14 @@ pub struct WlBuffer { pub client: Rc, _offset: usize, pub rect: Rect, - _stride: i32, - _format: &'static Format, + stride: i32, + format: &'static Format, pub image: Rc>, + mem: ClientMemOffset, + pub texture: CloneCell>>, pub(super) surfaces: CopyHashMap>, + width: i32, + height: i32, } impl WlBuffer { @@ -52,8 +58,8 @@ impl WlBuffer { return Err(WlBufferError::StrideTooSmall); } let image = pixman::Image::new( - mem, - format.pixman, + mem.clone(), + format.pixman.unwrap(), width as u32, height as u32, stride as u32, @@ -63,13 +69,27 @@ impl WlBuffer { client: client.clone(), _offset: offset, rect: Rect::new_sized(0, 0, width, height).unwrap(), - _stride: stride, - _format: format, + stride, + format, image: Rc::new(image), + mem, + width, + height, + texture: CloneCell::new(None), surfaces: Default::default(), }) } + pub fn update_texture(&self) -> Result<(), WlBufferError> { + self.texture.set(None); + let ctx = self.client.state.egl.get().unwrap(); + let tex = self.mem.access(|mem| { + GlTexture::import_texture(&ctx, mem, self.format, self.width, self.height, self.stride) + })??; + self.texture.set(Some(tex)); + Ok(()) + } + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), DestroyError> { let _req: Destroy = self.client.parse(self, parser)?; { diff --git a/src/ifs/wl_buffer/types.rs b/src/ifs/wl_buffer/types.rs index bd7739ee..e8e48920 100644 --- a/src/ifs/wl_buffer/types.rs +++ b/src/ifs/wl_buffer/types.rs @@ -1,8 +1,10 @@ use crate::client::{ClientError, EventFormatter, RequestParser}; +use crate::gles2::GlesError; use crate::ifs::wl_buffer::{WlBuffer, RELEASE}; use crate::object::Object; use crate::pixman::PixmanError; use crate::utils::buffd::{MsgFormatter, MsgParser, MsgParserError}; +use crate::ClientMemError; use std::fmt::{Debug, Formatter}; use std::rc::Rc; use thiserror::Error; @@ -17,8 +19,14 @@ pub enum WlBufferError { DestroyError(#[from] DestroyError), #[error("Pixman returned an error")] PixmanError(#[source] Box), + #[error("Could not access the client memory")] + ClientMemError(#[source] Box), + #[error("GLES could not import the client image")] + GlesError(#[source] Box), } efrom!(WlBufferError, PixmanError, PixmanError); +efrom!(WlBufferError, ClientMemError, ClientMemError); +efrom!(WlBufferError, GlesError, GlesError); #[derive(Debug, Error)] pub enum DestroyError { diff --git a/src/ifs/wl_seat/mod.rs b/src/ifs/wl_seat/mod.rs index a55b708f..26cf473b 100644 --- a/src/ifs/wl_seat/mod.rs +++ b/src/ifs/wl_seat/mod.rs @@ -8,7 +8,7 @@ use crate::client::{Client, ClientId, DynEventFormatter}; use crate::fixed::Fixed; use crate::globals::{Global, GlobalName}; use crate::ifs::wl_seat::wl_keyboard::{WlKeyboard, WlKeyboardId}; -use crate::ifs::wl_seat::wl_pointer::{WlPointer, WlPointerId}; +use crate::ifs::wl_seat::wl_pointer::{WlPointer, WlPointerId, POINTER_FRAME_SINCE_VERSION}; use crate::ifs::wl_seat::wl_touch::WlTouch; use crate::ifs::wl_surface::xdg_surface::xdg_toplevel::{XdgToplevel, XdgToplevelId}; use crate::ifs::wl_surface::WlSurface; @@ -139,12 +139,12 @@ impl WlSeatGlobal { KeyState::Released => wl_pointer::RELEASED, KeyState::Pressed => wl_pointer::PRESSED, }; - self.surface_pointer_event(surface, |p| p.button(0, 0, button, state)); + self.surface_pointer_event(0, surface, |p| p.button(0, 0, button, state)); } pub fn focus_surface(&self, surface: &Rc) { let pressed_keys: Vec<_> = self.pressed_keys.borrow().iter().copied().collect(); - self.surface_kb_event(&surface, |k| { + self.surface_kb_event(0, &surface, |k| { k.enter(0, surface.id, pressed_keys.clone()) }); let ModifierState { @@ -153,15 +153,13 @@ impl WlSeatGlobal { mods_locked, group, } = self.kb_state.borrow().mods(); - self.surface_kb_event(surface, |k| { + self.surface_kb_event(0, surface, |k| { k.modifiers(0, mods_depressed, mods_latched, mods_locked, group) }); } pub fn unfocus_surface(&self, surface: &Rc) { - self.surface_kb_event(surface, |k| { - k.leave(0, surface.id) - }) + self.surface_kb_event(0, surface, |k| k.leave(0, surface.id)) } fn focus_toplevel(&self, toplevel: &Rc) { @@ -191,23 +189,25 @@ impl WlSeatGlobal { self.set_new_position(x, y); } - fn for_each_seat(&self, client: ClientId, mut f: C) + fn for_each_seat(&self, ver: u32, client: ClientId, mut f: C) where C: FnMut(&Rc), { let bindings = self.bindings.borrow(); if let Some(hm) = bindings.get(&client) { for seat in hm.values() { - f(seat); + if seat.version >= ver { + f(seat); + } } } } - fn for_each_pointer(&self, client: ClientId, mut f: C) + fn for_each_pointer(&self, ver: u32, client: ClientId, mut f: C) where C: FnMut(&Rc), { - self.for_each_seat(client, |seat| { + self.for_each_seat(ver, client, |seat| { let pointers = seat.pointers.lock(); for pointer in pointers.values() { f(pointer); @@ -215,11 +215,11 @@ impl WlSeatGlobal { }) } - fn for_each_kb(&self, client: ClientId, mut f: C) + fn for_each_kb(&self, ver: u32, client: ClientId, mut f: C) where C: FnMut(&Rc), { - self.for_each_seat(client, |seat| { + self.for_each_seat(ver, client, |seat| { let keyboards = seat.keyboards.lock(); for keyboard in keyboards.values() { f(keyboard); @@ -227,23 +227,23 @@ impl WlSeatGlobal { }) } - fn surface_pointer_event(&self, surface: &WlSurface, mut f: F) + fn surface_pointer_event(&self, ver: u32, surface: &WlSurface, mut f: F) where F: FnMut(&Rc) -> DynEventFormatter, { let client = &surface.client; - self.for_each_pointer(client.id, |p| { + self.for_each_pointer(ver, client.id, |p| { client.event(f(p)); }); client.flush(); } - fn surface_kb_event(&self, surface: &WlSurface, mut f: F) + fn surface_kb_event(&self, ver: u32, surface: &WlSurface, mut f: F) where F: FnMut(&Rc) -> DynEventFormatter, { let client = &surface.client; - self.for_each_kb(client.id, |p| { + self.for_each_kb(ver, client.id, |p| { client.event(f(p)); }); client.flush(); @@ -321,7 +321,7 @@ impl WlSeatGlobal { } pub fn leave_surface(&self, n: &WlSurface) { - self.surface_pointer_event(n, |p| p.leave(0, n.id)); + self.surface_pointer_event(0, n, |p| p.leave(0, n.id)); } pub fn enter_toplevel(&self, n: &Rc) { @@ -329,12 +329,12 @@ impl WlSeatGlobal { } pub fn enter_surface(&self, n: &WlSurface, x: Fixed, y: Fixed) { - self.surface_pointer_event(n, |p| p.enter(0, n.id, x, y)); + self.surface_pointer_event(0, n, |p| p.enter(0, n.id, x, y)); } pub fn motion_surface(&self, n: &WlSurface, x: Fixed, y: Fixed) { - self.surface_pointer_event(n, |p| p.motion(0, x, y)); - self.surface_pointer_event(n, |p| p.frame()); + self.surface_pointer_event(0, n, |p| p.motion(0, x, y)); + self.surface_pointer_event(POINTER_FRAME_SINCE_VERSION, n, |p| p.frame()); } fn motion_event(&self, dx: Fixed, dy: Fixed) { @@ -370,8 +370,8 @@ impl WlSeatGlobal { ScrollAxis::Horizontal => wl_pointer::HORIZONTAL_SCROLL, ScrollAxis::Vertical => wl_pointer::VERTICAL_SCROLL, }; - self.surface_pointer_event(surface, |p| p.axis(0, axis, Fixed::from_int(delta))); - self.surface_pointer_event(surface, |p| p.frame()); + self.surface_pointer_event(0, surface, |p| p.axis(0, axis, Fixed::from_int(delta))); + self.surface_pointer_event(POINTER_FRAME_SINCE_VERSION, surface, |p| p.frame()); } fn scroll_event(&self, delta: i32, axis: ScrollAxis) { diff --git a/src/ifs/wl_seat/wl_pointer/mod.rs b/src/ifs/wl_seat/wl_pointer/mod.rs index a1301da7..595d195f 100644 --- a/src/ifs/wl_seat/wl_pointer/mod.rs +++ b/src/ifs/wl_seat/wl_pointer/mod.rs @@ -40,6 +40,8 @@ const CONTINUOUS: u32 = 2; #[allow(dead_code)] const WHEEL_TILT: u32 = 3; +pub const POINTER_FRAME_SINCE_VERSION: u32 = 5; + id!(WlPointerId); pub struct WlPointer { diff --git a/src/ifs/wl_shm/mod.rs b/src/ifs/wl_shm/mod.rs index 80b646f9..c8bcbd06 100644 --- a/src/ifs/wl_shm/mod.rs +++ b/src/ifs/wl_shm/mod.rs @@ -1,6 +1,7 @@ mod types; use crate::client::Client; +use crate::format::FORMATS; use crate::globals::{Global, GlobalName}; use crate::ifs::wl_shm_pool::WlShmPool; use crate::object::{Interface, Object, ObjectId}; @@ -41,7 +42,7 @@ impl WlShmGlobal { client: client.clone(), }); client.add_client_obj(&obj)?; - for &format in client.state.formats.values() { + for format in FORMATS { client.event(Box::new(FormatE { obj: obj.clone(), format, diff --git a/src/ifs/wl_shm/types.rs b/src/ifs/wl_shm/types.rs index 7251d38a..6e3ffa8e 100644 --- a/src/ifs/wl_shm/types.rs +++ b/src/ifs/wl_shm/types.rs @@ -65,7 +65,8 @@ pub(super) struct FormatE { } impl EventFormatter for FormatE { fn format(self: Box, fmt: &mut MsgFormatter<'_>) { - fmt.header(self.obj.id, FORMAT).uint(self.format.id); + fmt.header(self.obj.id, FORMAT) + .uint(self.format.wl_id.unwrap_or(self.format.drm)); } fn obj(&self) -> &dyn Object { &*self.obj @@ -76,7 +77,8 @@ impl Debug for FormatE { write!( f, "format(format: \"{}\" (0x{:x}))", - self.format.name, self.format.id + self.format.name, + self.format.wl_id.unwrap_or(self.format.drm), ) } } diff --git a/src/ifs/wl_shm_pool/mod.rs b/src/ifs/wl_shm_pool/mod.rs index caa94f17..597cdcdc 100644 --- a/src/ifs/wl_shm_pool/mod.rs +++ b/src/ifs/wl_shm_pool/mod.rs @@ -2,6 +2,7 @@ mod types; use crate::client::Client; use crate::clientmem::ClientMem; +use crate::format::{formats, map_wayland_format_id}; use crate::ifs::wl_buffer::WlBuffer; use crate::object::{Interface, Object, ObjectId}; use crate::utils::buffd::MsgParser; @@ -40,7 +41,8 @@ impl WlShmPool { fn create_buffer(&self, parser: MsgParser<'_, '_>) -> Result<(), CreateBufferError> { let req: CreateBuffer = self.client.parse(self, parser)?; - let format = match self.client.state.formats.get(&req.format) { + let drm_format = map_wayland_format_id(req.format); + let format = match formats().get(&drm_format) { Some(f) => *f, _ => return Err(CreateBufferError::InvalidFormat(req.format)), }; diff --git a/src/ifs/wl_surface/mod.rs b/src/ifs/wl_surface/mod.rs index 5036a6f9..a32e11e6 100644 --- a/src/ifs/wl_surface/mod.rs +++ b/src/ifs/wl_surface/mod.rs @@ -2,9 +2,13 @@ mod types; pub mod wl_subsurface; pub mod xdg_surface; +use crate::backend::{KeyState, ScrollAxis}; use crate::client::{Client, RequestParser}; +use crate::fixed::Fixed; +use crate::gles2::gl::GlTexture; use crate::ifs::wl_buffer::WlBuffer; use crate::ifs::wl_callback::WlCallback; +use crate::ifs::wl_seat::WlSeatGlobal; use crate::ifs::wl_surface::wl_subsurface::WlSubsurface; use crate::object::{Interface, Object, ObjectId}; use crate::pixman::Region; @@ -20,9 +24,6 @@ use std::mem; use std::ops::{Deref, DerefMut}; use std::rc::Rc; pub use types::*; -use crate::backend::{KeyState, ScrollAxis}; -use crate::fixed::Fixed; -use crate::ifs::wl_seat::WlSeatGlobal; const DESTROY: u32 = 0; const ATTACH: u32 = 1; @@ -342,6 +343,7 @@ impl WlSurface { buffer.surfaces.remove(&self.id); } if let Some((dx, dy, buffer)) = buffer_change { + let _ = buffer.update_texture(); new_size = Some(buffer.rect); self.buffer.set(Some(buffer)); self.buf_x.fetch_add(dx); diff --git a/src/ifs/wl_surface/wl_subsurface/mod.rs b/src/ifs/wl_surface/wl_subsurface/mod.rs index 09038660..68102c67 100644 --- a/src/ifs/wl_surface/wl_subsurface/mod.rs +++ b/src/ifs/wl_surface/wl_subsurface/mod.rs @@ -1,5 +1,8 @@ mod types; +use crate::backend::{KeyState, ScrollAxis}; +use crate::fixed::Fixed; +use crate::ifs::wl_seat::WlSeatGlobal; use crate::ifs::wl_surface::{ CommitAction, CommitContext, StackElement, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceId, }; @@ -13,9 +16,6 @@ use std::cell::{Cell, RefCell}; use std::ops::Deref; use std::rc::Rc; pub use types::*; -use crate::backend::{KeyState, ScrollAxis}; -use crate::fixed::Fixed; -use crate::ifs::wl_seat::WlSeatGlobal; const DESTROY: u32 = 0; const SET_POSITION: u32 = 1; diff --git a/src/main.rs b/src/main.rs index 4edf9ca5..59e28360 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,8 @@ generic_associated_types, type_alias_impl_trait, never_type, - c_variadic + c_variadic, + thread_local )] #![allow( clippy::len_zero, @@ -18,6 +19,7 @@ use crate::backends::xorg::{XorgBackend, XorgBackendError}; use crate::client::Clients; use crate::clientmem::ClientMemError; use crate::event_loop::EventLoopError; +use crate::gles2::egl; use crate::globals::{AddGlobal, Globals}; use crate::ifs::wl_compositor::WlCompositorGlobal; use crate::ifs::wl_data_device_manager::WlDataDeviceManagerGlobal; @@ -48,9 +50,11 @@ mod backend; mod backends; mod client; mod clientmem; +mod drm; mod event_loop; mod fixed; mod format; +mod gles2; mod globals; mod ifs; mod object; @@ -96,6 +100,8 @@ enum MainError { } fn main_() -> Result<(), MainError> { + egl::init(); + clientmem::init()?; let el = EventLoop::new()?; sighand::install(&el)?; @@ -111,11 +117,11 @@ fn main_() -> Result<(), MainError> { let state = Rc::new(State { eng: engine.clone(), el: el.clone(), + egl: Default::default(), wheel, clients: Clients::new(), next_name: NumCell::new(1), globals, - formats: format::formats(), output_ids: Default::default(), root: Rc::new(DisplayNode::new(node_ids.next())), node_ids, diff --git a/src/render/gles.rs b/src/render/gles.rs new file mode 100644 index 00000000..e45b8743 --- /dev/null +++ b/src/render/gles.rs @@ -0,0 +1,335 @@ +use crate::egl::EglContext; +use crate::gles2::gl::{with_scissor, GlFrameBuffer, GlProgram, GlShader}; +use crate::gles2::sys::{ + glActiveTexture, glBindTexture, glClear, glClearColor, glDisableVertexAttribArray, + glDrawArrays, glEnableVertexAttribArray, glTexParameteri, glUniform1f, glUniform1i, + glVertexAttribPointer, GLint, GL_COLOR_BUFFER_BIT, GL_FALSE, GL_FLOAT, GL_FRAGMENT_SHADER, + GL_LINEAR, GL_TEXTURE0, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_TRIANGLE_STRIP, + GL_VERTEX_SHADER, +}; +use crate::gles2::GlesError; +use crate::ifs::wl_buffer::WlBuffer; +use crate::ifs::wl_surface::xdg_surface::xdg_toplevel::XdgToplevel; +use crate::ifs::wl_surface::WlSurface; +use crate::pixman::Image; +use crate::render::{Border, Renderer}; +use crate::servermem::ServerMem; +use crate::tree::{ + ContainerFocus, ContainerNode, ContainerSplit, CONTAINER_BORDER, CONTAINER_TITLE_HEIGHT, +}; +use crate::tree::{FloatNode, OutputNode, WorkspaceNode}; +use renderdoc::{RenderDoc, V100}; +use std::borrow::BorrowMut; +use std::cell::RefCell; +use std::ops::Deref; +use std::ptr; +use std::rc::Rc; +use uapi::ustr; + +pub const RENDERDOC: bool = false; + +pub struct GlesRenderer { + ctx: Rc, + + renderdoc: Option>>, + + tex_prog: GlProgram, + tex_prog_pos: GLint, + tex_prog_texcoord: GLint, + tex_prog_tex: GLint, +} + +impl GlesRenderer { + pub unsafe fn new(ctx: &Rc) -> Result { + let vert = GlShader::compile(ctx, GL_VERTEX_SHADER, include_str!("shaders/tex.vert.glsl"))?; + let frag = GlShader::compile( + ctx, + GL_FRAGMENT_SHADER, + include_str!("shaders/tex.frag.glsl"), + )?; + let prog = GlProgram::link(&vert, &frag)?; + Ok(Self { + ctx: ctx.clone(), + tex_prog_pos: prog.get_attrib_location(ustr!("pos")), + tex_prog_texcoord: prog.get_attrib_location(ustr!("texcoord")), + tex_prog_tex: prog.get_uniform_location(ustr!("tex")), + tex_prog: prog, + renderdoc: if RENDERDOC { + Some(RefCell::new(RenderDoc::new().unwrap())) + } else { + None + }, + }) + } + + pub fn render_fb<'a>(&'a self, fb: &'a GlFrameBuffer) -> GlesImageRenderer<'a> { + if let Some(rd) = &self.renderdoc { + rd.borrow_mut() + .start_frame_capture(ptr::null(), ptr::null()); + } + GlesImageRenderer { + renderer: self, + image: fb, + } + } +} + +pub struct GlesImageRenderer<'a> { + renderer: &'a GlesRenderer, + image: &'a GlFrameBuffer, +} + +impl Drop for GlesImageRenderer<'_> { + fn drop(&mut self) { + if let Some(rd) = &self.renderer.renderdoc { + rd.borrow_mut().end_frame_capture(ptr::null(), ptr::null()); + } + } +} + +const NON_COLOR: (u8, u8, u8) = (100, 100, 100); +const CHILD_COLOR: (u8, u8, u8) = (200, 200, 200); +const YES_COLOR: (u8, u8, u8) = (0, 0, 255); + +fn focus_color(focus: ContainerFocus) -> (u8, u8, u8) { + match focus { + ContainerFocus::None => NON_COLOR, + ContainerFocus::Child => CHILD_COLOR, + ContainerFocus::Yes => YES_COLOR, + } +} + +impl Renderer for GlesImageRenderer<'_> { + fn render_output(&mut self, output: &OutputNode) { + unsafe { + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + } + if let Some(ws) = output.workspace.get() { + self.render_workspace(&ws); + } + } + + fn render_workspace(&mut self, workspace: &WorkspaceNode) { + if let Some(node) = workspace.container.get() { + self.render_container(&node, 0, 0) + } + } + + fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) { + let cwidth = container.width.get(); + let cheight = container.height.get(); + let num_children = container.num_children(); + if let Some(child) = container.mono_child.get() { + let space_per_child = cwidth / num_children as i32; + let mut rem = cwidth % num_children as i32; + let mut pos = x; + for child in container.children.iter() { + let (r, g, b) = focus_color(child.focus.get()); + let mut width = space_per_child; + if rem > 0 { + rem -= 1; + width += 1; + } + // let _ = self.image.fill_rect( + // r, + // g, + // b, + // 255, + // pos, + // y, + // pos + width as i32, + // y + CONTAINER_TITLE_HEIGHT as i32, + // ); + pos += width as i32; + } + with_scissor(&container.mono_body.get(), || { + let content = container.mono_content.get(); + child.node.render(self, x + content.x1(), y + content.y1()); + }); + } else { + let split = container.split.get(); + for (i, child) in container.children.iter().enumerate() { + let body = child.body.get(); + if body.x1() >= cwidth || body.y1() >= cheight { + break; + } + let (r, g, b) = focus_color(child.focus.get()); + // let _ = self.image.fill_rect( + // r, + // g, + // b, + // 255, + // x + body.x1(), + // y + body.y1() - CONTAINER_TITLE_HEIGHT, + // x + body.x2(), + // y + body.y1(), + // ); + { + let mut x1 = x + body.x1(); + let mut x2 = x + body.x2(); + let mut y2 = y + body.y2(); + let mut border = Border::empty(); + if i < num_children { + if split == ContainerSplit::Horizontal { + border |= Border::RIGHT; + x2 += CONTAINER_BORDER; + } else if split == ContainerSplit::Vertical { + border |= Border::BOTTOM; + y2 += CONTAINER_BORDER; + } + } + if i > 0 && split == ContainerSplit::Horizontal { + border |= Border::LEFT; + x1 -= CONTAINER_BORDER; + } + // let _ = self.image.fill_inner_border( + // r, + // g, + // b, + // 255, + // x1, + // y + body.y1() - CONTAINER_TITLE_HEIGHT, + // x2, + // y2, + // CONTAINER_BORDER as i32, + // border, + // ); + } + with_scissor(&body, || { + let content = child.content.get(); + child.node.render(self, x + content.x1(), y + content.y1()); + // self.image.fill_inner_border( + // 0, + // 0, + // 255, + // 255, + // x + body.x1(), + // y + body.y1(), + // x + body.x1() + body.width(), + // y + body.y1() + body.height(), + // 2, + // Border::all(), + // ); + // self.image.fill_inner_border( + // 255, + // 0, + // 0, + // 255, + // x + content.x1(), + // y + content.y1(), + // x + content.x1() + content.width(), + // y + content.y1() + content.height(), + // 2, + // Border::all(), + // ); + }); + } + } + } + + fn render_toplevel(&mut self, tl: &XdgToplevel, mut x: i32, mut y: i32) { + let surface = &tl.xdg.surface; + if let Some(geo) = tl.xdg.geometry() { + let (xt, yt) = geo.translate(x, y); + x = xt; + y = yt; + } + self.render_surface(surface, x, y); + } + + fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32) { + let children = surface.children.borrow(); + let buffer = match surface.buffer.get() { + Some(b) => b, + _ => { + log::warn!("surface has no buffer attached"); + return; + } + }; + if let Some(children) = children.deref() { + macro_rules! render { + ($children:expr) => { + for child in $children.rev_iter() { + if child.pending.get() { + continue; + } + let pos = child.sub_surface.position.get(); + self.render_surface(&child.sub_surface.surface, x + pos.x1(), y + pos.y1()); + } + }; + } + render!(&children.above); + self.render_buffer(&buffer, x, y); + render!(&children.below); + } else { + self.render_buffer(&buffer, x, y); + } + let mut fr = surface.frame_requests.borrow_mut(); + for cb in fr.drain(..) { + surface.client.dispatch_frame_requests.push(cb); + } + } + + fn render_buffer(&mut self, buffer: &WlBuffer, x: i32, y: i32) { + let texture = match buffer.texture.get() { + Some(t) => t, + _ => return, + }; + unsafe { + glActiveTexture(GL_TEXTURE0); + + glBindTexture(GL_TEXTURE_2D, texture.tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + self.renderer.tex_prog.use_(); + + glUniform1i(self.renderer.tex_prog_tex, 0); + + let texcoord: [f32; 8] = [1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0]; + + let f_width = self.image.width as f32; + let f_height = self.image.height as f32; + + let x1 = 2.0 * (x as f32 / f_width) - 1.0; + let y1 = 2.0 * (y as f32 / f_height) - 1.0; + let x2 = 2.0 * ((x + texture.width) as f32 / f_width) - 1.0; + let y2 = 2.0 * ((y + texture.height) as f32 / f_height) - 1.0; + + let pos: [f32; 8] = [x2, y1, x1, y1, x2, y2, x1, y2]; + + glVertexAttribPointer( + self.renderer.tex_prog_texcoord as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + texcoord.as_ptr() as _, + ); + glVertexAttribPointer( + self.renderer.tex_prog_pos as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + pos.as_ptr() as _, + ); + + glEnableVertexAttribArray(self.renderer.tex_prog_texcoord as _); + glEnableVertexAttribArray(self.renderer.tex_prog_pos as _); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(self.renderer.tex_prog_texcoord as _); + glDisableVertexAttribArray(self.renderer.tex_prog_pos as _); + + glBindTexture(GL_TEXTURE_2D, 0); + } + } + + fn render_floating(&mut self, floating: &FloatNode, x: i32, y: i32) { + if let Some(child) = floating.child.get() { + child.render(self, x, y) + } + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 64d51907..70bb1c18 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,6 +4,7 @@ use crate::ifs::wl_surface::WlSurface; use crate::tree::ContainerNode; use crate::tree::{FloatNode, OutputNode, WorkspaceNode}; +pub mod gles; pub mod pixman; bitflags::bitflags! { diff --git a/src/render/pixman.rs b/src/render/pixman.rs index db170f73..f60fa183 100644 --- a/src/render/pixman.rs +++ b/src/render/pixman.rs @@ -75,9 +75,7 @@ impl Renderer for PixmanRenderer<'_> { } self.image.with_clip(container.mono_body.get(), || { let content = container.mono_content.get(); - child - .node - .render(self, x + content.x1(), y + content.y1()); + child.node.render(self, x + content.x1(), y + content.y1()); }); } else { let split = container.split.get(); @@ -131,8 +129,30 @@ impl Renderer for PixmanRenderer<'_> { self.image.with_clip(body, || { let content = child.content.get(); child.node.render(self, x + content.x1(), y + content.y1()); - self.image.fill_inner_border(0, 0, 255, 255, x + body.x1(), y + body.y1(), x + body.x1() + body.width(), y + body.y1() + body.height(), 2, Border::all()); - self.image.fill_inner_border(255, 0, 0, 255, x + content.x1(), y + content.y1(), x + content.x1() + content.width(), y + content.y1() + content.height(), 2, Border::all()); + self.image.fill_inner_border( + 0, + 0, + 255, + 255, + x + body.x1(), + y + body.y1(), + x + body.x1() + body.width(), + y + body.y1() + body.height(), + 2, + Border::all(), + ); + self.image.fill_inner_border( + 255, + 0, + 0, + 255, + x + content.x1(), + y + content.y1(), + x + content.x1() + content.width(), + y + content.y1() + content.height(), + 2, + Border::all(), + ); }); } } @@ -154,8 +174,8 @@ impl Renderer for PixmanRenderer<'_> { Some(b) => b, _ => { log::warn!("surface has no buffer attached"); - return - }, + return; + } }; if let Some(children) = children.deref() { macro_rules! render { diff --git a/src/render/shaders/tex.frag.glsl b/src/render/shaders/tex.frag.glsl new file mode 100644 index 00000000..d713be11 --- /dev/null +++ b/src/render/shaders/tex.frag.glsl @@ -0,0 +1,7 @@ +precision mediump float; +varying vec2 v_texcoord; +uniform sampler2D tex; + +void main() { + gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0); +} diff --git a/src/render/shaders/tex.vert.glsl b/src/render/shaders/tex.vert.glsl new file mode 100644 index 00000000..29718593 --- /dev/null +++ b/src/render/shaders/tex.vert.glsl @@ -0,0 +1,8 @@ +attribute vec2 pos; +attribute vec2 texcoord; +varying vec2 v_texcoord; + +void main() { + gl_Position = vec4(pos, 0.0, 1.0); + v_texcoord = texcoord; +} diff --git a/src/state.rs b/src/state.rs index 884a0e74..685e2f7c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,14 +1,15 @@ use crate::async_engine::{AsyncEngine, SpawnedFuture}; use crate::backend::{BackendEvent, OutputId, OutputIds, SeatId, SeatIds}; use crate::client::{Client, Clients}; +use crate::egl::EglContext; use crate::event_loop::EventLoop; -use crate::format::Format; use crate::globals::{AddGlobal, Globals}; use crate::ifs::wl_output::WlOutputGlobal; use crate::ifs::wl_seat::WlSeatGlobal; use crate::ifs::wl_surface::NoneSurfaceExt; use crate::tree::{DisplayNode, NodeIds}; use crate::utils::asyncevent::AsyncEvent; +use crate::utils::clonecell::CloneCell; use crate::utils::copyhashmap::CopyHashMap; use crate::utils::linkedlist::LinkedList; use crate::utils::numcell::NumCell; @@ -21,11 +22,11 @@ use std::rc::Rc; pub struct State { pub eng: Rc, pub el: Rc, + pub egl: CloneCell>>, pub wheel: Rc, pub clients: Clients, pub next_name: NumCell, pub globals: Globals, - pub formats: AHashMap, pub output_ids: OutputIds, pub seat_ids: SeatIds, pub node_ids: NodeIds, diff --git a/src/tree/container.rs b/src/tree/container.rs index 9dd1af8e..c30fd40f 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -113,7 +113,11 @@ impl ContainerNode { } pub fn add_child_after(self: &Rc, prev: &dyn Node, new: Rc) { - let node = self.child_nodes.borrow().get(&prev.id()).map(|n| n.to_ref()); + let node = self + .child_nodes + .borrow() + .get(&prev.id()) + .map(|n| n.to_ref()); if let Some(node) = node { self.add_child_after_(&node, new); return; @@ -185,7 +189,9 @@ impl ContainerNode { body_size = body_size.min(remaining_content_size); remaining_content_size -= body_size; let (x1, y1, width, height) = match split { - ContainerSplit::Horizontal => (pos, CONTAINER_TITLE_HEIGHT, body_size, other_content_size), + ContainerSplit::Horizontal => { + (pos, CONTAINER_TITLE_HEIGHT, body_size, other_content_size) + } _ => (0, pos, other_content_size, body_size), }; let body = Rect::new_sized(x1, y1, width, height).unwrap(); @@ -209,7 +215,13 @@ impl ContainerNode { let (x1, y1, width, height, size) = match split { ContainerSplit::Horizontal => { let width = body.width() + add; - (pos, CONTAINER_TITLE_HEIGHT, width, other_content_size, width) + ( + pos, + CONTAINER_TITLE_HEIGHT, + width, + other_content_size, + width, + ) } _ => { let height = body.height() + add; diff --git a/src/tree/mod.rs b/src/tree/mod.rs index b0005aba..25802ed1 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -238,7 +238,8 @@ impl Node for OutputNode { } fn change_size(self: Rc, width: i32, height: i32) { - self.position.set(Rect::new_sized(0, 0, width, height).unwrap()); + self.position + .set(Rect::new_sized(0, 0, width, height).unwrap()); if let Some(c) = self.workspace.get() { c.change_size(width, height); } @@ -294,6 +295,7 @@ impl Node for FloatNode { fn child_size_changed(&self, _child: &dyn Node, width: i32, height: i32) { let pos = self.position.get(); - self.position.set(Rect::new_sized(pos.x1(), pos.x2(), width, height).unwrap()); + self.position + .set(Rect::new_sized(pos.x1(), pos.x2(), width, height).unwrap()); } } diff --git a/src/utils/bitflags.rs b/src/utils/bitflags.rs new file mode 100644 index 00000000..9ab2f653 --- /dev/null +++ b/src/utils/bitflags.rs @@ -0,0 +1,22 @@ +pub trait BitflagsExt { + fn contains(self, other: Self) -> bool; +} + +macro_rules! num { + ($ty:ident) => { + impl BitflagsExt for $ty { + fn contains(self, other: Self) -> bool { + self & other != 0 + } + } + }; +} + +num!(u8); +num!(u16); +num!(u32); +num!(u64); +num!(i8); +num!(i16); +num!(i32); +num!(i64); diff --git a/src/utils/debug_fn.rs b/src/utils/debug_fn.rs new file mode 100644 index 00000000..58c808a5 --- /dev/null +++ b/src/utils/debug_fn.rs @@ -0,0 +1,21 @@ +use std::fmt::{Debug, Formatter}; + +pub fn debug_fn(f: F) -> impl Debug +where + F: Fn(&mut Formatter<'_>) -> std::fmt::Result, +{ + DebugFn { f } +} + +struct DebugFn { + f: F, +} + +impl Debug for DebugFn +where + F: Fn(&mut Formatter<'_>) -> std::fmt::Result, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + (self.f)(f) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3bc23e08..974e54f3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,9 @@ pub mod asyncevent; +pub mod bitflags; pub mod buffd; pub mod clonecell; pub mod copyhashmap; +pub mod debug_fn; pub mod errorfmt; pub mod linkedlist; pub mod numcell;