Move to a single global `parley::LayoutContext` (#405)

Based on a suggestion from @dfrg.

This appears to have improved performance with the 30_000 hello's
example from ~$\frac{1}{20}$ms to $\frac{1}{160}$ms
So an approx 8x increase in throughput for that scene.
This commit is contained in:
Daniel McNab 2024-06-18 20:04:17 +01:00 committed by GitHub
parent dd196c7134
commit 9299b67986
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 77 additions and 47 deletions

View File

@ -102,7 +102,7 @@ impl Widget for CustomWidget {
// To render text, we first create a LayoutBuilder and set the text properties.
let mut lcx = parley::LayoutContext::new();
let mut text_layout_builder = lcx.ranged_builder(ctx.font_ctx(), &self.0, 1.0);
let mut text_layout_builder = lcx.ranged_builder(ctx.text_contexts().0, &self.0, 1.0);
text_layout_builder.push_default(&StyleProperty::FontStack(FontStack::Single(
FontFamily::Generic(parley::style::GenericFamily::Serif),

View File

@ -7,13 +7,14 @@ use std::any::Any;
use std::time::Duration;
use accesskit::{NodeBuilder, TreeUpdate};
use parley::FontContext;
use parley::{FontContext, LayoutContext};
use tracing::{trace, warn};
use crate::action::Action;
use crate::dpi::LogicalPosition;
use crate::promise::PromiseToken;
use crate::render_root::{RenderRootSignal, RenderRootState};
use crate::text2::TextBrush;
use crate::text_helpers::{ImeChangeSignal, TextFieldRegistration};
use crate::widget::{CursorChange, WidgetMut, WidgetState};
use crate::{CursorIcon, Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod};
@ -662,8 +663,12 @@ impl LayoutCtx<'_> {
}
impl_context_method!(LayoutCtx<'_>, PaintCtx<'_>, {
pub fn font_ctx(&mut self) -> &mut FontContext {
&mut self.global_state.font_context
/// Get the contexts needed to build and paint text sections.
pub fn text_contexts(&mut self) -> (&mut FontContext, &mut LayoutContext<TextBrush>) {
(
&mut self.global_state.font_context,
&mut self.global_state.text_layout_context,
)
}
});

View File

@ -5,7 +5,7 @@ use std::collections::VecDeque;
use accesskit::{ActionRequest, NodeBuilder, Tree, TreeUpdate};
use kurbo::Affine;
use parley::FontContext;
use parley::{FontContext, LayoutContext};
use tracing::{debug, info_span, warn};
use vello::peniko::{Color, Fill};
use vello::Scene;
@ -21,6 +21,7 @@ use crate::debug_logger::DebugLogger;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
use crate::event::{PointerEvent, TextEvent, WindowEvent};
use crate::kurbo::Point;
use crate::text2::TextBrush;
use crate::widget::{WidgetMut, WidgetState};
use crate::{
AccessCtx, AccessEvent, Action, BoxConstraints, CursorIcon, Handled, InternalLifeCycle,
@ -51,6 +52,7 @@ pub(crate) struct RenderRootState {
pub(crate) focused_widget: Option<WidgetId>,
pub(crate) next_focused_widget: Option<WidgetId>,
pub(crate) font_context: FontContext,
pub(crate) text_layout_context: LayoutContext<TextBrush>,
}
/// Defines how a windows size should be determined
@ -99,6 +101,7 @@ impl RenderRoot {
focused_widget: None,
next_focused_widget: None,
font_context: FontContext::default(),
text_layout_context: LayoutContext::new(),
},
rebuild_access_tree: true,
};

View File

@ -4,7 +4,7 @@
use std::ops::{Deref, DerefMut, Range};
use kurbo::Point;
use parley::FontContext;
use parley::{FontContext, LayoutContext};
use vello::Scene;
use winit::{
event::Ime,
@ -19,7 +19,7 @@ use crate::{
use super::{
offset_for_delete_backwards,
selection::{Affinity, Selection},
Selectable, TextWithSelection,
Selectable, TextBrush, TextWithSelection,
};
/// Text which can be edited
@ -73,18 +73,24 @@ impl<T: EditableText> TextEditor<T> {
self.preedit_range = None;
}
pub fn rebuild(&mut self, fcx: &mut FontContext) {
// TODO: Add the pre-edit range as an underlined region in the text attributes
self.inner.rebuild_with_attributes(fcx, |mut builder| {
if let Some(range) = self.preedit_range.as_ref() {
builder.push(
&parley::style::StyleProperty::Underline(true),
range.clone(),
);
}
builder
});
/// Rebuild the text.
///
/// See also [TextLayout::rebuild](crate::text2::TextLayout::rebuild) for more comprehensive docs.
pub fn rebuild(
&mut self,
font_ctx: &mut FontContext,
layout_ctx: &mut LayoutContext<TextBrush>,
) {
self.inner
.rebuild_with_attributes(font_ctx, layout_ctx, |mut builder| {
if let Some(range) = self.preedit_range.as_ref() {
builder.push(
&parley::style::StyleProperty::Underline(true),
range.clone(),
);
}
builder
});
}
pub fn draw(&mut self, scene: &mut Scene, point: impl Into<Point>) {

View File

@ -56,7 +56,6 @@ pub struct TextLayout<T> {
needs_layout: bool,
needs_line_breaks: bool,
layout: Layout<TextBrush>,
layout_context: LayoutContext<TextBrush>,
scratch_scene: Scene,
}
@ -131,7 +130,6 @@ impl<T> TextLayout<T> {
needs_layout: true,
needs_line_breaks: true,
layout: Layout::new(),
layout_context: LayoutContext::new(),
scratch_scene: Scene::new(),
}
}
@ -422,8 +420,12 @@ impl<T: TextStorage> TextLayout<T> {
/// This method should be called whenever any of these things may have changed.
/// A simple way to ensure this is correct is to always call this method
/// as part of your widget's [`layout`][crate::Widget::layout] method.
pub fn rebuild(&mut self, fcx: &mut FontContext) {
self.rebuild_with_attributes(fcx, |builder| builder);
pub fn rebuild(
&mut self,
font_ctx: &mut FontContext,
layout_ctx: &mut LayoutContext<TextBrush>,
) {
self.rebuild_with_attributes(font_ctx, layout_ctx, |builder| builder);
}
/// Rebuild the inner layout as needed, adding attributes to the underlying layout.
@ -431,7 +433,8 @@ impl<T: TextStorage> TextLayout<T> {
/// See [`Self::rebuild`] for more information
pub fn rebuild_with_attributes(
&mut self,
fcx: &mut FontContext,
font_ctx: &mut FontContext,
layout_ctx: &mut LayoutContext<TextBrush>,
attributes: impl for<'b> FnOnce(
RangedBuilder<'b, TextBrush, &'b str>,
) -> RangedBuilder<'b, TextBrush, &'b str>,
@ -439,9 +442,7 @@ impl<T: TextStorage> TextLayout<T> {
if self.needs_layout {
self.needs_layout = false;
let mut builder =
self.layout_context
.ranged_builder(fcx, self.text.as_str(), self.scale);
let mut builder = layout_ctx.ranged_builder(font_ctx, self.text.as_str(), self.scale);
builder.push_default(&StyleProperty::Brush(self.brush.clone()));
builder.push_default(&StyleProperty::FontSize(self.text_size));
builder.push_default(&StyleProperty::FontStack(self.font));

View File

@ -8,7 +8,7 @@ use std::ops::{Deref, DerefMut, Range};
use kurbo::{Affine, Line, Point, Stroke};
use parley::context::RangedBuilder;
use parley::FontContext;
use parley::{FontContext, LayoutContext};
use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
use vello::peniko::{Brush, Color};
use vello::Scene;
@ -209,10 +209,25 @@ impl<T: Selectable> TextWithSelection<T> {
self.needs_selection_update = true;
}
/// Rebuild the text layout.
///
/// See also [TextLayout::rebuild] for more comprehensive docs.
pub fn rebuild(
&mut self,
font_ctx: &mut FontContext,
layout_ctx: &mut LayoutContext<TextBrush>,
) {
self.rebuild_with_attributes(font_ctx, layout_ctx, |builder| builder);
}
// Intentionally aliases the method on `TextLayout`
/// Rebuild the text layout, adding attributes to the builder.
///
/// See also [TextLayout::rebuild_with_attributes] for more comprehensive docs.
pub fn rebuild_with_attributes(
&mut self,
fcx: &mut FontContext,
font_ctx: &mut FontContext,
layout_ctx: &mut LayoutContext<TextBrush>,
attributes: impl for<'b> FnOnce(
RangedBuilder<'b, TextBrush, &'b str>,
) -> RangedBuilder<'b, TextBrush, &'b str>,
@ -221,26 +236,23 @@ impl<T: Selectable> TextWithSelection<T> {
// selected range was previously or currently non-zero size (i.e. there is a selected range)
if self.needs_selection_update || self.layout.needs_rebuild() {
self.layout.invalidate();
self.layout.rebuild_with_attributes(fcx, |mut builder| {
if let Some(selection) = self.selection {
let range = selection.range();
if !range.is_empty() {
builder.push(
&parley::style::StyleProperty::Brush(self.highlight_brush.clone()),
range,
);
self.layout
.rebuild_with_attributes(font_ctx, layout_ctx, |mut builder| {
if let Some(selection) = self.selection {
let range = selection.range();
if !range.is_empty() {
builder.push(
&parley::style::StyleProperty::Brush(self.highlight_brush.clone()),
range,
);
}
}
}
attributes(builder)
});
attributes(builder)
});
self.needs_selection_update = false;
}
}
pub fn rebuild(&mut self, fcx: &mut FontContext) {
self.rebuild_with_attributes(fcx, |builder| builder);
}
pub fn draw(&mut self, scene: &mut Scene, point: impl Into<Point>) {
// TODO: Calculate the location for this in layout lazily?
if let Some(selection) = self.selection {

View File

@ -214,7 +214,8 @@ impl Widget for Label {
};
self.text_layout.set_max_advance(max_advance);
if self.text_layout.needs_rebuild() {
self.text_layout.rebuild(ctx.font_ctx());
let (font_ctx, layout_ctx) = ctx.text_contexts();
self.text_layout.rebuild(font_ctx, layout_ctx);
}
// We ignore trailing whitespace for a label
let text_size = self.text_layout.size();

View File

@ -248,7 +248,8 @@ impl Widget for Prose {
};
self.text_layout.set_max_advance(max_advance);
if self.text_layout.needs_rebuild() {
self.text_layout.rebuild(ctx.font_ctx());
let (font_ctx, layout_ctx) = ctx.text_contexts();
self.text_layout.rebuild(font_ctx, layout_ctx);
}
// We ignore trailing whitespace for a label
let text_size = self.text_layout.size();

View File

@ -273,7 +273,8 @@ impl Widget for Textbox {
};
self.editor.set_max_advance(max_advance);
if self.editor.needs_rebuild() {
self.editor.rebuild(ctx.font_ctx());
let (font_ctx, layout_ctx) = ctx.text_contexts();
self.editor.rebuild(font_ctx, layout_ctx);
}
let text_size = self.editor.size();
let width = if bc.max().width.is_finite() {