Skip to content

Commit afc721e

Browse files
authored
Extract text colors per glyph (#20245)
# Objective Colors are extracted per text section, transforms per glyph. This makes it awkward to set colors for individual glyphs as they have to be extracted separately. ## Solution * Add `color: LinearRgba` and `translation: Vec2` fields to `ExtractedGlyph`. * Move the `transform` fields from `ExtractedGlyph` and `ExtractedUiNode` to `ExtractedUiItem`. * Move the `rect` field from `ExtractedUiNode` to `ExtractedUiItem`. `ExtractedUiNode`s are smaller by 16 bytes, from 208 to 192 bytes in size. `ExtractedGlyph`s are smaller by 8 bytes, from 48 to 40 bytes in size. Also adds another micro-optimisation, `TextColor` has been added to the extracted `uinode_query`. This means that there is no need to do a `TextColor` look up for the main `Text` entity, only for the child text sections. ## Testing Output from the UI examples should be unchanged, main ones to check are `testbed_ui`, `testbed_full_ui`, `text_debug`, `text_wrap_debug` and `ui_transform`. Not a performance motivated change but this should show a marginal improvement over main, YMMV: ``` cargo run --example many_glyphs --release -- --no-text2d ```
1 parent 1a8f833 commit afc721e

File tree

4 files changed

+82
-63
lines changed

4 files changed

+82
-63
lines changed

crates/bevy_ui_render/src/debug_overlay.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,19 @@ pub fn extract_debug_overlay(
9191
render_entity: commands.spawn(TemporaryRenderEntity).id(),
9292
// Add a large number to the UI node's stack index so that the overlay is always drawn on top
9393
z_order: (ui_stack.uinodes.len() as u32 + uinode.stack_index()) as f32,
94-
color: Hsla::sequential_dispersed(entity.index()).into(),
95-
rect: Rect {
96-
min: Vec2::ZERO,
97-
max: uinode.size,
98-
},
9994
clip: maybe_clip
10095
.filter(|_| !debug_options.show_clipped)
10196
.map(|clip| clip.clip),
10297
image: AssetId::default(),
10398
extracted_camera_entity,
99+
transform: transform.into(),
104100
item: ExtractedUiItem::Node {
101+
color: Hsla::sequential_dispersed(entity.index()).into(),
102+
rect: Rect {
103+
min: Vec2::ZERO,
104+
max: uinode.size,
105+
},
105106
atlas_scaling: None,
106-
transform: transform.into(),
107107
flip_x: false,
108108
flip_y: false,
109109
border: BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor()),

crates/bevy_ui_render/src/gradient.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -404,22 +404,22 @@ pub fn extract_gradients(
404404
NodeType::Rect => stack_z_offsets::GRADIENT,
405405
NodeType::Border(_) => stack_z_offsets::BORDER_GRADIENT,
406406
},
407-
color: color.into(),
408-
rect: Rect {
409-
min: Vec2::ZERO,
410-
max: uinode.size,
411-
},
412407
image: AssetId::default(),
413408
clip: clip.map(|clip| clip.clip),
414409
extracted_camera_entity,
410+
transform: transform.into(),
415411
item: ExtractedUiItem::Node {
412+
color: color.into(),
413+
rect: Rect {
414+
min: Vec2::ZERO,
415+
max: uinode.size,
416+
},
416417
atlas_scaling: None,
417418
flip_x: false,
418419
flip_y: false,
419420
border_radius: uinode.border_radius,
420421
border: uinode.border,
421422
node_type,
422-
transform: transform.into(),
423423
},
424424
main_entity: entity.into(),
425425
render_entity: commands.spawn(TemporaryRenderEntity).id(),

crates/bevy_ui_render/src/lib.rs

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -345,15 +345,14 @@ impl<'w, 's> UiCameraMapper<'w, 's> {
345345

346346
pub struct ExtractedUiNode {
347347
pub z_order: f32,
348-
pub color: LinearRgba,
349-
pub rect: Rect,
350348
pub image: AssetId<Image>,
351349
pub clip: Option<Rect>,
352350
/// Render world entity of the extracted camera corresponding to this node's target camera.
353351
pub extracted_camera_entity: Entity,
354352
pub item: ExtractedUiItem,
355353
pub main_entity: MainEntity,
356354
pub render_entity: Entity,
355+
pub transform: Affine2,
357356
}
358357

359358
/// The type of UI node.
@@ -366,6 +365,8 @@ pub enum NodeType {
366365

367366
pub enum ExtractedUiItem {
368367
Node {
368+
color: LinearRgba,
369+
rect: Rect,
369370
atlas_scaling: Option<Vec2>,
370371
flip_x: bool,
371372
flip_y: bool,
@@ -376,7 +377,6 @@ pub enum ExtractedUiItem {
376377
/// Ordering: left, top, right, bottom.
377378
border: BorderRect,
378379
node_type: NodeType,
379-
transform: Affine2,
380380
},
381381
/// A contiguous sequence of text glyphs from the same section
382382
Glyphs {
@@ -386,7 +386,8 @@ pub enum ExtractedUiItem {
386386
}
387387

388388
pub struct ExtractedGlyph {
389-
pub transform: Affine2,
389+
pub color: LinearRgba,
390+
pub translation: Vec2,
390391
pub rect: Rect,
391392
}
392393

@@ -464,17 +465,17 @@ pub fn extract_uinode_background_colors(
464465
extracted_uinodes.uinodes.push(ExtractedUiNode {
465466
render_entity: commands.spawn(TemporaryRenderEntity).id(),
466467
z_order: uinode.stack_index as f32 + stack_z_offsets::BACKGROUND_COLOR,
467-
color: background_color.0.into(),
468-
rect: Rect {
469-
min: Vec2::ZERO,
470-
max: uinode.size,
471-
},
472468
clip: clip.map(|clip| clip.clip),
473469
image: AssetId::default(),
474470
extracted_camera_entity,
471+
transform: transform.into(),
475472
item: ExtractedUiItem::Node {
473+
color: background_color.0.into(),
474+
rect: Rect {
475+
min: Vec2::ZERO,
476+
max: uinode.size,
477+
},
476478
atlas_scaling: None,
477-
transform: transform.into(),
478479
flip_x: false,
479480
flip_y: false,
480481
border: uinode.border(),
@@ -551,14 +552,14 @@ pub fn extract_uinode_images(
551552
extracted_uinodes.uinodes.push(ExtractedUiNode {
552553
z_order: uinode.stack_index as f32 + stack_z_offsets::IMAGE,
553554
render_entity: commands.spawn(TemporaryRenderEntity).id(),
554-
color: image.color.into(),
555-
rect,
556555
clip: clip.map(|clip| clip.clip),
557556
image: image.image.id(),
558557
extracted_camera_entity,
558+
transform: transform.into(),
559559
item: ExtractedUiItem::Node {
560+
color: image.color.into(),
561+
rect,
560562
atlas_scaling,
561-
transform: transform.into(),
562563
flip_x: image.flip_x,
563564
flip_y: image.flip_y,
564565
border: uinode.border,
@@ -649,17 +650,17 @@ pub fn extract_uinode_borders(
649650

650651
extracted_uinodes.uinodes.push(ExtractedUiNode {
651652
z_order: computed_node.stack_index as f32 + stack_z_offsets::BORDER,
652-
color,
653-
rect: Rect {
654-
max: computed_node.size(),
655-
..Default::default()
656-
},
657653
image,
658654
clip: maybe_clip.map(|clip| clip.clip),
659655
extracted_camera_entity,
656+
transform: transform.into(),
660657
item: ExtractedUiItem::Node {
658+
color,
659+
rect: Rect {
660+
max: computed_node.size(),
661+
..Default::default()
662+
},
661663
atlas_scaling: None,
662-
transform: transform.into(),
663664
flip_x: false,
664665
flip_y: false,
665666
border: computed_node.border(),
@@ -682,16 +683,16 @@ pub fn extract_uinode_borders(
682683
extracted_uinodes.uinodes.push(ExtractedUiNode {
683684
z_order: computed_node.stack_index as f32 + stack_z_offsets::BORDER,
684685
render_entity: commands.spawn(TemporaryRenderEntity).id(),
685-
color: outline.color.into(),
686-
rect: Rect {
687-
max: outline_size,
688-
..Default::default()
689-
},
690686
image,
691687
clip: maybe_clip.map(|clip| clip.clip),
692688
extracted_camera_entity,
689+
transform: transform.into(),
693690
item: ExtractedUiItem::Node {
694-
transform: transform.into(),
691+
color: outline.color.into(),
692+
rect: Rect {
693+
max: outline_size,
694+
..Default::default()
695+
},
695696
atlas_scaling: None,
696697
flip_x: false,
697698
flip_y: false,
@@ -873,17 +874,17 @@ pub fn extract_viewport_nodes(
873874
extracted_uinodes.uinodes.push(ExtractedUiNode {
874875
z_order: uinode.stack_index as f32 + stack_z_offsets::IMAGE,
875876
render_entity: commands.spawn(TemporaryRenderEntity).id(),
876-
color: LinearRgba::WHITE,
877-
rect: Rect {
878-
min: Vec2::ZERO,
879-
max: uinode.size,
880-
},
881877
clip: clip.map(|clip| clip.clip),
882878
image: image.id(),
883879
extracted_camera_entity,
880+
transform: transform.into(),
884881
item: ExtractedUiItem::Node {
882+
color: LinearRgba::WHITE,
883+
rect: Rect {
884+
min: Vec2::ZERO,
885+
max: uinode.size,
886+
},
885887
atlas_scaling: None,
886-
transform: transform.into(),
887888
flip_x: false,
888889
flip_y: false,
889890
border: uinode.border(),
@@ -908,6 +909,7 @@ pub fn extract_text_sections(
908909
Option<&CalculatedClip>,
909910
&ComputedUiTargetCamera,
910911
&ComputedTextBlock,
912+
&TextColor,
911913
&TextLayoutInfo,
912914
)>,
913915
>,
@@ -926,6 +928,7 @@ pub fn extract_text_sections(
926928
clip,
927929
camera,
928930
computed_block,
931+
text_color,
929932
text_layout_info,
930933
) in &uinode_query
931934
{
@@ -940,6 +943,8 @@ pub fn extract_text_sections(
940943

941944
let transform = Affine2::from(*transform) * Affine2::from_translation(-0.5 * uinode.size());
942945

946+
let mut color = text_color.0.to_linear();
947+
943948
for (
944949
i,
945950
PositionedGlyph {
@@ -956,14 +961,15 @@ pub fn extract_text_sections(
956961
.textures[atlas_info.location.glyph_index]
957962
.as_rect();
958963
extracted_uinodes.glyphs.push(ExtractedGlyph {
959-
transform: transform * Affine2::from_translation(*position),
964+
color,
965+
translation: *position,
960966
rect,
961967
});
962968

963969
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| {
964970
info.span_index != *span_index || info.atlas_info.texture != atlas_info.texture
965971
}) {
966-
let color = text_styles
972+
color = text_styles
967973
.get(
968974
computed_block
969975
.entities()
@@ -976,13 +982,12 @@ pub fn extract_text_sections(
976982
extracted_uinodes.uinodes.push(ExtractedUiNode {
977983
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
978984
render_entity: commands.spawn(TemporaryRenderEntity).id(),
979-
color,
980985
image: atlas_info.texture,
981986
clip: clip.map(|clip| clip.clip),
982987
extracted_camera_entity,
983-
rect,
984988
item: ExtractedUiItem::Glyphs { range: start..end },
985989
main_entity: entity.into(),
990+
transform,
986991
});
987992
start = end;
988993
}
@@ -1047,21 +1052,21 @@ pub fn extract_text_shadows(
10471052
.textures[atlas_info.location.glyph_index]
10481053
.as_rect();
10491054
extracted_uinodes.glyphs.push(ExtractedGlyph {
1050-
transform: node_transform * Affine2::from_translation(*position),
1055+
color: shadow.color.into(),
1056+
translation: *position,
10511057
rect,
10521058
});
10531059

10541060
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| {
10551061
info.span_index != *span_index || info.atlas_info.texture != atlas_info.texture
10561062
}) {
10571063
extracted_uinodes.uinodes.push(ExtractedUiNode {
1064+
transform: node_transform,
10581065
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
10591066
render_entity: commands.spawn(TemporaryRenderEntity).id(),
1060-
color: shadow.color.into(),
10611067
image: atlas_info.texture,
10621068
clip: clip.map(|clip| clip.clip),
10631069
extracted_camera_entity,
1064-
rect,
10651070
item: ExtractedUiItem::Glyphs { range: start..end },
10661071
main_entity: entity.into(),
10671072
});
@@ -1114,17 +1119,17 @@ pub fn extract_text_background_colors(
11141119
extracted_uinodes.uinodes.push(ExtractedUiNode {
11151120
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
11161121
render_entity: commands.spawn(TemporaryRenderEntity).id(),
1117-
color: text_background_color.0.to_linear(),
1118-
rect: Rect {
1119-
min: Vec2::ZERO,
1120-
max: rect.size(),
1121-
},
11221122
clip: clip.map(|clip| clip.clip),
11231123
image: AssetId::default(),
11241124
extracted_camera_entity,
1125+
transform: transform * Affine2::from_translation(rect.center()),
11251126
item: ExtractedUiItem::Node {
1127+
color: text_background_color.0.to_linear(),
1128+
rect: Rect {
1129+
min: Vec2::ZERO,
1130+
max: rect.size(),
1131+
},
11261132
atlas_scaling: None,
1127-
transform: transform * Affine2::from_translation(rect.center()),
11281133
flip_x: false,
11291134
flip_y: false,
11301135
border: uinode.border(),
@@ -1397,18 +1402,21 @@ pub fn prepare_uinodes(
13971402
border_radius,
13981403
border,
13991404
node_type,
1400-
transform,
1405+
rect,
1406+
color,
14011407
} => {
14021408
let mut flags = if extracted_uinode.image != AssetId::default() {
14031409
shader_flags::TEXTURED
14041410
} else {
14051411
shader_flags::UNTEXTURED
14061412
};
14071413

1408-
let mut uinode_rect = extracted_uinode.rect;
1414+
let mut uinode_rect = *rect;
14091415

14101416
let rect_size = uinode_rect.size();
14111417

1418+
let transform = extracted_uinode.transform;
1419+
14121420
// Specify the corners of the node
14131421
let positions = QUAD_VERTEX_POSITIONS
14141422
.map(|pos| transform.transform_point2(pos * rect_size).extend(0.));
@@ -1516,7 +1524,7 @@ pub fn prepare_uinodes(
15161524
.map(|pos| pos / atlas_extent)
15171525
};
15181526

1519-
let color = extracted_uinode.color.to_f32_array();
1527+
let color = color.to_f32_array();
15201528
if let NodeType::Border(border_flags) = *node_type {
15211529
flags |= border_flags;
15221530
}
@@ -1548,16 +1556,18 @@ pub fn prepare_uinodes(
15481556

15491557
let atlas_extent = image.size_2d().as_vec2();
15501558

1551-
let color = extracted_uinode.color.to_f32_array();
15521559
for glyph in &extracted_uinodes.glyphs[range.clone()] {
1560+
let color = glyph.color.to_f32_array();
15531561
let glyph_rect = glyph.rect;
15541562
let rect_size = glyph_rect.size();
15551563

15561564
// Specify the corners of the glyph
15571565
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
1558-
glyph
1566+
extracted_uinode
15591567
.transform
1560-
.transform_point2(pos * glyph_rect.size())
1568+
.transform_point2(
1569+
glyph.translation + pos * glyph_rect.size(),
1570+
)
15611571
.extend(0.)
15621572
});
15631573

@@ -1593,7 +1603,7 @@ pub fn prepare_uinodes(
15931603

15941604
// cull nodes that are completely clipped
15951605
let transformed_rect_size =
1596-
glyph.transform.transform_vector2(rect_size);
1606+
extracted_uinode.transform.transform_vector2(rect_size);
15971607
if positions_diff[0].x - positions_diff[1].x
15981608
>= transformed_rect_size.x.abs()
15991609
|| positions_diff[1].y - positions_diff[2].y
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Extract UI text colors per glyph
3+
pull_requests: [20245]
4+
---
5+
6+
The UI renderer now extracts text colors per glyph and transforms per text section.
7+
`color: LinearRgba` and `translation: Vec2` have been added to `ExtractedGlyph`.
8+
The `transform` field has moved from `ExtractedGlyph` and `ExtractedUiNode` to `ExtractedUiItem`.
9+
The `rect` field has moved from `ExtractedUiNode` to `ExtractedUiItem`.

0 commit comments

Comments
 (0)