make UI text rendering camera driven (#13697)

# Objective

- Fixes #13687 

## Solution

- Text rendering in UI is still dependent on the `PrimaryWIndow`
- implements #10559 for text rendering

There are other parts of UI that are still `PrimaryWindow` dependent, if
the changes here are OK I'll apply them everywhere.
I'm not a fan of the `EntityHashMap` here to hold the scale factors, but
it seems the quick and easy fix

## Testing

- Run example `multiple_windows` on a screen with a scale factor
different than 1, close the primary window
This commit is contained in:
François Mockers 2024-06-06 02:20:50 +02:00 committed by GitHub
parent 52215ce072
commit 3d9b1e4025
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,6 +1,9 @@
use crate::{ContentSize, FixedMeasure, Measure, Node, NodeMeasure, UiScale}; use crate::{
ContentSize, DefaultUiCamera, FixedMeasure, Measure, Node, NodeMeasure, TargetCamera, UiScale,
};
use bevy_asset::Assets; use bevy_asset::Assets;
use bevy_ecs::{ use bevy_ecs::{
entity::{Entity, EntityHashMap},
prelude::{Component, DetectChanges}, prelude::{Component, DetectChanges},
query::With, query::With,
reflect::ReflectComponent, reflect::ReflectComponent,
@ -9,13 +12,13 @@ use bevy_ecs::{
}; };
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::texture::Image; use bevy_render::{camera::Camera, texture::Image};
use bevy_sprite::TextureAtlasLayout; use bevy_sprite::TextureAtlasLayout;
use bevy_text::{ use bevy_text::{
scale_value, BreakLineOn, Font, FontAtlasSets, Text, TextError, TextLayoutInfo, scale_value, BreakLineOn, Font, FontAtlasSets, Text, TextError, TextLayoutInfo,
TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
}; };
use bevy_window::{PrimaryWindow, Window}; use bevy_utils::Entry;
use taffy::style::AvailableSpace; use taffy::style::AvailableSpace;
/// Text system flags /// Text system flags
@ -112,41 +115,54 @@ fn create_text_measure(
/// A `Measure` is used by the UI's layout algorithm to determine the appropriate amount of space /// A `Measure` is used by the UI's layout algorithm to determine the appropriate amount of space
/// to provide for the text given the fonts, the text itself and the constraints of the layout. /// to provide for the text given the fonts, the text itself and the constraints of the layout.
/// ///
/// * All measures are regenerated if the primary window's scale factor or [`UiScale`] is changed. /// * Measures are regenerated if the target camera's scale factor (or primary window if no specific target) or [`UiScale`] is changed.
/// * Changes that only modify the colors of a `Text` do not require a new `Measure`. This system /// * Changes that only modify the colors of a `Text` do not require a new `Measure`. This system
/// is only able to detect that a `Text` component has changed and will regenerate the `Measure` on /// is only able to detect that a `Text` component has changed and will regenerate the `Measure` on
/// color changes. This can be expensive, particularly for large blocks of text, and the [`bypass_change_detection`](bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection) /// color changes. This can be expensive, particularly for large blocks of text, and the [`bypass_change_detection`](bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection)
/// method should be called when only changing the `Text`'s colors. /// method should be called when only changing the `Text`'s colors.
pub fn measure_text_system( pub fn measure_text_system(
mut last_scale_factor: Local<f32>, mut last_scale_factors: Local<EntityHashMap<f32>>,
fonts: Res<Assets<Font>>, fonts: Res<Assets<Font>>,
windows: Query<&Window, With<PrimaryWindow>>, camera_query: Query<(Entity, &Camera)>,
default_ui_camera: DefaultUiCamera,
ui_scale: Res<UiScale>, ui_scale: Res<UiScale>,
mut text_query: Query<(Ref<Text>, &mut ContentSize, &mut TextFlags), With<Node>>, mut text_query: Query<
(
Ref<Text>,
&mut ContentSize,
&mut TextFlags,
Option<&TargetCamera>,
),
With<Node>,
>,
) { ) {
let window_scale_factor = windows let mut scale_factors: EntityHashMap<f32> = EntityHashMap::default();
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.);
let scale_factor = ui_scale.0 * window_scale_factor; for (text, content_size, text_flags, camera) in &mut text_query {
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
#[allow(clippy::float_cmp)] else {
if *last_scale_factor == scale_factor { continue;
// scale factor unchanged, only create new measure funcs for modified text };
for (text, content_size, text_flags) in &mut text_query { let scale_factor = match scale_factors.entry(camera_entity) {
if text.is_changed() || text_flags.needs_new_measure_func || content_size.is_added() { Entry::Occupied(entry) => *entry.get(),
create_text_measure(&fonts, scale_factor, text, content_size, text_flags); Entry::Vacant(entry) => *entry.insert(
} camera_query
} .get(camera_entity)
} else { .ok()
// scale factor changed, create new measure funcs for all text .and_then(|(_, c)| c.target_scaling_factor())
*last_scale_factor = scale_factor; .unwrap_or(1.0)
* ui_scale.0,
for (text, content_size, text_flags) in &mut text_query { ),
};
if last_scale_factors.get(&camera_entity) != Some(&scale_factor)
|| text.is_changed()
|| text_flags.needs_new_measure_func
|| content_size.is_added()
{
create_text_measure(&fonts, scale_factor, text, content_size, text_flags); create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
} }
} }
*last_scale_factors = scale_factors;
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -219,49 +235,47 @@ fn queue_text(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn text_system( pub fn text_system(
mut textures: ResMut<Assets<Image>>, mut textures: ResMut<Assets<Image>>,
mut last_scale_factor: Local<f32>, mut last_scale_factors: Local<EntityHashMap<f32>>,
fonts: Res<Assets<Font>>, fonts: Res<Assets<Font>>,
windows: Query<&Window, With<PrimaryWindow>>, camera_query: Query<(Entity, &Camera)>,
default_ui_camera: DefaultUiCamera,
text_settings: Res<TextSettings>, text_settings: Res<TextSettings>,
ui_scale: Res<UiScale>, ui_scale: Res<UiScale>,
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>, mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
mut font_atlas_sets: ResMut<FontAtlasSets>, mut font_atlas_sets: ResMut<FontAtlasSets>,
mut text_pipeline: ResMut<TextPipeline>, mut text_pipeline: ResMut<TextPipeline>,
mut text_query: Query<(Ref<Node>, &Text, &mut TextLayoutInfo, &mut TextFlags)>, mut text_query: Query<(
Ref<Node>,
&Text,
&mut TextLayoutInfo,
&mut TextFlags,
Option<&TargetCamera>,
)>,
) { ) {
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621 let mut scale_factors: EntityHashMap<f32> = EntityHashMap::default();
let window_scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.);
let scale_factor = ui_scale.0 * window_scale_factor; for (node, text, text_layout_info, text_flags, camera) in &mut text_query {
let inverse_scale_factor = scale_factor.recip(); let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
if *last_scale_factor == scale_factor { else {
// Scale factor unchanged, only recompute text for modified text nodes continue;
for (node, text, text_layout_info, text_flags) in &mut text_query { };
if node.is_changed() || text_flags.needs_recompute { let scale_factor = match scale_factors.entry(camera_entity) {
queue_text( Entry::Occupied(entry) => *entry.get(),
&fonts, Entry::Vacant(entry) => *entry.insert(
&mut text_pipeline, camera_query
&mut font_atlas_sets, .get(camera_entity)
&mut texture_atlases, .ok()
&mut textures, .and_then(|(_, c)| c.target_scaling_factor())
&text_settings, .unwrap_or(1.0)
scale_factor, * ui_scale.0,
inverse_scale_factor, ),
text, };
node, let inverse_scale_factor = scale_factor.recip();
text_flags,
text_layout_info,
);
}
}
} else {
// Scale factor changed, recompute text for all text nodes
*last_scale_factor = scale_factor;
for (node, text, text_layout_info, text_flags) in &mut text_query { if last_scale_factors.get(&camera_entity) != Some(&scale_factor)
|| node.is_changed()
|| text_flags.needs_recompute
{
queue_text( queue_text(
&fonts, &fonts,
&mut text_pipeline, &mut text_pipeline,
@ -278,4 +292,5 @@ pub fn text_system(
); );
} }
} }
*last_scale_factors = scale_factors;
} }