mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Optimize UI text measurement (#15003)
# Objective - Avoid cloning the `CosmicBuffer` every time you create a new text measurement. ## Solution - Inject a buffer query when calculating layout so existing buffers can be reused. ## Testing - I tested the `text`, `text_debug`, and `text_wrap_debug` examples. - I did not do a performance test.
This commit is contained in:
parent
f0560b8e78
commit
41474226c3
5 changed files with 81 additions and 27 deletions
|
@ -1,7 +1,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use bevy_asset::{AssetId, Assets};
|
||||
use bevy_ecs::{component::Component, reflect::ReflectComponent, system::Resource};
|
||||
use bevy_ecs::{component::Component, entity::Entity, reflect::ReflectComponent, system::Resource};
|
||||
use bevy_math::{UVec2, Vec2};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::texture::Image;
|
||||
|
@ -238,8 +238,10 @@ impl TextPipeline {
|
|||
///
|
||||
/// Produces a [`TextMeasureInfo`] which can be used by a layout system
|
||||
/// to measure the text area on demand.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_text_measure(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
fonts: &Assets<Font>,
|
||||
sections: &[TextSection],
|
||||
scale_factor: f64,
|
||||
|
@ -270,9 +272,7 @@ impl TextPipeline {
|
|||
Ok(TextMeasureInfo {
|
||||
min: min_width_content_size,
|
||||
max: max_width_content_size,
|
||||
// TODO: This clone feels wasteful, is there another way to structure TextMeasureInfo
|
||||
// that it doesn't need to own a buffer? - bytemunch
|
||||
buffer: buffer.0.clone(),
|
||||
entity,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -299,23 +299,14 @@ pub struct TextLayoutInfo {
|
|||
/// Size information for a corresponding [`Text`](crate::Text) component.
|
||||
///
|
||||
/// Generated via [`TextPipeline::create_text_measure`].
|
||||
#[derive(Debug)]
|
||||
pub struct TextMeasureInfo {
|
||||
/// Minimum size for a text area in pixels, to be used when laying out widgets with taffy
|
||||
pub min: Vec2,
|
||||
/// Maximum size for a text area in pixels, to be used when laying out widgets with taffy
|
||||
pub max: Vec2,
|
||||
buffer: Buffer,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TextMeasureInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TextMeasureInfo")
|
||||
.field("min", &self.min)
|
||||
.field("max", &self.max)
|
||||
.field("buffer", &"_")
|
||||
.field("font_system", &"_")
|
||||
.finish()
|
||||
}
|
||||
/// The entity that is measured.
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
impl TextMeasureInfo {
|
||||
|
@ -323,11 +314,13 @@ impl TextMeasureInfo {
|
|||
pub fn compute_size(
|
||||
&mut self,
|
||||
bounds: TextBounds,
|
||||
buffer: &mut Buffer,
|
||||
font_system: &mut cosmic_text::FontSystem,
|
||||
) -> Vec2 {
|
||||
self.buffer
|
||||
.set_size(font_system, bounds.width, bounds.height);
|
||||
buffer_dimensions(&self.buffer)
|
||||
// Note that this arbitrarily adjusts the buffer layout. We assume the buffer is always 'refreshed'
|
||||
// whenever a canonical state is required.
|
||||
buffer.set_size(font_system, bounds.width, bounds.height);
|
||||
buffer_dimensions(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ use bevy_hierarchy::{Children, Parent};
|
|||
use bevy_math::{UVec2, Vec2};
|
||||
use bevy_render::camera::{Camera, NormalizedRenderTarget};
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_text::TextPipeline;
|
||||
use bevy_text::{CosmicBuffer, TextPipeline};
|
||||
use bevy_transform::components::Transform;
|
||||
use bevy_utils::tracing::warn;
|
||||
use bevy_utils::{HashMap, HashSet};
|
||||
|
@ -95,6 +95,7 @@ pub fn ui_layout_system(
|
|||
just_children_query: Query<&Children>,
|
||||
mut removed_components: UiLayoutSystemRemovedComponentParam,
|
||||
mut node_transform_query: Query<(&mut Node, &mut Transform)>,
|
||||
#[cfg(feature = "bevy_text")] mut buffer_query: Query<&mut CosmicBuffer>,
|
||||
#[cfg(feature = "bevy_text")] mut text_pipeline: ResMut<TextPipeline>,
|
||||
) {
|
||||
struct CameraLayoutInfo {
|
||||
|
@ -217,6 +218,8 @@ pub fn ui_layout_system(
|
|||
}
|
||||
});
|
||||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
let text_buffers = &mut buffer_query;
|
||||
#[cfg(feature = "bevy_text")]
|
||||
let font_system = text_pipeline.font_system_mut();
|
||||
// clean up removed nodes after syncing children to avoid potential panic (invalid SlotMap key used)
|
||||
|
@ -236,6 +239,8 @@ pub fn ui_layout_system(
|
|||
*camera_id,
|
||||
camera.size,
|
||||
#[cfg(feature = "bevy_text")]
|
||||
text_buffers,
|
||||
#[cfg(feature = "bevy_text")]
|
||||
font_system,
|
||||
);
|
||||
for root in &camera.root_nodes {
|
||||
|
|
|
@ -196,11 +196,14 @@ without UI components as a child of an entity with UI components, results may be
|
|||
}
|
||||
|
||||
/// Compute the layout for each window entity's corresponding root node in the layout.
|
||||
pub fn compute_camera_layout(
|
||||
pub fn compute_camera_layout<'a>(
|
||||
&mut self,
|
||||
camera: Entity,
|
||||
render_target_resolution: UVec2,
|
||||
#[cfg(feature = "bevy_text")] font_system: &mut bevy_text::cosmic_text::FontSystem,
|
||||
#[cfg(feature = "bevy_text")] buffer_query: &'a mut bevy_ecs::prelude::Query<
|
||||
&mut bevy_text::CosmicBuffer,
|
||||
>,
|
||||
#[cfg(feature = "bevy_text")] font_system: &'a mut bevy_text::cosmic_text::FontSystem,
|
||||
) {
|
||||
let Some(camera_root_nodes) = self.camera_roots.get(&camera) else {
|
||||
return;
|
||||
|
@ -223,6 +226,15 @@ without UI components as a child of an entity with UI components, results may be
|
|||
-> taffy::Size<f32> {
|
||||
context
|
||||
.map(|ctx| {
|
||||
#[cfg(feature = "bevy_text")]
|
||||
let buffer = get_text_buffer(
|
||||
crate::widget::TextMeasure::needs_buffer(
|
||||
known_dimensions.height,
|
||||
available_space.width,
|
||||
),
|
||||
ctx,
|
||||
buffer_query,
|
||||
);
|
||||
let size = ctx.measure(
|
||||
MeasureArgs {
|
||||
width: known_dimensions.width,
|
||||
|
@ -231,6 +243,8 @@ without UI components as a child of an entity with UI components, results may be
|
|||
available_height: available_space.height,
|
||||
#[cfg(feature = "bevy_text")]
|
||||
font_system,
|
||||
#[cfg(feature = "bevy_text")]
|
||||
buffer,
|
||||
#[cfg(not(feature = "bevy_text"))]
|
||||
font_system: std::marker::PhantomData,
|
||||
},
|
||||
|
@ -284,3 +298,22 @@ with UI components as a child of an entity without UI components, results may be
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
fn get_text_buffer<'a>(
|
||||
needs_buffer: bool,
|
||||
ctx: &mut NodeMeasure,
|
||||
query: &'a mut bevy_ecs::prelude::Query<&mut bevy_text::CosmicBuffer>,
|
||||
) -> Option<&'a mut bevy_text::cosmic_text::Buffer> {
|
||||
// We avoid a query lookup whenever the buffer is not required.
|
||||
if !needs_buffer {
|
||||
return None;
|
||||
}
|
||||
let NodeMeasure::Text(crate::widget::TextMeasure { info }) = ctx else {
|
||||
return None;
|
||||
};
|
||||
let Ok(buffer) = query.get_mut(info.entity) else {
|
||||
return None;
|
||||
};
|
||||
Some(buffer.into_inner())
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ pub struct MeasureArgs<'a> {
|
|||
pub available_height: AvailableSpace,
|
||||
#[cfg(feature = "bevy_text")]
|
||||
pub font_system: &'a mut bevy_text::cosmic_text::FontSystem,
|
||||
#[cfg(feature = "bevy_text")]
|
||||
pub buffer: Option<&'a mut bevy_text::cosmic_text::Buffer>,
|
||||
// When `bevy_text` is disabled, use `PhantomData` in order to keep lifetime in type signature.
|
||||
#[cfg(not(feature = "bevy_text"))]
|
||||
pub font_system: std::marker::PhantomData<&'a mut ()>,
|
||||
|
|
|
@ -19,7 +19,7 @@ use bevy_text::{
|
|||
scale_value, BreakLineOn, CosmicBuffer, Font, FontAtlasSets, JustifyText, Text, TextBounds,
|
||||
TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, YAxisOrientation,
|
||||
};
|
||||
use bevy_utils::Entry;
|
||||
use bevy_utils::{tracing::error, Entry};
|
||||
use taffy::style::AvailableSpace;
|
||||
|
||||
/// Text system flags
|
||||
|
@ -47,12 +47,20 @@ pub struct TextMeasure {
|
|||
pub info: TextMeasureInfo,
|
||||
}
|
||||
|
||||
impl TextMeasure {
|
||||
/// Checks if the cosmic text buffer is needed for measuring the text.
|
||||
pub fn needs_buffer(height: Option<f32>, available_width: AvailableSpace) -> bool {
|
||||
height.is_none() && matches!(available_width, AvailableSpace::Definite(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl Measure for TextMeasure {
|
||||
fn measure(&mut self, measure_args: MeasureArgs, _style: &taffy::Style) -> Vec2 {
|
||||
let MeasureArgs {
|
||||
width,
|
||||
height,
|
||||
available_width,
|
||||
buffer,
|
||||
font_system,
|
||||
..
|
||||
} = measure_args;
|
||||
|
@ -71,9 +79,18 @@ impl Measure for TextMeasure {
|
|||
height
|
||||
.map_or_else(
|
||||
|| match available_width {
|
||||
AvailableSpace::Definite(_) => self
|
||||
.info
|
||||
.compute_size(TextBounds::new_horizontal(x), font_system),
|
||||
AvailableSpace::Definite(_) => {
|
||||
if let Some(buffer) = buffer {
|
||||
self.info.compute_size(
|
||||
TextBounds::new_horizontal(x),
|
||||
buffer,
|
||||
font_system,
|
||||
)
|
||||
} else {
|
||||
error!("text measure failed, buffer is missing");
|
||||
Vec2::default()
|
||||
}
|
||||
}
|
||||
AvailableSpace::MinContent => Vec2::new(x, self.info.min.y),
|
||||
AvailableSpace::MaxContent => Vec2::new(x, self.info.max.y),
|
||||
},
|
||||
|
@ -86,6 +103,7 @@ impl Measure for TextMeasure {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
#[inline]
|
||||
fn create_text_measure(
|
||||
entity: Entity,
|
||||
fonts: &Assets<Font>,
|
||||
scale_factor: f64,
|
||||
text: Ref<Text>,
|
||||
|
@ -96,6 +114,7 @@ fn create_text_measure(
|
|||
text_alignment: JustifyText,
|
||||
) {
|
||||
match text_pipeline.create_text_measure(
|
||||
entity,
|
||||
fonts,
|
||||
&text.sections,
|
||||
scale_factor,
|
||||
|
@ -141,6 +160,7 @@ pub fn measure_text_system(
|
|||
ui_scale: Res<UiScale>,
|
||||
mut text_query: Query<
|
||||
(
|
||||
Entity,
|
||||
Ref<Text>,
|
||||
&mut ContentSize,
|
||||
&mut TextFlags,
|
||||
|
@ -153,7 +173,7 @@ pub fn measure_text_system(
|
|||
) {
|
||||
let mut scale_factors: EntityHashMap<f32> = EntityHashMap::default();
|
||||
|
||||
for (text, content_size, text_flags, camera, mut buffer) in &mut text_query {
|
||||
for (entity, text, content_size, text_flags, camera, mut buffer) in &mut text_query {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
|
@ -176,6 +196,7 @@ pub fn measure_text_system(
|
|||
{
|
||||
let text_alignment = text.justify;
|
||||
create_text_measure(
|
||||
entity,
|
||||
&fonts,
|
||||
scale_factor.into(),
|
||||
text,
|
||||
|
|
Loading…
Reference in a new issue