From 74a30be10b347d298189a071b8ff812aedc8b08a Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Thu, 27 Feb 2025 08:09:44 +0300 Subject: Cache texture in OpenAnimation Don't recreate it unless the size changes. This lays the groundwork for also tracking damage in the future. --- src/render_helpers/offscreen.rs | 305 +++++++++++----------------------------- src/render_helpers/texture.rs | 11 ++ 2 files changed, 96 insertions(+), 220 deletions(-) (limited to 'src/render_helpers') diff --git a/src/render_helpers/offscreen.rs b/src/render_helpers/offscreen.rs index 9005e80c..9f4857af 100644 --- a/src/render_helpers/offscreen.rs +++ b/src/render_helpers/offscreen.rs @@ -1,251 +1,116 @@ +use std::cell::RefCell; + +use anyhow::Context as _; use smithay::backend::allocator::Fourcc; -use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement}; -use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage}; -use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer}; -use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions}; -use smithay::utils::{Buffer, Physical, Rectangle, Scale, Transform}; - -use super::primary_gpu_texture::PrimaryGpuTextureRenderElement; -use super::render_to_texture; -use super::renderer::AsGlesFrame; -use super::texture::{TextureBuffer, TextureRenderElement}; -use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; - -/// Renders elements into an off-screen buffer. +use smithay::backend::renderer::element::RenderElement; +use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; +use smithay::backend::renderer::sync::SyncPoint; +use smithay::backend::renderer::{Bind as _, Offscreen as _, Texture as _}; +use smithay::utils::{Logical, Point, Scale, Transform}; + +use super::texture::TextureBuffer; +use super::{encompassing_geo, render_elements}; + +/// Buffer for offscreen rendering. #[derive(Debug)] -pub struct OffscreenRenderElement { - // The texture, if rendering succeeded. - texture: Option, - // The fallback buffer in case the rendering fails. - fallback: SolidColorRenderElement, +pub struct OffscreenBuffer { + /// The cached texture buffer. + /// + /// Lazily created when `render` is called. Recreated when necessary. + buffer: RefCell>>, } -impl OffscreenRenderElement { - pub fn new( +impl OffscreenBuffer { + pub fn render( + &self, renderer: &mut GlesRenderer, - scale: i32, + scale: Scale, elements: &[impl RenderElement], - result_alpha: f32, - ) -> Self { - let _span = tracy_client::span!("OffscreenRenderElement::new"); - - let geo = elements - .iter() - .map(|ele| ele.geometry(Scale::from(f64::from(scale)))) - .reduce(|a, b| a.merge(b)) - .unwrap_or_default(); - let logical_size = geo.size.to_logical(scale); - - let fallback_buffer = SolidColorBuffer::new(logical_size, [1., 0., 0., 1.]); - let fallback = SolidColorRenderElement::from_buffer( - &fallback_buffer, - geo.loc, - Scale::from(scale as f64), - result_alpha, - Kind::Unspecified, - ); + ) -> anyhow::Result<(TextureBuffer, SyncPoint, Point)> { + let _span = tracy_client::span!("OffscreenBuffer::render"); + let geo = encompassing_geo(scale, elements.iter()); let elements = elements.iter().rev().map(|ele| { - RelocateRenderElement::from_element(ele, (-geo.loc.x, -geo.loc.y), Relocate::Relative) + RelocateRenderElement::from_element(ele, geo.loc.upscale(-1), Relocate::Relative) }); - match render_to_texture( - renderer, - geo.size, - Scale::from(scale as f64), - Transform::Normal, - Fourcc::Abgr8888, - elements, - ) { - Ok((texture, _sync_point)) => { - let buffer = TextureBuffer::from_texture( - renderer, - texture, - scale as f64, - Transform::Normal, - Vec::new(), - ); - let element = TextureRenderElement::from_texture_buffer( - buffer, - geo.loc.to_f64().to_logical(scale as f64), - result_alpha, - None, - None, - Kind::Unspecified, - ); - Self { - texture: Some(PrimaryGpuTextureRenderElement(element)), - fallback, - } - } - Err(err) => { - warn!("error off-screening elements: {err:?}"); - Self { - texture: None, - fallback, - } - } - } - } -} + let buffer_size = geo.size.to_logical(1).to_buffer(1, Transform::Normal); + let offset = geo.loc.to_f64().to_logical(scale); -impl Element for OffscreenRenderElement { - fn id(&self) -> &Id { - if let Some(texture) = &self.texture { - texture.id() - } else { - self.fallback.id() - } - } + let mut buffer = self.buffer.borrow_mut(); - fn current_commit(&self) -> CommitCounter { - if let Some(texture) = &self.texture { - texture.current_commit() - } else { - self.fallback.current_commit() - } - } + // Check if we need to create or recreate the texture. + let size_string; + let mut reason = ""; + if let Some(buf) = buffer.as_mut() { + let old_size = buf.texture().size(); + if old_size != buffer_size { + size_string = format!( + "size changed from {} × {} to {} × {}", + old_size.w, old_size.h, buffer_size.w, buffer_size.h + ); + reason = &size_string; - fn geometry(&self, scale: Scale) -> Rectangle { - if let Some(texture) = &self.texture { - texture.geometry(scale) - } else { - self.fallback.geometry(scale) - } - } + *buffer = None; + } else if !buf.is_texture_reference_unique() { + reason = "not unique"; - fn transform(&self) -> Transform { - if let Some(texture) = &self.texture { - texture.transform() + *buffer = None; + } } else { - self.fallback.transform() + reason = "first render"; } - } - fn src(&self) -> Rectangle { - if let Some(texture) = &self.texture { - texture.src() + let buffer = if let Some(buffer) = buffer.as_mut() { + buffer } else { - self.fallback.src() - } - } + trace!("creating new texture: {reason}"); + let span = tracy_client::span!("creating offscreen buffer"); + span.emit_text(reason); - fn damage_since( - &self, - scale: Scale, - commit: Option, - ) -> DamageSet { - if let Some(texture) = &self.texture { - texture.damage_since(scale, commit) - } else { - self.fallback.damage_since(scale, commit) - } - } + let texture: GlesTexture = renderer + .create_buffer(Fourcc::Abgr8888, buffer_size) + .context("error creating texture")?; - fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { - if let Some(texture) = &self.texture { - texture.opaque_regions(scale) - } else { - self.fallback.opaque_regions(scale) - } - } + buffer.insert(TextureBuffer::from_texture( + renderer, + texture, + scale, + Transform::Normal, + Vec::new(), + )) + }; - fn alpha(&self) -> f32 { - if let Some(texture) = &self.texture { - texture.alpha() - } else { - self.fallback.alpha() - } - } + // Update the texture scale. + buffer.set_texture_scale(scale); - fn kind(&self) -> Kind { - if let Some(texture) = &self.texture { - texture.kind() - } else { - self.fallback.kind() - } - } -} + // Increment the commit counter since we're rendering new contents to the buffer. + buffer.increment_commit_counter(); -impl RenderElement for OffscreenRenderElement { - fn draw( - &self, - frame: &mut GlesFrame<'_, '_>, - src: Rectangle, - dst: Rectangle, - damage: &[Rectangle], - opaque_regions: &[Rectangle], - ) -> Result<(), GlesError> { - let gles_frame = frame.as_gles_frame(); - if let Some(texture) = &self.texture { - RenderElement::::draw( - texture, - gles_frame, - src, - dst, - damage, - opaque_regions, - )?; - } else { - RenderElement::::draw( - &self.fallback, - gles_frame, - src, - dst, - damage, - opaque_regions, - )?; - } - Ok(()) - } + // Render to the buffer. + let mut texture = buffer.texture().clone(); + let mut target = renderer + .bind(&mut texture) + .context("error binding texture")?; - fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option { - if let Some(texture) = &self.texture { - texture.underlying_storage(renderer) - } else { - self.fallback.underlying_storage(renderer) - } - } -} + let sync_point = render_elements( + renderer, + &mut target, + geo.size, + scale, + Transform::Normal, + elements, + )?; -impl<'render> RenderElement> for OffscreenRenderElement { - fn draw( - &self, - frame: &mut TtyFrame<'_, '_, '_>, - src: Rectangle, - dst: Rectangle, - damage: &[Rectangle], - opaque_regions: &[Rectangle], - ) -> Result<(), TtyRendererError<'render>> { - let gles_frame = frame.as_gles_frame(); - if let Some(texture) = &self.texture { - RenderElement::::draw( - texture, - gles_frame, - src, - dst, - damage, - opaque_regions, - )?; - } else { - RenderElement::::draw( - &self.fallback, - gles_frame, - src, - dst, - damage, - opaque_regions, - )?; - } - Ok(()) + Ok((buffer.clone(), sync_point, offset)) } +} - fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option { - if let Some(texture) = &self.texture { - texture.underlying_storage(renderer) - } else { - self.fallback.underlying_storage(renderer) +impl Default for OffscreenBuffer { + fn default() -> Self { + OffscreenBuffer { + buffer: RefCell::new(None), } } } diff --git a/src/render_helpers/texture.rs b/src/render_helpers/texture.rs index a5d38a6f..e88ac0e3 100644 --- a/src/render_helpers/texture.rs +++ b/src/render_helpers/texture.rs @@ -1,5 +1,6 @@ use smithay::backend::allocator::Fourcc; use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage}; +use smithay::backend::renderer::gles::GlesTexture; use smithay::backend::renderer::utils::{CommitCounter, OpaqueRegions}; use smithay::backend::renderer::{Frame as _, ImportMem, Renderer, Texture}; use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform}; @@ -104,6 +105,10 @@ impl TextureBuffer { pub fn set_texture_transform(&mut self, transform: Transform) { self.transform = transform; } + + pub fn increment_commit_counter(&mut self) { + self.commit_counter.increment(); + } } impl TextureBuffer { @@ -115,6 +120,12 @@ impl TextureBuffer { } } +impl TextureBuffer { + pub fn is_texture_reference_unique(&mut self) -> bool { + self.texture.is_unique_reference() + } +} + impl TextureRenderElement { pub fn from_texture_buffer( buffer: TextureBuffer, -- cgit