From a04870388f0cd5caef5e57693fc6cbe4cef1279c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 5 Nov 2023 15:54:19 +0100 Subject: [PATCH] text: re-use textures if possible --- src/compositor.rs | 1 + src/portal/ptr_gui.rs | 18 +++++---- src/renderer.rs | 12 +++--- src/state.rs | 8 ++++ src/text.rs | 88 +++++++++++++++++++++++++++++++++++++---- src/tree/container.rs | 36 ++++++++++++----- src/tree/float.rs | 9 ++--- src/tree/output.rs | 16 +++++--- src/tree/placeholder.rs | 8 ++-- src/tree/workspace.rs | 2 + 10 files changed, 153 insertions(+), 45 deletions(-) diff --git a/src/compositor.rs b/src/compositor.rs index 2524bbf1..edc0e9f0 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -410,6 +410,7 @@ fn create_dummy_output(state: &Rc) { desired_output: CloneCell::new(dummy_output.global.output_id.clone()), jay_workspaces: Default::default(), capture: Cell::new(false), + title_texture: Cell::new(None), }); dummy_workspace.output_link.set(Some( dummy_output.workspaces.add_last(dummy_workspace.clone()), diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 6a232818..6c173e23 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -4,12 +4,12 @@ use { cursor::KnownCursor, fixed::Fixed, format::ARGB8888, - gfx_api::{GfxContext, GfxFramebuffer, GfxTexture}, + gfx_api::{GfxContext, GfxFramebuffer}, ifs::zwlr_layer_shell_v1::OVERLAY, portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, renderer::renderer_base::RendererBase, scale::Scale, - text::{self, TextMeasurement}, + text::{self, TextMeasurement, TextTexture}, theme::Color, utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -118,7 +118,7 @@ pub struct Button { pub bg_hover_color: Cell, pub text: RefCell, pub font: RefCell>, - pub tex: CloneCell>>, + pub tex: CloneCell>, pub owner: CloneCell>>, } @@ -162,10 +162,12 @@ impl GuiElement for Button { _max_width: f32, _max_height: f32, ) -> (f32, f32) { + let old_tex = self.tex.take(); let font = self.font.borrow_mut(); let text = self.text.borrow_mut(); let tex = text::render_fitting2( ctx, + old_tex, None, &font, &text, @@ -213,7 +215,7 @@ impl GuiElement for Button { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get()); r.render_texture( - &tex, + &tex.texture, tx.round() as _, ty.round() as _, ARGB8888, @@ -260,7 +262,7 @@ pub struct Label { pub data: GuiElementData, pub font: RefCell>, pub text: RefCell, - pub tex: CloneCell>>, + pub tex: CloneCell>, } impl Default for Label { @@ -286,10 +288,12 @@ impl GuiElement for Label { _max_width: f32, _max_height: f32, ) -> (f32, f32) { + let old_tex = self.tex.take(); let text = self.text.borrow_mut(); let font = self.font.borrow_mut(); let tex = text::render_fitting2( ctx, + old_tex, None, &font, &text, @@ -300,7 +304,7 @@ impl GuiElement for Label { ) .ok(); let (tex, width, height) = match tex { - Some((t, _)) => (Some(t.clone()), t.width(), t.height()), + Some((t, _)) => (Some(t.clone()), t.texture.width(), t.texture.height()), _ => (None, 0, 0), }; self.tex.set(tex); @@ -311,7 +315,7 @@ impl GuiElement for Label { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x, y); r.render_texture( - &tex, + &tex.texture, tx.round() as _, ty.round() as _, ARGB8888, diff --git a/src/renderer.rs b/src/renderer.rs index ffb54340..ff1ae61f 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -155,7 +155,7 @@ impl Renderer<'_> { if let Some(status) = &rd.status { let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y); self.base.render_texture( - &status.tex, + &status.tex.texture, x, y, ARGB8888, @@ -197,10 +197,10 @@ impl Renderer<'_> { &Color::from_rgba_straight(20, 20, 20, 255), ); if let Some(tex) = placeholder.textures.get(&self.base.scale) { - let x = x + (pos.width() - tex.width()) / 2; - let y = y + (pos.height() - tex.height()) / 2; + let x = x + (pos.width() - tex.texture.width()) / 2; + let y = y + (pos.height() - tex.texture.height()) / 2; self.base.render_texture( - &tex, + &tex.texture, x, y, ARGB8888, @@ -237,7 +237,7 @@ impl Renderer<'_> { for title in titles { let (x, y) = self.base.scale_point(x + title.x, y + title.y); self.base.render_texture( - &title.tex, + &title.tex.texture, x, y, ARGB8888, @@ -439,7 +439,7 @@ impl Renderer<'_> { if let Some(title) = floating.title_textures.get(&self.base.scale) { let (x, y) = self.base.scale_point(x + bw, y + bw); self.base.render_texture( - &title, + &title.texture, x, y, ARGB8888, diff --git a/src/state.rs b/src/state.rs index 3d164de2..f7932290 100644 --- a/src/state.rs +++ b/src/state.rs @@ -237,6 +237,7 @@ impl DrmDevData { struct UpdateTextTexturesVisitor; impl NodeVisitorBase for UpdateTextTexturesVisitor { fn visit_container(&mut self, node: &Rc) { + node.children.iter().for_each(|c| c.title_tex.clear()); node.schedule_compute_render_data(); node.node_visit_children(self); } @@ -245,10 +246,12 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { + node.title_textures.clear(); node.schedule_render_titles(); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { + node.textures.clear(); node.update_texture(); node.node_visit_children(self); } @@ -339,6 +342,11 @@ impl State { impl NodeVisitorBase for Walker { fn visit_container(&mut self, node: &Rc) { node.render_data.borrow_mut().titles.clear(); + node.children.iter().for_each(|c| c.title_tex.clear()); + node.node_visit_children(self); + } + fn visit_workspace(&mut self, node: &Rc) { + node.title_texture.set(None); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { diff --git a/src/text.rs b/src/text.rs index 9913641e..bc527a31 100644 --- a/src/text.rs +++ b/src/text.rs @@ -11,8 +11,13 @@ use { }, rect::Rect, theme::Color, + utils::clonecell::UnsafeCellCloneSafe, + }, + std::{ + borrow::Cow, + ops::{Deref, Neg}, + rc::Rc, }, - std::{ops::Neg, rc::Rc}, thiserror::Error, }; @@ -32,6 +37,47 @@ pub enum TextError { ImageData(#[source] PangoError), } +#[derive(PartialEq)] +struct Config<'a> { + x: i32, + y: Option, + width: i32, + height: i32, + padding: i32, + font: Cow<'a, str>, + text: Cow<'a, str>, + color: Color, + ellipsize: bool, + markup: bool, + scale: Option, +} + +impl<'a> Config<'a> { + fn to_static(self) -> Config<'static> { + Config { + x: self.x, + y: self.y, + width: self.width, + height: self.height, + padding: self.padding, + font: Cow::Owned(self.font.into_owned()), + text: Cow::Owned(self.text.into_owned()), + color: self.color, + ellipsize: self.ellipsize, + markup: self.markup, + scale: self.scale, + } + } +} + +#[derive(Clone)] +pub struct TextTexture { + config: Rc>, + pub texture: Rc, +} + +unsafe impl UnsafeCellCloneSafe for TextTexture {} + struct Data { image: Rc, cctx: Rc, @@ -95,20 +141,22 @@ pub fn measure( pub fn render( ctx: &Rc, + old: Option, width: i32, height: i32, font: &str, text: &str, color: Color, scale: Option, -) -> Result, TextError> { +) -> Result { render2( - ctx, 1, None, width, height, 1, font, text, color, true, false, scale, + ctx, old, 1, None, width, height, 1, font, text, color, true, false, scale, ) } fn render2( ctx: &Rc, + old: Option, x: i32, y: Option, width: i32, @@ -120,7 +168,25 @@ fn render2( ellipsize: bool, markup: bool, scale: Option, -) -> Result, TextError> { +) -> Result { + let config = Config { + x, + y, + width, + height, + padding, + font: Cow::Borrowed(font), + text: Cow::Borrowed(text), + color, + ellipsize, + markup, + scale, + }; + if let Some(old2) = &old { + if old2.config.deref() == &config { + return Ok(old.unwrap()); + } + } let data = create_data(font, width, height, scale)?; if ellipsize { data.layout @@ -148,21 +214,25 @@ fn render2( .clone() .shmem_texture(bytes, ARGB8888, width, height, data.image.stride()) { - Ok(t) => Ok(t), + Ok(t) => Ok(TextTexture { + config: Rc::new(config.to_static()), + texture: t, + }), Err(e) => Err(TextError::RenderError(e)), } } pub fn render_fitting( ctx: &Rc, + old: Option, height: Option, font: &str, text: &str, color: Color, markup: bool, scale: Option, -) -> Result, TextError> { - render_fitting2(ctx, height, font, text, color, markup, scale, false).map(|(a, _)| a) +) -> Result { + render_fitting2(ctx, old, height, font, text, color, markup, scale, false).map(|(a, _)| a) } #[derive(Debug, Copy, Clone, Default)] @@ -174,6 +244,7 @@ pub struct TextMeasurement { pub fn render_fitting2( ctx: &Rc, + old: Option, height: Option, font: &str, text: &str, @@ -181,7 +252,7 @@ pub fn render_fitting2( markup: bool, scale: Option, include_measurements: bool, -) -> Result<(Rc, TextMeasurement), TextError> { +) -> Result<(TextTexture, TextMeasurement), TextError> { let measurement = measure(font, text, markup, scale, include_measurements)?; let y = match height { Some(_) => None, @@ -189,6 +260,7 @@ pub fn render_fitting2( }; let res = render2( ctx, + old, measurement.ink_rect.x1().neg(), y, measurement.ink_rect.width(), diff --git a/src/tree/container.rs b/src/tree/container.rs index 41d517f2..aebfdd59 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -3,7 +3,6 @@ use { backend::KeyState, cursor::KnownCursor, fixed::Fixed, - gfx_api::GfxTexture, ifs::wl_seat::{ collect_kb_foci, collect_kb_foci2, wl_pointer::PendingScroll, NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, @@ -12,7 +11,7 @@ use { renderer::Renderer, scale::Scale, state::State, - text, + text::{self, TextTexture}, tree::{ walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FoundNode, Node, NodeId, ToplevelData, ToplevelNode, WorkspaceNode, @@ -24,7 +23,7 @@ use { numcell::NumCell, rc_eq::rc_eq, scroller::Scroller, - smallmap::SmallMapMut, + smallmap::{SmallMap, SmallMapMut}, }, }, ahash::AHashMap, @@ -77,7 +76,7 @@ tree_id!(ContainerNodeId); pub struct ContainerTitle { pub x: i32, pub y: i32, - pub tex: Rc, + pub tex: TextTexture, } #[derive(Default)] @@ -128,6 +127,7 @@ pub struct ContainerChild { pub node: Rc, pub active: Cell, title: RefCell, + pub title_tex: SmallMap, pub title_rect: Cell, focus_history: Cell>>>, @@ -182,6 +182,7 @@ impl ContainerNode { content: Default::default(), factor: Cell::new(1.0), title: Default::default(), + title_tex: Default::default(), title_rect: Default::default(), focus_history: Default::default(), }), @@ -289,6 +290,7 @@ impl ContainerNode { content: Default::default(), factor: Default::default(), title: Default::default(), + title_tex: Default::default(), title_rect: Default::default(), focus_history: Default::default(), }); @@ -678,6 +680,7 @@ impl ContainerNode { } let title = child.title.borrow_mut(); for (scale, _) in scales.iter() { + let old_tex = child.title_tex.remove(scale); let titles = rd.titles.get_or_default_mut(*scale); 'render_title: { let mut th = th; @@ -693,12 +696,24 @@ impl ContainerNode { break 'render_title; } if let Some(ctx) = &ctx { - match text::render(ctx, width, th, &font, title.deref(), color, scalef) { - Ok(t) => titles.push(ContainerTitle { - x: rect.x1(), - y: rect.y1(), - tex: t, - }), + match text::render( + ctx, + old_tex, + width, + th, + &font, + title.deref(), + color, + scalef, + ) { + Ok(t) => { + child.title_tex.insert(*scale, t.clone()); + titles.push(ContainerTitle { + x: rect.x1(), + y: rect.y1(), + tex: t, + }) + } Err(e) => { log::error!("Could not render title {}: {}", title, ErrorFmt(e)); } @@ -1268,6 +1283,7 @@ impl ContainingNode for ContainerNode { content: Default::default(), factor: Cell::new(node.factor.get()), title: Default::default(), + title_tex: Default::default(), title_rect: Cell::new(node.title_rect.get()), focus_history: Cell::new(None), }); diff --git a/src/tree/float.rs b/src/tree/float.rs index 1f0e5b60..268cfe83 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -3,13 +3,12 @@ use { backend::KeyState, cursor::KnownCursor, fixed::Fixed, - gfx_api::GfxTexture, ifs::wl_seat::{NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT}, rect::Rect, renderer::Renderer, scale::Scale, state::State, - text, + text::{self, TextTexture}, tree::{ walker::NodeVisitor, ContainingNode, FindTreeResult, FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode, @@ -44,7 +43,7 @@ pub struct FloatNode { pub layout_scheduled: Cell, pub render_titles_scheduled: Cell, pub title: RefCell, - pub title_textures: CopyHashMap>, + pub title_textures: CopyHashMap, seats: RefCell>, } @@ -179,7 +178,6 @@ impl FloatNode { let bw = theme.sizes.border_width.get(); let font = theme.font.borrow_mut(); let title = self.title.borrow_mut(); - self.title_textures.clear(); let pos = self.position.get(); if pos.width() <= 2 * bw || title.is_empty() { return; @@ -190,6 +188,7 @@ impl FloatNode { }; let scales = self.state.scales.lock(); for (scale, _) in scales.iter() { + let old_tex = self.title_textures.remove(scale); let mut th = th; let mut scalef = None; let mut width = pos.width() - 2 * bw; @@ -202,7 +201,7 @@ impl FloatNode { if th == 0 || width == 0 { continue; } - let texture = match text::render(&ctx, width, th, &font, &title, tc, scalef) { + let texture = match text::render(&ctx, old_tex, width, th, &font, &title, tc, scalef) { Ok(t) => t, Err(e) => { log::error!("Could not render title {}: {}", title, ErrorFmt(e)); diff --git a/src/tree/output.rs b/src/tree/output.rs index 933188c4..68b311f1 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -23,7 +23,7 @@ use { renderer::Renderer, scale::Scale, state::State, - text, + text::{self, TextTexture}, tree::{ walker::NodeVisitor, Direction, FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode, }, @@ -165,6 +165,7 @@ impl OutputNode { let output_width = self.global.pos.get().width(); rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap(); for ws in self.workspaces.iter() { + let old_tex = ws.title_texture.take(); let mut title_width = th; 'create_texture: { if let Some(ctx) = self.state.render_ctx.get() { @@ -177,6 +178,7 @@ impl OutputNode { }; let title = match text::render_fitting( &ctx, + old_tex, Some(texture_height), &font, &ws.name, @@ -190,8 +192,9 @@ impl OutputNode { break 'create_texture; } }; + ws.title_texture.set(Some(title.clone())); let mut x = pos + 1; - let mut width = title.width(); + let mut width = title.texture.width(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } @@ -205,7 +208,7 @@ impl OutputNode { x2: pos + title_width, tex_x: x, tex_y: 0, - tex: title, + tex: title.texture, ws: ws.deref().clone(), }); } @@ -224,6 +227,7 @@ impl OutputNode { pos += title_width; } 'set_status: { + let old_tex = rd.status.take().map(|s| s.tex); let ctx = match self.state.render_ctx.get() { Some(ctx) => ctx, _ => break 'set_status, @@ -235,6 +239,7 @@ impl OutputNode { let tc = self.state.theme.colors.bar_text.get(); let title = match text::render_fitting( &ctx, + old_tex, Some(texture_height), &font, &status, @@ -248,7 +253,7 @@ impl OutputNode { break 'set_status; } }; - let mut width = title.width(); + let mut width = title.texture.width(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } @@ -324,6 +329,7 @@ impl OutputNode { desired_output: CloneCell::new(self.global.output_id.clone()), jay_workspaces: Default::default(), capture: self.state.default_workspace_capture.clone(), + title_texture: Default::default(), }); ws.output_link .set(Some(self.workspaces.add_last(ws.clone()))); @@ -473,7 +479,7 @@ pub struct OutputTitle { pub struct OutputStatus { pub tex_x: i32, pub tex_y: i32, - pub tex: Rc, + pub tex: TextTexture, } #[derive(Copy, Clone)] diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index 72c3635b..e259d550 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -3,13 +3,12 @@ use { client::Client, cursor::KnownCursor, fixed::Fixed, - gfx_api::GfxTexture, ifs::wl_seat::{NodeSeatState, WlSeatGlobal}, rect::Rect, renderer::Renderer, scale::Scale, state::State, - text, + text::{self, TextTexture}, tree::{ Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelData, ToplevelNode, @@ -25,7 +24,7 @@ pub struct PlaceholderNode { id: PlaceholderNodeId, toplevel: ToplevelData, destroyed: Cell, - pub textures: SmallMap, 2>, + pub textures: SmallMap, } impl PlaceholderNode { @@ -47,11 +46,11 @@ impl PlaceholderNode { } pub fn update_texture(&self) { - self.textures.clear(); if let Some(ctx) = self.toplevel.state.render_ctx.get() { let scales = self.toplevel.state.scales.lock(); let rect = self.toplevel.pos.get(); for (scale, _) in scales.iter() { + let old_tex = self.textures.remove(scale); let mut width = rect.width(); let mut height = rect.height(); if *scale != 1 { @@ -63,6 +62,7 @@ impl PlaceholderNode { let font = format!("monospace {}", width / 10); match text::render_fitting( &ctx, + old_tex, Some(height), &font, "Fullscreen", diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 00c11353..6bb8de77 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -10,6 +10,7 @@ use { }, rect::Rect, renderer::Renderer, + text::TextTexture, tree::{ container::ContainerNode, walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitorBase, OutputNode, StackedNode, @@ -43,6 +44,7 @@ pub struct WorkspaceNode { pub desired_output: CloneCell>, pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc>, pub capture: Cell, + pub title_texture: Cell>, } impl WorkspaceNode {