From 3d9b1e402508e7beaaaabe0f93f64a332177757f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Thu, 6 Jun 2024 02:20:50 +0200 Subject: [PATCH] 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 --- crates/bevy_ui/src/widget/text.rs | 135 +++++++++++++++++------------- 1 file changed, 75 insertions(+), 60 deletions(-) diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 6d044596a2..2fd25552d2 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -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_ecs::{ + entity::{Entity, EntityHashMap}, prelude::{Component, DetectChanges}, query::With, reflect::ReflectComponent, @@ -9,13 +12,13 @@ use bevy_ecs::{ }; use bevy_math::Vec2; 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_text::{ scale_value, BreakLineOn, Font, FontAtlasSets, Text, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation, }; -use bevy_window::{PrimaryWindow, Window}; +use bevy_utils::Entry; use taffy::style::AvailableSpace; /// 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 /// 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 /// 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) /// method should be called when only changing the `Text`'s colors. pub fn measure_text_system( - mut last_scale_factor: Local, + mut last_scale_factors: Local>, fonts: Res>, - windows: Query<&Window, With>, + camera_query: Query<(Entity, &Camera)>, + default_ui_camera: DefaultUiCamera, ui_scale: Res, - mut text_query: Query<(Ref, &mut ContentSize, &mut TextFlags), With>, + mut text_query: Query< + ( + Ref, + &mut ContentSize, + &mut TextFlags, + Option<&TargetCamera>, + ), + With, + >, ) { - let window_scale_factor = windows - .get_single() - .map(|window| window.resolution.scale_factor()) - .unwrap_or(1.); + let mut scale_factors: EntityHashMap = EntityHashMap::default(); - let scale_factor = ui_scale.0 * window_scale_factor; - - #[allow(clippy::float_cmp)] - if *last_scale_factor == scale_factor { - // scale factor unchanged, only create new measure funcs for modified text - for (text, content_size, text_flags) in &mut text_query { - if text.is_changed() || text_flags.needs_new_measure_func || content_size.is_added() { - create_text_measure(&fonts, scale_factor, text, content_size, text_flags); - } - } - } else { - // scale factor changed, create new measure funcs for all text - *last_scale_factor = scale_factor; - - for (text, content_size, text_flags) in &mut text_query { + for (text, content_size, text_flags, camera) in &mut text_query { + let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) + else { + continue; + }; + let scale_factor = match scale_factors.entry(camera_entity) { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => *entry.insert( + camera_query + .get(camera_entity) + .ok() + .and_then(|(_, c)| c.target_scaling_factor()) + .unwrap_or(1.0) + * ui_scale.0, + ), + }; + 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); } } + *last_scale_factors = scale_factors; } #[allow(clippy::too_many_arguments)] @@ -219,49 +235,47 @@ fn queue_text( #[allow(clippy::too_many_arguments)] pub fn text_system( mut textures: ResMut>, - mut last_scale_factor: Local, + mut last_scale_factors: Local>, fonts: Res>, - windows: Query<&Window, With>, + camera_query: Query<(Entity, &Camera)>, + default_ui_camera: DefaultUiCamera, text_settings: Res, ui_scale: Res, mut texture_atlases: ResMut>, mut font_atlas_sets: ResMut, mut text_pipeline: ResMut, - mut text_query: Query<(Ref, &Text, &mut TextLayoutInfo, &mut TextFlags)>, + mut text_query: Query<( + Ref, + &Text, + &mut TextLayoutInfo, + &mut TextFlags, + Option<&TargetCamera>, + )>, ) { - // TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621 - let window_scale_factor = windows - .get_single() - .map(|window| window.resolution.scale_factor()) - .unwrap_or(1.); + let mut scale_factors: EntityHashMap = EntityHashMap::default(); - let scale_factor = ui_scale.0 * window_scale_factor; - let inverse_scale_factor = scale_factor.recip(); - if *last_scale_factor == scale_factor { - // Scale factor unchanged, only recompute text for modified text nodes - for (node, text, text_layout_info, text_flags) in &mut text_query { - if node.is_changed() || text_flags.needs_recompute { - queue_text( - &fonts, - &mut text_pipeline, - &mut font_atlas_sets, - &mut texture_atlases, - &mut textures, - &text_settings, - scale_factor, - inverse_scale_factor, - text, - node, - 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, camera) in &mut text_query { + let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) + else { + continue; + }; + let scale_factor = match scale_factors.entry(camera_entity) { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => *entry.insert( + camera_query + .get(camera_entity) + .ok() + .and_then(|(_, c)| c.target_scaling_factor()) + .unwrap_or(1.0) + * ui_scale.0, + ), + }; + let inverse_scale_factor = scale_factor.recip(); - 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( &fonts, &mut text_pipeline, @@ -278,4 +292,5 @@ pub fn text_system( ); } } + *last_scale_factors = scale_factors; }