use { crate::{ allocator::Allocator, cmm::{ cmm_description::{ColorDescription, LinearColorDescription}, cmm_render_intent::RenderIntent, }, cpu_worker::CpuWorker, cursor::Cursor, damage::DamageVisualizer, eventfd_cache::Eventfd, fixed::Fixed, format::Format, io_uring::{IoUring, IoUringError}, rect::{Rect, Region}, renderer::{Renderer, renderer_base::RendererBase}, scale::Scale, state::State, theme::Color, tree::{Node, OutputNode, Transform}, utils::{ clonecell::UnsafeCellCloneSafe, errorfmt::ErrorFmt, oserror::OsErrorExt, static_text::StaticText, }, video::{ Modifier, dmabuf::DmaBuf, drm::syncobj::{Syncobj, SyncobjCtx, SyncobjPoint}, }, }, ahash::AHashMap, indexmap::{IndexMap, IndexSet}, jay_config::video::GfxApi as ConfigGfxApi, linearize::Linearize, std::{ any::Any, cell::{Cell, OnceCell}, error::Error, ffi::CString, fmt::{Debug, Formatter}, ops::Deref, rc::Rc, slice, sync::atomic::{AtomicU64, Ordering::Relaxed}, }, thiserror::Error, uapi::{OwnedFd, c}, }; #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Linearize)] pub enum GfxApi { OpenGl, Vulkan, } impl StaticText for GfxApi { fn text(&self) -> &'static str { self.to_str() } } impl TryFrom for GfxApi { type Error = (); fn try_from(value: ConfigGfxApi) -> Result { let v = match value { ConfigGfxApi::OpenGl => GfxApi::OpenGl, ConfigGfxApi::Vulkan => GfxApi::Vulkan, _ => return Err(()), }; Ok(v) } } impl Into for GfxApi { fn into(self) -> ConfigGfxApi { match self { GfxApi::OpenGl => ConfigGfxApi::OpenGl, GfxApi::Vulkan => ConfigGfxApi::Vulkan, } } } impl GfxApi { pub fn to_str(&self) -> &'static str { match self { GfxApi::OpenGl => "OpenGl", GfxApi::Vulkan => "Vulkan", } } pub fn from_str_lossy(s: &str) -> Option { match &*s.to_ascii_lowercase() { "opengl" => Some(Self::OpenGl), "vulkan" => Some(Self::Vulkan), _ => None, } } } pub enum GfxApiOpt { Sync, FillRect(FillRect), CopyTexture(CopyTexture), RoundedFillRect(RoundedFillRect), RoundedCopyTexture(RoundedCopyTexture), } pub struct GfxRenderPass { pub ops: Vec, pub clear: Option, pub clear_cd: Rc, } #[derive(Default, Debug, Copy, Clone, PartialEq)] pub struct SampleRect { pub x1: f32, pub y1: f32, pub x2: f32, pub y2: f32, pub buffer_transform: Transform, } impl SampleRect { pub fn identity() -> Self { Self { x1: 0.0, y1: 0.0, x2: 1.0, y2: 1.0, buffer_transform: Transform::None, } } pub fn is_covering(&self) -> bool { self.x1 == 0.0 && self.y1 == 0.0 && self.x2 == 1.0 && self.y2 == 1.0 } pub fn to_points(&self) -> [[f32; 2]; 4] { use Transform::*; let x1 = self.x1; let x2 = self.x2; let y1 = self.y1; let y2 = self.y2; match self.buffer_transform { None => [[x2, y1], [x1, y1], [x2, y2], [x1, y2]], Rotate90 => [ [y1, 1.0 - x2], [y1, 1.0 - x1], [y2, 1.0 - x2], [y2, 1.0 - x1], ], Rotate180 => [ [1.0 - x2, 1.0 - y1], [1.0 - x1, 1.0 - y1], [1.0 - x2, 1.0 - y2], [1.0 - x1, 1.0 - y2], ], Rotate270 => [ [1.0 - y1, x2], [1.0 - y1, x1], [1.0 - y2, x2], [1.0 - y2, x1], ], Flip => [ [1.0 - x2, y1], [1.0 - x1, y1], [1.0 - x2, y2], [1.0 - x1, y2], ], FlipRotate90 => [[y1, x2], [y1, x1], [y2, x2], [y2, x1]], FlipRotate180 => [ [x2, 1.0 - y1], [x1, 1.0 - y1], [x2, 1.0 - y2], [x1, 1.0 - y2], ], FlipRotate270 => [ [1.0 - y1, 1.0 - x2], [1.0 - y1, 1.0 - x1], [1.0 - y2, 1.0 - x2], [1.0 - y2, 1.0 - x1], ], } } } #[derive(Copy, Clone, Debug, PartialEq)] pub struct FramebufferRect { pub x1: f32, pub x2: f32, pub y1: f32, pub y2: f32, pub output_transform: Transform, } impl FramebufferRect { pub fn new( x1: f32, y1: f32, x2: f32, y2: f32, transform: Transform, width: f32, height: f32, ) -> Self { Self { x1: 2.0 * x1 / width - 1.0, x2: 2.0 * x2 / width - 1.0, y1: 2.0 * y1 / height - 1.0, y2: 2.0 * y2 / height - 1.0, output_transform: transform, } } pub fn to_points(&self) -> [[f32; 2]; 4] { use Transform::*; let x1 = self.x1; let x2 = self.x2; let y1 = self.y1; let y2 = self.y2; match self.output_transform { None => [[x2, y1], [x1, y1], [x2, y2], [x1, y2]], Rotate90 => [[y1, -x2], [y1, -x1], [y2, -x2], [y2, -x1]], Rotate180 => [[-x2, -y1], [-x1, -y1], [-x2, -y2], [-x1, -y2]], Rotate270 => [[-y1, x2], [-y1, x1], [-y2, x2], [-y2, x1]], Flip => [[-x2, y1], [-x1, y1], [-x2, y2], [-x1, y2]], FlipRotate90 => [[y1, x2], [y1, x1], [y2, x2], [y2, x1]], FlipRotate180 => [[x2, -y1], [x1, -y1], [x2, -y2], [x1, -y2]], FlipRotate270 => [[-y1, -x2], [-y1, -x1], [-y2, -x2], [-y2, -x1]], } } pub fn is_covering(&self) -> bool { self.x1 == -1.0 && self.y1 == -1.0 && self.x2 == 1.0 && self.y2 == 1.0 } pub fn to_rect(&self, width: f32, height: f32) -> Rect { let mut x1 = self.x1; let mut x2 = self.x2; let mut y1 = self.y1; let mut y2 = self.y2; (x1, y1, x2, y2) = match self.output_transform { Transform::None => (x1, y1, x2, y2), Transform::Rotate90 => (y1, -x2, y2, -x1), Transform::Rotate180 => (-x2, -y2, -x1, -y1), Transform::Rotate270 => (-y2, x1, -y1, x2), Transform::Flip => (-x2, y1, -x1, y2), Transform::FlipRotate90 => (y1, x1, y2, x2), Transform::FlipRotate180 => (x1, -y2, x2, -y1), Transform::FlipRotate270 => (-y2, -x2, -y1, -x1), }; let x1 = ((x1 + 1.0) / 2.0 * width).round() as i32; let x2 = ((x2 + 1.0) / 2.0 * width).round() as i32; let y1 = ((y1 + 1.0) / 2.0 * height).round() as i32; let y2 = ((y2 + 1.0) / 2.0 * height).round() as i32; Rect::new_saturating(x1, y1, x2, y2) } } #[derive(Debug)] pub struct FillRect { pub rect: FramebufferRect, pub color: Color, pub alpha: Option, pub render_intent: RenderIntent, pub cd: Rc, } impl FillRect { pub fn effective_color(&self) -> Color { let mut color = self.color; if let Some(alpha) = self.alpha { color = color * alpha; } color } } pub struct CopyTexture { pub tex: Rc, pub source: SampleRect, pub target: FramebufferRect, pub buffer_resv: Option>, pub acquire_sync: AcquireSync, pub release_sync: ReleaseSync, pub alpha: Option, pub opaque: bool, pub render_intent: RenderIntent, pub cd: Rc, pub alpha_mode: AlphaMode, } #[derive(Debug)] pub struct RoundedFillRect { pub rect: FramebufferRect, pub color: Color, pub alpha: Option, pub render_intent: RenderIntent, pub cd: Rc, /// Size of the rectangle in physical pixels. pub size: [f32; 2], /// Per-corner radius in physical pixels: [top_left, top_right, bottom_right, bottom_left]. pub corner_radius: [f32; 4], /// Border width in physical pixels. 0 means a filled rounded rect (no cutout). pub border_width: f32, /// Output scale for antialiasing. pub scale: f32, /// Sort order hint within the RoundedFill bucket (lower renders first). pub z_order: u32, } impl RoundedFillRect { pub fn effective_color(&self) -> Color { let mut color = self.color; if let Some(alpha) = self.alpha { color = color * alpha; } color } } pub struct RoundedCopyTexture { pub tex: Rc, pub source: SampleRect, pub target: FramebufferRect, pub buffer_resv: Option>, pub acquire_sync: AcquireSync, pub release_sync: ReleaseSync, pub alpha: Option, pub opaque: bool, pub render_intent: RenderIntent, pub cd: Rc, pub alpha_mode: AlphaMode, /// Size of the rectangle in physical pixels. pub size: [f32; 2], /// Per-corner radius in physical pixels: [top_left, top_right, bottom_right, bottom_left]. pub corner_radius: [f32; 4], /// Output scale for antialiasing. pub scale: f32, } #[derive(Clone, Debug, PartialEq)] pub struct SyncFile(pub Rc); impl Deref for SyncFile { type Target = Rc; fn deref(&self) -> &Self::Target { &self.0 } } unsafe impl UnsafeCellCloneSafe for SyncFile {} #[derive(Clone)] pub enum AcquireSync { None, Implicit, FdSync(FdSync), Unnecessary, } impl AcquireSync { pub fn from_fd_sync(sync: Option) -> Self { match sync { None => Self::Unnecessary, Some(sync) => Self::FdSync(sync), } } pub fn get_sync_file(&self) -> Option<&SyncFile> { match self { Self::None => None, Self::Implicit => None, Self::FdSync(sync) => sync.get_sync_file(), Self::Unnecessary => None, } } } #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum ReleaseSync { None, Implicit, Explicit, } impl Debug for AcquireSync { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let name = match self { AcquireSync::None => "None", AcquireSync::Implicit => "Implicit", AcquireSync::FdSync(d) => return Debug::fmt(d, f), AcquireSync::Unnecessary => "Unnecessary", }; f.debug_struct(name).finish_non_exhaustive() } } pub trait BufferResv: Debug { fn set_sync(&self, user: BufferResvUser, sync: &FdSync); } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct BufferResvUser(u64); impl Default for BufferResvUser { fn default() -> Self { static NEXT_ID: AtomicU64 = AtomicU64::new(1); Self(NEXT_ID.fetch_add(1, Relaxed)) } } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ResetStatus { Guilty, Innocent, Unknown, Other(u32), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] pub enum AlphaMode { #[default] PremultipliedElectrical, PremultipliedOptical, Straight, } pub trait GfxBlendBuffer: Any + Debug {} pub trait GfxFramebuffer: Debug { fn physical_size(&self) -> (i32, i32); fn render_with_region( self: Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, clear_cd: &Rc, region: &Region, blend_buffer: Option<&Rc>, blend_cd: &Rc, ) -> Result, GfxError>; fn format(&self) -> &'static Format; fn full_region(&self) -> Region { let (width, height) = self.physical_size(); Region::new(Rect::new_sized_saturating(0, 0, width, height)) } } pub trait GfxInternalFramebuffer: GfxFramebuffer { fn stride(&self) -> i32; fn staging_size(&self) -> usize; fn download( self: Rc, staging: &Rc, callback: Rc, mem: Rc, damage: Region, ) -> Result, GfxError>; } impl dyn GfxFramebuffer { pub fn render( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, clear_cd: &Rc, blend_buffer: Option<&Rc>, blend_cd: &Rc, ) -> Result, GfxError> { self.clone().render_with_region( acquire_sync, release_sync, cd, ops, clear, clear_cd, &self.full_region(), blend_buffer, blend_cd, ) } pub fn clear( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, ) -> Result, GfxError> { self.clear_with( acquire_sync, release_sync, cd, &Color::TRANSPARENT, &cd.linear, ) } pub fn clear_with( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, color: &Color, color_cd: &Rc, ) -> Result, GfxError> { self.render( acquire_sync, release_sync, cd, &[], Some(color), color_cd, None, cd, ) } pub fn logical_size(&self, transform: Transform) -> (i32, i32) { logical_size(self.physical_size(), transform) } pub fn renderer_base<'a>( &self, ops: &'a mut Vec, scale: Scale, transform: Transform, ) -> RendererBase<'a> { renderer_base(self.physical_size(), ops, scale, transform) } pub fn copy_texture( self: &Rc, fb_acquire_sync: AcquireSync, fb_release_sync: ReleaseSync, fb_cd: &Rc, texture: &Rc, texture_cd: &Rc, resv: Option<&Rc>, acquire_sync: AcquireSync, release_sync: ReleaseSync, x: i32, y: i32, ) -> Result, GfxError> { let mut ops = vec![]; let scale = Scale::from_int(1); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); renderer.render_texture( texture, None, x, y, None, None, scale, None, resv.cloned(), acquire_sync, release_sync, false, texture_cd, RenderIntent::Perceptual, AlphaMode::PremultipliedElectrical, ); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); self.render( fb_acquire_sync, fb_release_sync, fb_cd, &ops, clear, &fb_cd.linear, None, fb_cd, ) } pub fn render_custom( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, scale: Scale, clear: Option<&Color>, clear_cd: &Rc, blend_buffer: Option<&Rc>, blend_cd: &Rc, f: &mut dyn FnMut(&mut RendererBase), ) -> Result, GfxError> { let mut ops = vec![]; let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); f(&mut renderer); self.render( acquire_sync, release_sync, cd, &ops, clear, clear_cd, blend_buffer, blend_cd, ) } pub fn create_render_pass( &self, node: &dyn Node, state: &State, cursor_rect: Option, scale: Scale, render_cursor: bool, render_hardware_cursor: bool, black_background: bool, fill_black_in_grace_period: bool, transform: Transform, visualizer: Option<&DamageVisualizer>, ) -> GfxRenderPass { create_render_pass( self.physical_size(), node, state, cursor_rect, scale, render_cursor, render_hardware_cursor, black_background, fill_black_in_grace_period, transform, visualizer, ) } pub fn perform_render_pass( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, pass: &GfxRenderPass, region: &Region, blend_buffer: Option<&Rc>, blend_cd: &Rc, ) -> Result, GfxError> { self.clone().render_with_region( acquire_sync, release_sync, cd, &pass.ops, pass.clear.as_ref(), &pass.clear_cd, region, blend_buffer, blend_cd, ) } pub fn render_output( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, node: &OutputNode, state: &State, cursor_rect: Option, scale: Scale, render_hardware_cursor: bool, fill_black_in_grace_period: bool, blend_buffer: Option<&Rc>, blend_cd: &Rc, ) -> Result, GfxError> { self.render_node( acquire_sync, release_sync, cd, node, state, cursor_rect, scale, true, render_hardware_cursor, node.has_fullscreen(), fill_black_in_grace_period, node.global.persistent.transform.get(), blend_buffer, blend_cd, ) } pub fn render_node( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, node: &dyn Node, state: &State, cursor_rect: Option, scale: Scale, render_cursor: bool, render_hardware_cursor: bool, black_background: bool, fill_black_in_grace_period: bool, transform: Transform, blend_buffer: Option<&Rc>, blend_cd: &Rc, ) -> Result, GfxError> { let pass = self.create_render_pass( node, state, cursor_rect, scale, render_cursor, render_hardware_cursor, black_background, fill_black_in_grace_period, transform, None, ); self.perform_render_pass( acquire_sync, release_sync, cd, &pass, &self.full_region(), blend_buffer, blend_cd, ) } pub fn render_hardware_cursor( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, cursor: &dyn Cursor, state: &State, scale: Scale, transform: Transform, cd: &Rc, ) -> Result, GfxError> { let mut ops = vec![]; let mut renderer = Renderer { base: self.renderer_base(&mut ops, scale, transform), state, logical_extents: Rect::new_empty(0, 0), pixel_extents: { let (width, height) = self.logical_size(transform); Rect::new_saturating(0, 0, width, height) }, stretch: None, corner_radius: None, }; cursor.render_hardware_cursor(&mut renderer); self.render( acquire_sync, release_sync, cd, &ops, Some(&Color::TRANSPARENT), &cd.linear, None, cd, ) } } pub trait GfxImage { fn to_framebuffer(self: Rc) -> Result, GfxError>; fn to_texture(self: Rc) -> Result, GfxError>; fn width(&self) -> i32; fn height(&self) -> i32; } pub trait GfxTexture: Any + Debug { fn size(&self) -> (i32, i32); fn dmabuf(&self) -> Option<&DmaBuf>; fn format(&self) -> &'static Format; } pub trait ShmGfxTexture: GfxTexture {} pub trait AsyncShmGfxTextureCallback { fn completed(self: Rc, res: Result<(), GfxError>); } bitflags! { StagingBufferUsecase: u32; STAGING_UPLOAD, STAGING_DOWNLOAD, } pub trait GfxStagingBuffer: Any { fn size(&self) -> usize; } pub trait GfxBuffer: Any {} pub trait AsyncShmGfxTextureTransferCancellable { fn cancel(&self, id: u64); } pub struct PendingShmTransfer { cancel: Rc, id: u64, } pub trait ShmMemory { fn len(&self) -> usize; fn safe_access(&self) -> ShmMemoryBacking; fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box>; } pub enum ShmMemoryBacking { Ptr(*const [Cell]), Fd(Rc, usize), } impl ShmMemory for Vec> { fn len(&self) -> usize { self.len() } fn safe_access(&self) -> ShmMemoryBacking { ShmMemoryBacking::Ptr(&**self) } fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box> { f(self); Ok(()) } } pub trait AsyncShmGfxTexture: GfxTexture { fn staging_size(&self) -> usize { 0 } fn async_upload( self: Rc, staging: &Rc, callback: Rc, mem: Rc, damage: Region, ) -> Result, GfxError>; fn async_upload_from_buffer( self: Rc, buf: &Rc, callback: Rc, damage: Region, ) -> Result, GfxError> { let _ = buf; let _ = callback; let _ = damage; #[derive(Debug, Error)] #[error("Host buffers are not supported")] struct E; Err(GfxError(Box::new(E))) } fn sync_upload(self: Rc, shm: &[Cell], damage: Region) -> Result<(), GfxError>; fn compatible_with( &self, format: &'static Format, width: i32, height: i32, stride: i32, ) -> bool; } pub trait GfxContext: Debug { fn reset_status(&self) -> Option; fn render_node(&self) -> Option>; fn formats(&self) -> &Rc>; fn fast_ram_access(&self) -> bool; fn dmabuf_fb(self: Rc, buf: &DmaBuf) -> Result, GfxError> { self.dmabuf_img(buf)?.to_framebuffer() } fn dmabuf_img(self: Rc, buf: &DmaBuf) -> Result, GfxError>; fn shmem_texture( self: Rc, old: Option>, data: &[Cell], format: &'static Format, width: i32, height: i32, stride: i32, damage: Option<&[Rect]>, ) -> Result, GfxError>; fn async_shmem_texture( self: Rc, format: &'static Format, width: i32, height: i32, stride: i32, cpu_worker: &Rc, ) -> Result, GfxError>; fn allocator(&self) -> Rc; fn gfx_api(&self) -> GfxApi; fn create_internal_fb( self: Rc, cpu_worker: &Rc, width: i32, height: i32, stride: i32, format: &'static Format, ) -> Result, GfxError>; fn syncobj_ctx(&self) -> Option<&Rc>; fn create_staging_buffer( &self, size: usize, usecase: StagingBufferUsecase, ) -> Rc { let _ = usecase; struct Dummy(usize); impl GfxStagingBuffer for Dummy { fn size(&self) -> usize { self.0 } } Rc::new(Dummy(size)) } fn acquire_blend_buffer( &self, width: i32, height: i32, ) -> Result, GfxError>; fn supports_color_management(&self) -> bool { false } fn supports_alpha_modes(&self) -> bool { false } fn supports_invalid_modifier(&self) -> bool { false } fn create_dmabuf_buffer( &self, dmabuf: &OwnedFd, offset: usize, size: usize, format: &'static Format, ) -> Result, GfxError> { let _ = dmabuf; let _ = offset; let _ = size; let _ = format; #[derive(Debug, Error)] #[error("Host buffers are not supported")] struct E; Err(GfxError(Box::new(E))) } } #[derive(Clone, Debug)] pub struct GfxWriteModifier { pub needs_render_usage: bool, } pub fn needs_render_usage<'a>(mut modifiers: impl Iterator) -> bool { modifiers.any(|m| m.needs_render_usage) } #[derive(Debug)] pub struct GfxFormat { pub format: &'static Format, pub read_modifiers: IndexSet, pub write_modifiers: IndexMap, pub supports_shm: bool, } #[derive(Error)] #[error(transparent)] pub struct GfxError(pub Box); impl Debug for GfxError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Debug::fmt(&self.0, f) } } impl GfxFormat { pub fn cross_intersect(&self, other: &GfxFormat) -> GfxFormat { assert_eq!(self.format, other.format); GfxFormat { format: self.format, read_modifiers: self .read_modifiers .iter() .copied() .filter(|m| other.write_modifiers.contains_key(m)) .collect(), write_modifiers: self .write_modifiers .iter() .map(|(m, v)| (*m, v.clone())) .filter(|(m, _)| other.read_modifiers.contains(m)) .collect(), supports_shm: self.supports_shm && other.supports_shm, } } } pub fn cross_intersect_formats( local: &AHashMap, remote: &AHashMap, ) -> AHashMap { let mut res = AHashMap::new(); for lf in local.values() { if let Some(rf) = remote.get(&lf.format.drm) { let f = lf.cross_intersect(rf); if f.read_modifiers.is_empty() && f.write_modifiers.is_empty() { continue; } res.insert(f.format.drm, f); } } res } impl PendingShmTransfer { pub fn new(cancel: Rc, id: u64) -> Self { Self { cancel, id } } } impl Drop for PendingShmTransfer { fn drop(&mut self) { self.cancel.cancel(self.id); } } pub fn create_render_pass( physical_size: (i32, i32), node: &dyn Node, state: &State, cursor_rect: Option, scale: Scale, render_cursor: bool, render_hardware_cursor: bool, black_background: bool, fill_black_in_grace_period: bool, transform: Transform, visualizer: Option<&DamageVisualizer>, ) -> GfxRenderPass { if fill_black_in_grace_period && state.idle.in_grace_period.get() { return GfxRenderPass { ops: vec![], clear: Some(Color::SOLID_BLACK), clear_cd: state.color_manager.srgb_gamma22().linear.clone(), }; } let mut ops = vec![]; let mut renderer = Renderer { base: renderer_base(physical_size, &mut ops, scale, transform), state, logical_extents: node.node_absolute_position().at_point(0, 0), pixel_extents: { let (width, height) = logical_size(physical_size, transform); Rect::new_saturating(0, 0, width, height) }, stretch: None, corner_radius: None, }; node.node_render(&mut renderer, 0, 0, None); if let Some(rect) = cursor_rect { let seats = state.globals.lock_seats(); for seat in seats.values() { let (x, y) = seat.pointer_cursor().position_int(); if let Some(im) = seat.input_method() { for (_, popup) in im.popups() { if popup.surface.node_visible() { let pos = popup.surface.buffer_abs_pos.get(); let extents = popup.surface.extents.get().move_(pos.x1(), pos.y1()); if extents.intersects(&rect) { let (x, y) = rect.translate(pos.x1(), pos.y1()); renderer.render_surface(&popup.surface, x, y, None); } } } } if let Some(highlight) = seat.ui_drag_highlight() { renderer.render_highlight(&highlight.move_(-rect.x1(), -rect.y1())); } if let Some(drag) = seat.toplevel_drag() { drag.render(&mut renderer, &rect, x, y); } if let Some(dnd_icon) = seat.dnd_icon() { dnd_icon.render(&mut renderer, &rect, x, y); } if render_cursor { let cursor_user_group = seat.cursor_group(); if (render_hardware_cursor || !cursor_user_group.hardware_cursor()) && let Some(cursor_user) = cursor_user_group.active() && let Some(cursor) = cursor_user.get() { cursor.tick(); let (mut x, mut y) = cursor_user.position(); x -= Fixed::from_int(rect.x1()); y -= Fixed::from_int(rect.y1()); cursor.render(&mut renderer, x, y); } } } } if let Some(visualizer) = visualizer && let Some(cursor_rect) = cursor_rect { visualizer.render(&cursor_rect, &mut renderer.base); } let c = match black_background { true => Color::SOLID_BLACK, false => state.theme.colors.background.get(), }; GfxRenderPass { ops, clear: Some(c), clear_cd: state.color_manager.srgb_gamma22().linear.clone(), } } pub fn renderer_base<'a>( physical_size: (i32, i32), ops: &'a mut Vec, scale: Scale, transform: Transform, ) -> RendererBase<'a> { let (width, height) = logical_size(physical_size, transform); RendererBase { ops, scaled: scale != 1, scale, scalef: scale.to_f64(), transform, fb_width: width as _, fb_height: height as _, } } pub fn logical_size(physical_size: (i32, i32), transform: Transform) -> (i32, i32) { transform.maybe_swap(physical_size) } pub struct ReservedSyncobjPoint { pub ctx: Rc, pub syncobj: Rc, pub point: SyncobjPoint, pub sync_file: OnceCell>, pub signaled: Eventfd, } impl Debug for ReservedSyncobjPoint { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("ReservedSyncobjPoint") .field("syncobj", &self.syncobj.id()) .field("point", &self.point) .finish_non_exhaustive() } } #[derive(Clone, Debug)] pub enum FdSync { SyncFile(SyncFile), Syncobj(Rc), } unsafe impl UnsafeCellCloneSafe for FdSync {} impl FdSync { pub async fn try_signaled(&self, ring: &Rc) -> Result<(), IoUringError> { match self { FdSync::SyncFile(f) => ring.readable(&f.0).await.map(drop), FdSync::Syncobj(obj) => obj.signaled.signaled().await, } } pub async fn signaled(&self, ring: &Rc, name: &str) { if let Err(e) = self.try_signaled(ring).await { log::error!( "Could not wait for {name} sync to become signaled: {}", ErrorFmt(e), ); } } pub fn signaled_blocking(&self, name: &str) { let res = match self { FdSync::Syncobj(obj) => obj.signaled.signaled_blocking(), FdSync::SyncFile(f) => { let mut pollfd = c::pollfd { fd: f.raw(), events: c::POLLIN, revents: 0, }; uapi::poll(slice::from_mut(&mut pollfd), -1) .map(drop) .to_os_error() } }; if let Err(e) = res { log::error!( "Could not wait for {name} sync to become signaled: {}", ErrorFmt(e), ); } } pub fn is_unsignaled(&self) -> bool { !self.is_signaled() } pub fn is_signaled(&self) -> bool { match self { FdSync::Syncobj(obj) => obj.signaled.is_signaled(), FdSync::SyncFile(f) => { let mut pollfd = c::pollfd { fd: f.raw(), events: c::POLLIN, revents: 0, }; uapi::poll(slice::from_mut(&mut pollfd), 0) == Ok(1) } } } pub fn get_sync_file(&self) -> Option<&SyncFile> { match self { FdSync::SyncFile(f) => Some(f), FdSync::Syncobj(obj) => { if obj.signaled.is_signaled() { return None; } obj.sync_file .get_or_init(|| { match obj.ctx.export_sync_file_blocking(&obj.syncobj, obj.point) { Ok(sf) => Some(sf), Err(e) => { log::error!("Could not export sync file: {}", ErrorFmt(e)); None } } }) .as_ref() } } } } #[derive(Debug)] pub struct DirectScanoutPosition { pub src_width: i32, pub src_height: i32, pub crtc_x: i32, pub crtc_y: i32, pub crtc_width: i32, pub crtc_height: i32, } impl GfxRenderPass { pub fn prepare_direct_scanout( &self, mode_w: i32, mode_h: i32, blend_cd: &Rc, cd: &Rc, no_scaling: bool, ) -> Option<(&CopyTexture, DirectScanoutPosition)> { let ct = 'ct: { let mut ops = self.ops.iter().rev(); let ct = 'ct2: { for opt in &mut ops { match opt { GfxApiOpt::Sync => {} GfxApiOpt::FillRect(_) => { // Top-most layer must be a texture. return None; } GfxApiOpt::CopyTexture(ct) => break 'ct2 ct, GfxApiOpt::RoundedFillRect(_) => return None, GfxApiOpt::RoundedCopyTexture(_) => return None, } } return None; }; if ct.alpha_mode != AlphaMode::PremultipliedElectrical { // Direct scanout requires premultiplied electrical alpha. return None; } if !ct.cd.embeds_into(cd) { // Direct scanout requires embeddable color descriptions. return None; } if !ct.opaque && !ct.cd.embeds_into(blend_cd) { // Blending changes the appearance of translucent buffers. return None; } if ct.alpha.is_some() { // Direct scanout with alpha factor is not supported. return None; } if !ct.tex.format().has_alpha && ct.target.is_covering() { // Texture covers the entire screen and is opaque. break 'ct ct; } for opt in ops { match opt { GfxApiOpt::Sync => {} GfxApiOpt::FillRect(fr) => { if fr.effective_color() == Color::SOLID_BLACK { // Black fills can be ignored because this is the CRTC background color. if fr.rect.is_covering() { // If fill covers the entire screen, we don't have to look further. break 'ct ct; } } else { // Fill could be visible. return None; } } GfxApiOpt::CopyTexture(_) => { // Texture could be visible. return None; } GfxApiOpt::RoundedFillRect(_) => return None, GfxApiOpt::RoundedCopyTexture(_) => return None, } } if let Some(clear) = self.clear && clear != Color::SOLID_BLACK { // Background could be visible. return None; } ct }; if let AcquireSync::None | AcquireSync::Implicit = ct.acquire_sync { // Cannot perform scanout without explicit sync. return None; } if ct.source.buffer_transform != ct.target.output_transform { // Rotations and mirroring are not supported. return None; } if !ct.source.is_covering() { // Viewports are not supported. return None; } if ct.target.x1 < -1.0 || ct.target.y1 < -1.0 || ct.target.x2 > 1.0 || ct.target.y2 > 1.0 { // Rendering outside the screen is not supported. return None; } let (tex_w, tex_h) = ct.tex.size(); let (x1, x2, y1, y2) = { let plane_w = mode_w as f32; let plane_h = mode_h as f32; let ((x1, x2), (y1, y2)) = ct .target .output_transform .maybe_swap(((ct.target.x1, ct.target.x2), (ct.target.y1, ct.target.y2))); ( (x1 + 1.0) * plane_w / 2.0, (x2 + 1.0) * plane_w / 2.0, (y1 + 1.0) * plane_h / 2.0, (y2 + 1.0) * plane_h / 2.0, ) }; let (crtc_w, crtc_h) = (x2 - x1, y2 - y1); if crtc_w < 0.0 || crtc_h < 0.0 { // Flipping x or y axis is not supported. return None; } if no_scaling && (tex_w as f32, tex_h as f32) != (crtc_w, crtc_h) { // If scaling is not supported, we cannot scale the texture. return None; } let position = DirectScanoutPosition { src_width: tex_w, src_height: tex_h, crtc_x: x1 as _, crtc_y: y1 as _, crtc_width: crtc_w as _, crtc_height: crtc_h as _, }; Some((ct, position)) } }