Cache texture in OpenAnimation

Don't recreate it unless the size changes. This lays the groundwork for also
tracking damage in the future.
This commit is contained in:
Ivan Molodetskikh
2025-02-27 08:09:44 +03:00
parent 1c521e4831
commit 74a30be10b
3 changed files with 117 additions and 244 deletions
+13 -16
View File
@@ -2,27 +2,27 @@ use std::collections::HashMap;
use anyhow::Context as _;
use glam::{Mat3, Vec2};
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::utils::{
Relocate, RelocateRenderElement, RescaleRenderElement,
};
use smithay::backend::renderer::element::{Kind, RenderElement};
use smithay::backend::renderer::gles::{GlesRenderer, Uniform};
use smithay::backend::renderer::Texture;
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
use crate::animation::Animation;
use crate::niri_render_elements;
use crate::render_helpers::offscreen::OffscreenBuffer;
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
use crate::render_helpers::render_to_encompassing_texture;
use crate::render_helpers::shader_element::ShaderRenderElement;
use crate::render_helpers::shaders::{mat3_uniform, ProgramType, Shaders};
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
use crate::render_helpers::texture::TextureRenderElement;
#[derive(Debug)]
pub struct OpenAnimation {
anim: Animation,
random_seed: f32,
buffer: OffscreenBuffer,
}
niri_render_elements! {
@@ -37,6 +37,7 @@ impl OpenAnimation {
Self {
anim,
random_seed: fastrand::f32(),
buffer: OffscreenBuffer::default(),
}
}
@@ -59,17 +60,15 @@ impl OpenAnimation {
let progress = self.anim.value();
let clamped_progress = self.anim.clamped_value().clamp(0., 1.);
let (texture, _sync_point, geo) = render_to_encompassing_texture(
renderer,
scale,
Transform::Normal,
Fourcc::Abgr8888,
elements,
)
.context("error rendering to texture")?;
let (buffer, _sync_point, offset) = self
.buffer
.render(renderer, scale, elements)
.context("error rendering to offscreen buffer")?;
let offset = geo.loc.to_f64().to_logical(scale);
let texture_size = geo.size.to_f64().to_logical(scale);
// OffscreenBuffer renders with Transform::Normal and the scale that we passed, so we can
// assume that below.
let texture = buffer.texture();
let texture_size = buffer.logical_size();
if Shaders::get(renderer).program(ProgramType::Open).is_some() {
let mut area = Rectangle::new(location + offset, texture_size);
@@ -120,8 +119,6 @@ impl OpenAnimation {
.into());
}
let buffer =
TextureBuffer::from_texture(renderer, texture, scale, Transform::Normal, Vec::new());
let elem = TextureRenderElement::from_texture_buffer(
buffer,
Point::from((0., 0.)),
+93 -228
View File
@@ -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 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::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};
use super::texture::TextureBuffer;
use super::{encompassing_geo, render_elements};
/// Renders elements into an off-screen buffer.
/// Buffer for offscreen rendering.
#[derive(Debug)]
pub struct OffscreenRenderElement {
// The texture, if rendering succeeded.
texture: Option<PrimaryGpuTextureRenderElement>,
// 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<Option<TextureBuffer<GlesTexture>>>,
}
impl OffscreenRenderElement {
pub fn new(
impl OffscreenBuffer {
pub fn render(
&self,
renderer: &mut GlesRenderer,
scale: i32,
scale: Scale<f64>,
elements: &[impl RenderElement<GlesRenderer>],
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<GlesTexture>, SyncPoint, Point<f64, Logical>)> {
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(
let buffer_size = geo.size.to_logical(1).to_buffer(1, Transform::Normal);
let offset = geo.loc.to_f64().to_logical(scale);
let mut buffer = self.buffer.borrow_mut();
// 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;
*buffer = None;
} else if !buf.is_texture_reference_unique() {
reason = "not unique";
*buffer = None;
}
} else {
reason = "first render";
}
let buffer = if let Some(buffer) = buffer.as_mut() {
buffer
} else {
trace!("creating new texture: {reason}");
let span = tracy_client::span!("creating offscreen buffer");
span.emit_text(reason);
let texture: GlesTexture = renderer
.create_buffer(Fourcc::Abgr8888, buffer_size)
.context("error creating texture")?;
buffer.insert(TextureBuffer::from_texture(
renderer,
texture,
scale,
Transform::Normal,
Vec::new(),
))
};
// Update the texture scale.
buffer.set_texture_scale(scale);
// Increment the commit counter since we're rendering new contents to the buffer.
buffer.increment_commit_counter();
// Render to the buffer.
let mut texture = buffer.texture().clone();
let mut target = renderer
.bind(&mut texture)
.context("error binding texture")?;
let sync_point = render_elements(
renderer,
&mut target,
geo.size,
Scale::from(scale as f64),
scale,
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,
}
}
}
)?;
Ok((buffer.clone(), sync_point, offset))
}
}
impl Element for OffscreenRenderElement {
fn id(&self) -> &Id {
if let Some(texture) = &self.texture {
texture.id()
} else {
self.fallback.id()
}
}
fn current_commit(&self) -> CommitCounter {
if let Some(texture) = &self.texture {
texture.current_commit()
} else {
self.fallback.current_commit()
}
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
if let Some(texture) = &self.texture {
texture.geometry(scale)
} else {
self.fallback.geometry(scale)
}
}
fn transform(&self) -> Transform {
if let Some(texture) = &self.texture {
texture.transform()
} else {
self.fallback.transform()
}
}
fn src(&self) -> Rectangle<f64, Buffer> {
if let Some(texture) = &self.texture {
texture.src()
} else {
self.fallback.src()
}
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
if let Some(texture) = &self.texture {
texture.damage_since(scale, commit)
} else {
self.fallback.damage_since(scale, commit)
}
}
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
if let Some(texture) = &self.texture {
texture.opaque_regions(scale)
} else {
self.fallback.opaque_regions(scale)
}
}
fn alpha(&self) -> f32 {
if let Some(texture) = &self.texture {
texture.alpha()
} else {
self.fallback.alpha()
}
}
fn kind(&self) -> Kind {
if let Some(texture) = &self.texture {
texture.kind()
} else {
self.fallback.kind()
}
}
}
impl RenderElement<GlesRenderer> for OffscreenRenderElement {
fn draw(
&self,
frame: &mut GlesFrame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
opaque_regions: &[Rectangle<i32, Physical>],
) -> Result<(), GlesError> {
let gles_frame = frame.as_gles_frame();
if let Some(texture) = &self.texture {
RenderElement::<GlesRenderer>::draw(
texture,
gles_frame,
src,
dst,
damage,
opaque_regions,
)?;
} else {
RenderElement::<GlesRenderer>::draw(
&self.fallback,
gles_frame,
src,
dst,
damage,
opaque_regions,
)?;
}
Ok(())
}
fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
if let Some(texture) = &self.texture {
texture.underlying_storage(renderer)
} else {
self.fallback.underlying_storage(renderer)
}
}
}
impl<'render> RenderElement<TtyRenderer<'render>> for OffscreenRenderElement {
fn draw(
&self,
frame: &mut TtyFrame<'_, '_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
opaque_regions: &[Rectangle<i32, Physical>],
) -> Result<(), TtyRendererError<'render>> {
let gles_frame = frame.as_gles_frame();
if let Some(texture) = &self.texture {
RenderElement::<GlesRenderer>::draw(
texture,
gles_frame,
src,
dst,
damage,
opaque_regions,
)?;
} else {
RenderElement::<GlesRenderer>::draw(
&self.fallback,
gles_frame,
src,
dst,
damage,
opaque_regions,
)?;
}
Ok(())
}
fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option<UnderlyingStorage> {
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),
}
}
}
+11
View File
@@ -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<T> TextureBuffer<T> {
pub fn set_texture_transform(&mut self, transform: Transform) {
self.transform = transform;
}
pub fn increment_commit_counter(&mut self) {
self.commit_counter.increment();
}
}
impl<T: Texture> TextureBuffer<T> {
@@ -115,6 +120,12 @@ impl<T: Texture> TextureBuffer<T> {
}
}
impl TextureBuffer<GlesTexture> {
pub fn is_texture_reference_unique(&mut self) -> bool {
self.texture.is_unique_reference()
}
}
impl<T> TextureRenderElement<T> {
pub fn from_texture_buffer(
buffer: TextureBuffer<T>,