aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-03-01 09:45:57 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-03-10 07:59:14 +0300
commit7b033aa7c6ed4b634224b001b3514276b9c06c64 (patch)
treecfbd975987b2f6c04ca8d7432891363f5dcfcfef /src
parentefd8372b202ec09fe8b07b38a592790cb88db67f (diff)
downloadniri-7b033aa7c6ed4b634224b001b3514276b9c06c64.tar.gz
niri-7b033aa7c6ed4b634224b001b3514276b9c06c64.tar.bz2
niri-7b033aa7c6ed4b634224b001b3514276b9c06c64.zip
offscreen: Track and return damage
This is the second part of the damage equation: now the offscreen element itself reports correct damage, so partial redraws to the texture don't cause full redraws of the texture element itself.
Diffstat (limited to 'src')
-rw-r--r--src/layout/opening_window.rs34
-rw-r--r--src/render_helpers/offscreen.rs244
-rw-r--r--src/render_helpers/texture.rs4
3 files changed, 230 insertions, 52 deletions
diff --git a/src/layout/opening_window.rs b/src/layout/opening_window.rs
index 860c8101..7e24b4cb 100644
--- a/src/layout/opening_window.rs
+++ b/src/layout/opening_window.rs
@@ -12,11 +12,9 @@ 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::offscreen::{OffscreenBuffer, OffscreenRenderElement};
use crate::render_helpers::shader_element::ShaderRenderElement;
use crate::render_helpers::shaders::{mat3_uniform, ProgramType, Shaders};
-use crate::render_helpers::texture::TextureRenderElement;
#[derive(Debug)]
pub struct OpenAnimation {
@@ -27,7 +25,7 @@ pub struct OpenAnimation {
niri_render_elements! {
OpeningWindowRenderElement => {
- Texture = RelocateRenderElement<RescaleRenderElement<PrimaryGpuTextureRenderElement>>,
+ Offscreen = RelocateRenderElement<RescaleRenderElement<OffscreenRenderElement>>,
Shader = ShaderRenderElement,
}
}
@@ -60,17 +58,18 @@ impl OpenAnimation {
let progress = self.anim.value();
let clamped_progress = self.anim.clamped_value().clamp(0., 1.);
- let (buffer, _sync_point, offset) = self
+ let (elem, _sync_point) = self
.buffer
.render(renderer, scale, elements)
.context("error rendering to offscreen buffer")?;
- // 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() {
+ // OffscreenBuffer renders with Transform::Normal and the scale that we passed, so we
+ // can assume that below.
+ let offset = elem.offset();
+ let texture = elem.texture();
+ let texture_size = elem.logical_size();
+
let mut area = Rectangle::new(location + offset, texture_size);
// Expand the area a bit to allow for more varied effects.
@@ -119,27 +118,18 @@ impl OpenAnimation {
.into());
}
- let elem = TextureRenderElement::from_texture_buffer(
- buffer,
- Point::from((0., 0.)),
- clamped_progress as f32,
- None,
- None,
- Kind::Unspecified,
- );
-
- let elem = PrimaryGpuTextureRenderElement(elem);
+ let elem = elem.with_alpha(clamped_progress as f32);
let center = geo_size.to_point().downscale(2.);
let elem = RescaleRenderElement::from_element(
elem,
- (center - offset).to_physical_precise_round(scale),
+ center.to_physical_precise_round(scale),
(progress / 2. + 0.5).max(0.),
);
let elem = RelocateRenderElement::from_element(
elem,
- (location + offset).to_physical_precise_round(scale),
+ location.to_physical_precise_round(scale),
Relocate::Relative,
);
diff --git a/src/render_helpers/offscreen.rs b/src/render_helpers/offscreen.rs
index b6bbbc12..5d33f8e8 100644
--- a/src/render_helpers/offscreen.rs
+++ b/src/render_helpers/offscreen.rs
@@ -4,18 +4,26 @@ use anyhow::Context as _;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
-use smithay::backend::renderer::element::RenderElement;
-use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
+use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
+use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture};
use smithay::backend::renderer::sync::SyncPoint;
-use smithay::backend::renderer::{Bind as _, Color32F, Offscreen as _, Texture as _};
-use smithay::utils::{Logical, Point, Scale, Transform};
+use smithay::backend::renderer::utils::{
+ CommitCounter, DamageBag, DamageSet, DamageSnapshot, OpaqueRegions,
+};
+use smithay::backend::renderer::{
+ Bind as _, Color32F, Frame as _, Offscreen as _, Renderer, Texture as _,
+};
+use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
use super::encompassing_geo;
-use super::texture::TextureBuffer;
+use super::renderer::AsGlesFrame as _;
+use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
/// Buffer for offscreen rendering.
#[derive(Debug)]
pub struct OffscreenBuffer {
+ id: Id,
+
/// The cached texture buffer.
///
/// Lazily created when `render` is called. Recreated when necessary.
@@ -24,8 +32,28 @@ pub struct OffscreenBuffer {
#[derive(Debug)]
struct Inner {
- buffer: TextureBuffer<GlesTexture>,
+ /// The texture with offscreened contents.
+ texture: GlesTexture,
+ /// Id of the renderer that the texture comes from.
+ renderer_id: usize,
+ /// Scale of the texture.
+ scale: Scale<f64>,
+ /// Damage tracker for drawing to the texture.
damage: OutputDamageTracker,
+ /// Damage of this offscreen element itself facing outside.
+ outer_damage: DamageBag<i32, Buffer>,
+}
+
+#[derive(Debug, Clone)]
+pub struct OffscreenRenderElement {
+ id: Id,
+ texture: GlesTexture,
+ renderer_id: usize,
+ scale: Scale<f64>,
+ damage: DamageSnapshot<i32, Buffer>,
+ offset: Point<f64, Logical>,
+ alpha: f32,
+ kind: Kind,
}
impl OffscreenBuffer {
@@ -34,7 +62,7 @@ impl OffscreenBuffer {
renderer: &mut GlesRenderer,
scale: Scale<f64>,
elements: &[impl RenderElement<GlesRenderer>],
- ) -> anyhow::Result<(TextureBuffer<GlesTexture>, SyncPoint, Point<f64, Logical>)> {
+ ) -> anyhow::Result<(OffscreenRenderElement, SyncPoint)> {
let _span = tracy_client::span!("OffscreenBuffer::render");
let geo = encompassing_geo(scale, elements.iter());
@@ -50,8 +78,13 @@ impl OffscreenBuffer {
// Check if we need to create or recreate the texture.
let size_string;
let mut reason = "";
- if let Some(Inner { buffer, .. }) = inner.as_mut() {
- let old_size = buffer.texture().size();
+ if let Some(Inner {
+ texture,
+ renderer_id,
+ ..
+ }) = inner.as_mut()
+ {
+ let old_size = texture.size();
if old_size != buffer_size {
size_string = format!(
"size changed from {} × {} to {} × {}",
@@ -60,10 +93,14 @@ impl OffscreenBuffer {
reason = &size_string;
*inner = None;
- } else if !buffer.is_texture_reference_unique() {
+ } else if !texture.is_unique_reference() {
reason = "not unique";
*inner = None;
+ } else if *renderer_id != renderer.id() {
+ reason = "renderer id changed";
+
+ *inner = None;
}
} else {
reason = "first render";
@@ -80,30 +117,29 @@ impl OffscreenBuffer {
.create_buffer(Fourcc::Abgr8888, buffer_size)
.context("error creating texture")?;
- let buffer = TextureBuffer::from_texture(
- renderer,
- texture,
- scale,
- Transform::Normal,
- Vec::new(),
- );
let damage = OutputDamageTracker::new(geo.size, scale, Transform::Normal);
- inner.insert(Inner { buffer, damage })
+ inner.insert(Inner {
+ texture,
+ renderer_id: renderer.id(),
+ scale,
+ damage,
+ outer_damage: DamageBag::default(),
+ })
};
// Recreate the damage tracker if the scale changes. We already recreate it for buffer size
// changes, and transform is always Normal.
- if inner.buffer.texture_scale() != scale {
- inner.buffer.set_texture_scale(scale);
+ if inner.scale != scale {
+ inner.scale = scale;
trace!("recreating damage tracker due to scale change");
inner.damage = OutputDamageTracker::new(geo.size, scale, Transform::Normal);
+ inner.outer_damage = DamageBag::default();
}
let res = {
- let mut texture = inner.buffer.texture().clone();
- let mut target = renderer.bind(&mut texture)?;
+ let mut target = renderer.bind(&mut inner.texture)?;
inner.damage.render_output(
renderer,
&mut target,
@@ -113,12 +149,29 @@ impl OffscreenBuffer {
)?
};
- if res.damage.is_some() {
- // Increment the commit counter if some contents updated.
- inner.buffer.increment_commit_counter();
+ // Add the resulting damage to the outer tracker.
+ if let Some(damage) = res.damage {
+ // OutputDamageTracker gives us Physical coordinate space, but it's actually the Buffer
+ // space because we were rendering to a texture.
+ let size = geo.size.to_logical(1);
+ let damage = damage
+ .iter()
+ .map(|rect| rect.to_logical(1).to_buffer(1, Transform::Normal, &size));
+ inner.outer_damage.add(damage);
}
- Ok((inner.buffer.clone(), res.sync, offset))
+ let elem = OffscreenRenderElement {
+ id: self.id.clone(),
+ texture: inner.texture.clone(),
+ renderer_id: inner.renderer_id,
+ scale,
+ damage: inner.outer_damage.snapshot(),
+ offset,
+ alpha: 1.,
+ kind: Kind::Unspecified,
+ };
+
+ Ok((elem, res.sync))
}
}
@@ -126,6 +179,145 @@ impl Default for OffscreenBuffer {
fn default() -> Self {
OffscreenBuffer {
inner: RefCell::new(None),
+ id: Id::new(),
+ }
+ }
+}
+
+impl OffscreenRenderElement {
+ pub fn texture(&self) -> &GlesTexture {
+ &self.texture
+ }
+
+ pub fn offset(&self) -> Point<f64, Logical> {
+ self.offset
+ }
+
+ pub fn with_alpha(mut self, alpha: f32) -> Self {
+ self.alpha = alpha;
+ self
+ }
+
+ pub fn logical_size(&self) -> Size<f64, Logical> {
+ self.texture
+ .size()
+ .to_f64()
+ .to_logical(self.scale, Transform::Normal)
+ }
+
+ fn damage_since(&self, commit: Option<CommitCounter>) -> DamageSet<i32, Buffer> {
+ self.damage
+ .damage_since(commit)
+ .unwrap_or_else(|| DamageSet::from_slice(&[Rectangle::from_size(self.texture.size())]))
+ }
+}
+
+impl Element for OffscreenRenderElement {
+ fn id(&self) -> &Id {
+ &self.id
+ }
+
+ fn current_commit(&self) -> CommitCounter {
+ self.damage.current_commit()
+ }
+
+ fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
+ let logical_geo = Rectangle::new(self.offset, self.logical_size());
+ logical_geo.to_physical_precise_round(scale)
+ }
+
+ fn transform(&self) -> Transform {
+ Transform::Normal
+ }
+
+ fn src(&self) -> Rectangle<f64, Buffer> {
+ Rectangle::from_size(self.texture.size()).to_f64()
+ }
+
+ fn damage_since(
+ &self,
+ scale: Scale<f64>,
+ commit: Option<CommitCounter>,
+ ) -> DamageSet<i32, Physical> {
+ let texture_size = self.texture.size().to_f64();
+
+ self.damage_since(commit)
+ .into_iter()
+ .map(|rect| {
+ rect.to_f64()
+ .to_logical(self.scale, Transform::Normal, &texture_size)
+ .to_physical_precise_up(scale)
+ })
+ .collect()
+ }
+
+ fn opaque_regions(&self, _scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
+ OpaqueRegions::default()
+ }
+
+ fn alpha(&self) -> f32 {
+ self.alpha
+ }
+
+ fn kind(&self) -> Kind {
+ self.kind
+ }
+}
+
+impl RenderElement<GlesRenderer> for OffscreenRenderElement {
+ fn draw(
+ &self,
+ frame: &mut GlesFrame<'_, '_>,
+ src: Rectangle<f64, Buffer>,
+ dest: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ opaque_regions: &[Rectangle<i32, Physical>],
+ ) -> Result<(), GlesError> {
+ if frame.id() != self.renderer_id {
+ warn!("trying to render texture from different renderer");
+ return Ok(());
}
+
+ frame.render_texture_from_to(
+ &self.texture,
+ src,
+ dest,
+ damage,
+ opaque_regions,
+ Transform::Normal,
+ self.alpha,
+ None,
+ &[],
+ )
+ }
+
+ fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage<'_>> {
+ // If scanout for things other than Wayland buffers is implemented, this will need to take
+ // the target GPU into account.
+ None
+ }
+}
+
+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();
+ RenderElement::<GlesRenderer>::draw(&self, gles_frame, src, dst, damage, opaque_regions)?;
+ Ok(())
+ }
+
+ fn underlying_storage(
+ &self,
+ _renderer: &mut TtyRenderer<'render>,
+ ) -> Option<UnderlyingStorage> {
+ // If scanout for things other than Wayland buffers is implemented, this will need to take
+ // the target GPU into account.
+ None
}
}
diff --git a/src/render_helpers/texture.rs b/src/render_helpers/texture.rs
index e88ac0e3..2967c361 100644
--- a/src/render_helpers/texture.rs
+++ b/src/render_helpers/texture.rs
@@ -105,10 +105,6 @@ 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> {