use std::ptr; use anyhow::{ensure, Context}; use niri_config::BlockOutFrom; use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::allocator::{Buffer, Fourcc}; use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement}; use smithay::backend::renderer::element::{Kind, RenderElement}; use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTarget, GlesTexture}; use smithay::backend::renderer::sync::SyncPoint; use smithay::backend::renderer::{Bind, Color32F, ExportMem, Frame, Offscreen, Renderer}; use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; use smithay::reexports::wayland_server::protocol::wl_shm; use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size, Transform}; use smithay::wayland::shm; use solid_color::{SolidColorBuffer, SolidColorRenderElement}; use self::primary_gpu_texture::PrimaryGpuTextureRenderElement; use self::texture::{TextureBuffer, TextureRenderElement}; pub mod border; pub mod clipped_surface; pub mod damage; pub mod debug; pub mod gradient_fade_texture; pub mod memory; pub mod offscreen; pub mod primary_gpu_texture; pub mod render_elements; pub mod renderer; pub mod resize; pub mod resources; pub mod shader_element; pub mod shaders; pub mod shadow; pub mod snapshot; pub mod solid_color; pub mod surface; pub mod texture; /// What we're rendering for. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RenderTarget { /// Rendering to display on screen. Output, /// Rendering for a screencast. Screencast, /// Rendering for any other screen capture. ScreenCapture, } /// Buffer with location, src and dst. #[derive(Debug)] pub struct BakedBuffer { pub buffer: B, pub location: Point, pub src: Option>, pub dst: Option>, } /// Render elements split into normal and popup. #[derive(Debug)] pub struct SplitElements { pub normal: Vec, pub popups: Vec, } pub trait ToRenderElement { type RenderElement; fn to_render_element( &self, location: Point, scale: Scale, alpha: f32, kind: Kind, ) -> Self::RenderElement; } impl RenderTarget { pub fn should_block_out(self, block_out_from: Option) -> bool { match block_out_from { None => false, Some(BlockOutFrom::Screencast) => self == RenderTarget::Screencast, Some(BlockOutFrom::ScreenCapture) => self != RenderTarget::Output, } } } impl Default for SplitElements { fn default() -> Self { Self { normal: Vec::new(), popups: Vec::new(), } } } impl IntoIterator for SplitElements { type Item = E; type IntoIter = std::iter::Chain, std::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.popups.into_iter().chain(self.normal) } } impl SplitElements { pub fn iter(&self) -> std::iter::Chain, std::slice::Iter<'_, E>> { self.popups.iter().chain(&self.normal) } pub fn into_vec(self) -> Vec { let Self { normal, mut popups } = self; popups.extend(normal); popups } pub fn extend(&mut self, other: SplitElements) { self.popups.extend(other.popups); self.normal.extend(other.normal); } } impl ToRenderElement for BakedBuffer> { type RenderElement = PrimaryGpuTextureRenderElement; fn to_render_element( &self, location: Point, _scale: Scale, alpha: f32, kind: Kind, ) -> Self::RenderElement { let elem = TextureRenderElement::from_texture_buffer( self.buffer.clone(), location + self.location, alpha, self.src, self.dst.map(|dst| dst.to_f64()), kind, ); PrimaryGpuTextureRenderElement(elem) } } impl ToRenderElement for BakedBuffer { type RenderElement = SolidColorRenderElement; fn to_render_element( &self, location: Point, _scale: Scale, alpha: f32, kind: Kind, ) -> Self::RenderElement { SolidColorRenderElement::from_buffer(&self.buffer, location + self.location, alpha, kind) } } pub fn encompassing_geo( scale: Scale, elements: impl Iterator>, ) -> Rectangle { elements .map(|ele| ele.geometry(scale)) .reduce(|a, b| a.merge(b)) .unwrap_or_default() } pub fn render_to_encompassing_texture( renderer: &mut GlesRenderer, scale: Scale, transform: Transform, fourcc: Fourcc, elements: &[impl RenderElement], ) -> anyhow::Result<(GlesTexture, SyncPoint, Rectangle)> { let geo = encompassing_geo(scale, elements.iter()); let elements = elements.iter().rev().map(|ele| { RelocateRenderElement::from_element(ele, geo.loc.upscale(-1), Relocate::Relative) }); let (texture, sync_point) = render_to_texture(renderer, geo.size, scale, transform, fourcc, elements)?; Ok((texture, sync_point, geo)) } pub fn render_to_texture( renderer: &mut GlesRenderer, size: Size, scale: Scale, transform: Transform, fourcc: Fourcc, elements: impl Iterator>, ) -> anyhow::Result<(GlesTexture, SyncPoint)> { let _span = tracy_client::span!(); let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal); let mut texture: GlesTexture = renderer .create_buffer(fourcc, buffer_size) .context("error creating texture")?; let sync_point = { let mut target = renderer .bind(&mut texture) .context("error binding texture")?; render_elements(renderer, &mut target, size, scale, transform, elements)? }; Ok((texture, sync_point)) } pub fn render_and_download( renderer: &mut GlesRenderer, size: Size, scale: Scale, transform: Transform, fourcc: Fourcc, elements: impl Iterator>, ) -> anyhow::Result { let _span = tracy_client::span!(); let (mut texture, _) = render_to_texture(renderer, size, scale, transform, fourcc, elements)?; let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal); // FIXME: would be nice to avoid binding the second time here (after render_to_texture()), but // borrowing makes this inconvenient. let target = renderer .bind(&mut texture) .context("error binding texture")?; let mapping = renderer .copy_framebuffer(&target, Rectangle::from_size(buffer_size), fourcc) .context("error copying framebuffer")?; Ok(mapping) } pub fn render_to_vec( renderer: &mut GlesRenderer, size: Size, scale: Scale, transform: Transform, fourcc: Fourcc, elements: impl Iterator>, ) -> anyhow::Result> { let _span = tracy_client::span!(); let mapping = render_and_download(renderer, size, scale, transform, fourcc, elements) .context("error rendering")?; let copy = renderer .map_texture(&mapping) .context("error mapping texture")?; Ok(copy.to_vec()) } pub fn render_to_dmabuf( renderer: &mut GlesRenderer, mut dmabuf: Dmabuf, size: Size, scale: Scale, transform: Transform, elements: impl Iterator>, ) -> anyhow::Result { let _span = tracy_client::span!(); ensure!( dmabuf.width() == size.w as u32 && dmabuf.height() == size.h as u32, "invalid buffer size" ); let mut target = renderer .bind(&mut dmabuf) .context("error binding texture")?; render_elements(renderer, &mut target, size, scale, transform, elements) } pub fn render_to_shm( renderer: &mut GlesRenderer, buffer: &WlBuffer, size: Size, scale: Scale, transform: Transform, elements: impl Iterator>, ) -> anyhow::Result<()> { let _span = tracy_client::span!(); shm::with_buffer_contents_mut(buffer, |shm_buffer, shm_len, buffer_data| { ensure!( // The buffer prefers pixels in little endian ... buffer_data.format == wl_shm::Format::Xrgb8888 && buffer_data.width == size.w && buffer_data.height == size.h && buffer_data.stride == size.w * 4 && shm_len == buffer_data.stride as usize * buffer_data.height as usize, "invalid buffer format or size" ); let mapping = render_and_download(renderer, size, scale, transform, Fourcc::Xrgb8888, elements)?; let bytes = renderer .map_texture(&mapping) .context("error mapping texture")?; unsafe { let _span = tracy_client::span!("copy_nonoverlapping"); ptr::copy_nonoverlapping(bytes.as_ptr(), shm_buffer.cast(), shm_len); } Ok(()) }) .context("expected shm buffer, but didn't get one")? } pub fn clear_dmabuf(renderer: &mut GlesRenderer, mut dmabuf: Dmabuf) -> anyhow::Result { let size = dmabuf.size(); let size = size.to_logical(1, Transform::Normal).to_physical(1); let mut target = renderer.bind(&mut dmabuf).context("error binding dmabuf")?; let mut frame = renderer .render(&mut target, size, Transform::Normal) .context("error starting frame")?; frame .clear(Color32F::TRANSPARENT, &[Rectangle::from_size(size)]) .context("error clearing")?; frame.finish().context("error finishing frame") } fn render_elements( renderer: &mut GlesRenderer, target: &mut GlesTarget, size: Size, scale: Scale, transform: Transform, elements: impl Iterator>, ) -> anyhow::Result { let transform = transform.invert(); let output_rect = Rectangle::from_size(transform.transform_size(size)); let mut frame = renderer .render(target, size, transform) .context("error starting frame")?; frame .clear(Color32F::TRANSPARENT, &[output_rect]) .context("error clearing")?; for element in elements { let src = element.src(); let dst = element.geometry(scale); if let Some(mut damage) = output_rect.intersection(dst) { damage.loc -= dst.loc; element .draw(&mut frame, src, dst, &[damage], &[]) .context("error drawing element")?; } } frame.finish().context("error finishing frame") }