-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Adding TextOutline component to add outlining by upstreaming implemen… #19639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Can you add some screenshots for showcase? With and without the component I don't know if permission to upstream something is necessary, but have you talked with either someone from bevy and/or bevy_cobweb_ui? |
Also have 2 different lines for the fixes, GitHub only linked to the first issue |
…tation from bevy_cobweb_ui fixes: bevyengine#17076 fixes: bevyengine#7210 Co-authored-by: koe <ukoe@protonmail.com>
@UkoeHB are you okay if we upstream this? |
@alice-i-cecile yes, no problem. |
crates/bevy_ui/src/render/mod.rs
Outdated
for offset_x in -width..=width { | ||
for offset_y in -width..=width { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not obvious, but this has catastrophically awful worst case performance.
For example, suppose that a Text
has three glyphs, each from a different font, and an outline width of 3. In the inner PositionedGlyph
s loop the font texture atlas changes for each glyph, so a new batch is generated per glyph. The inner loop is repeated 48 times, so 144 batches are produced for just three glyphs.
Instead the PositionedGlyph
s need to be iterated in the outer loop. Then all the outline sprites for each glyph will be batched together, at worst resulting in one batch per glyph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please have a look if I understood you correctly when you have a chance, I just moved the order of the iterations as I think you meant.
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
…r to only one batch per glyph
crates/bevy_ui/src/render/mod.rs
Outdated
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| { | ||
info.span_index != *span_index | ||
|| info.atlas_info.texture != atlas_info.texture | ||
}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| { | |
info.span_index != *span_index | |
|| info.atlas_info.texture != atlas_info.texture | |
}) { | |
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| { | |
info.atlas_info.texture != atlas_info.texture | |
}) { |
I think it's safe to remove this, since span-bound text color is irrelevant. @ickshonpe what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this ExtractedUiNode
bit needs to be moved to the outer loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If i move the extraced_uinodes.push bit, I get strange artifacts in the output...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If i move the extraced_uinodes.push bit, I get strange artifacts in the output...
Only extracted_uinodes.uinodes
should be in the outer loop (after the inner loop). The .glyphs
needs to stay in the inner loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, thats what I tried, and that ended up giving me the artifacts, I am not quite sure as to why, but the output hints at me pushing the values wrongly somehow..
extracted_uinodes.glyphs.push(ExtractedGlyph {
transform: Affine2::from_mat3(
transform * Mat3::from_translation(*position),
),
rect,
});
} // offset y
} // offset x
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| {
info.span_index != *span_index || info.atlas_info.texture != atlas_info.texture
}) {
start = end;
extracted_uinodes.uinodes.push(ExtractedUiNode {
render_entity: commands.spawn(TemporaryRenderEntity).id(),
stack_index: uinode.stack_index,
color,
image: atlas_info.texture.id(),
clip: clip.map(|clip| clip.clip),
extracted_camera_entity,
rect,
item: ExtractedUiItem::Glyphs { range: start..end },
main_entity: entity.into(),
});
}
end += 1;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah looks like end += 1;
needs to be moved to the inner loop after pushing to .glyphs
. And start = end;
needs to move to the top of the outer loop. The conditional there should be moved to a separate bool variable since you'll have two blocks conditioned on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I fixed it in bevy_cobweb_ui
: https://github.com/UkoeHB/bevy_cobweb_ui/blob/a3265e1c80a4b21cdd7cf8200e294f2a6712248e/src/ui_bevy/ui_ext/text_rendering.rs#L11
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's safe to remove this, since span-bound text color is irrelevant. @ickshonpe what do you think?
Yep not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just had another look before I go to sleep, I see more problems now but too tired to think things through properly:
- It's going to be awkward to get it to work well with scale factor or transform scaling transform, might not be possible even.
- The offsets should radiate out rather than being arranged in a grid.
- Outline width should be clamped to a relatively low max value.
|
||
let transform = Affine2::from(*global_transform) | ||
* Affine2::from_translation( | ||
-0.5 * uinode.size() + offset / uinode.inverse_scale_factor(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think multiplying the offset scale factor is probably wrong, it might cause gaps in the outline at higher scale factors even. The outline width needs to be scaled instead.
crates/bevy_ui/src/render/mod.rs
Outdated
for offset_x in -width..=width { | ||
for offset_y in -width..=width { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The offsets could be in a circular pattern instead, so the outline doesn't get distorted.
@TotalKrill I applied @ickshonpe's feedback: https://github.com/UkoeHB/bevy_cobweb_ui/blob/main/src/ui_bevy/ui_ext/text_rendering.rs Note we need to batch anti-aliased glyphs separate from non-anti-aliased glyphs since they have different colors. |
I don't like this, but you are probably right that it's necessary. With a UI scale of 6, on an M1 Mac (scale factor 2), with width 4, the lag penetrated my OS (laggy cursor even without the test app focused). |
Yep it's very non-obvious that text outlines should be so expensive. We could make width into an enum even. Have thin, medium, and thick variants or something? |
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
I would argue that we could go with a clearer enum of Px1, Px2, Px3. Mostly because Px4 is so expensive as to be almost unusable. It would also quite clearly communicate the limitations of this implementation, while still allowing us to have it. I imagine that for non-changing outlined text, this would be very usable anyway. |
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Let me know what You think of this impl, I dont particulary like it, but it is very hard to miss the potential performance cost of using this feature. For my usecase to be able to read white text, on a mostly white background when viewed from certain angles, this will be very sufficient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TotalKrill can you check the updated reference implementation? In order to get antialiasing right, some changes are needed.
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
Updated with your reference implementation, just adjusted for the changes from GlobalTransform -> UiGlobalTransform |
Add Text Outline component to add text borders/outline to text nodes by upstreaming implementation from bevy_cobweb_ui
fixes: #17076,#7210