Skip to content

Fix debug-profile slider stutter (#25500) - #25536

Merged
alice-i-cecile merged 8 commits into
bevyengine:mainfrom
bugsweeper:fix/issue-25500-slider-debug-perf
Sep 2, 2026
Merged

alice-i-cecile merged 8 commits into
bevyengine:mainfrom
bugsweeper:fix/issue-25500-slider-debug-perf

Conversation

@bugsweeper

Copy link
Copy Markdown
Contributor

Objective

  • Fixes feathers_gallery poor performance in debug mode #25500.
  • On debug builds, dragging the color slider/color plane in feathers_gallery updated in visible discrete steps instead of smoothly. Measured on Linux + NVIDIA RTX 4070 + Vulkan: out of 138 consecutive frames during a slow, controlled drag, only 86 states were unique (52 frames repeated the previous state) in debug, versus 129/138 unique in release. The slider's value updated correctly the whole time — this was a rendering smoothness problem, not an input problem.
  • Two causes were found:
    • EditableText triggered a full intrinsic-size recomputation (and thus a full Taffy layout pass) every frame, regardless of whether its rendered size had actually changed.
    • ColorSlider and ColorPlane moved their thumb every frame by writing Node::left/Node::top, which are layout-affecting properties and force a Taffy relayout on every frame during a drag.
    • Both are comparatively cheap in optimized builds but expensive enough in debug builds to visibly drop frames.

Solution

  • update_editable_text_content_size now caches the inputs that actually determine EditableText's intrinsic size (visible_width, visible_lines) in a new EditableTextContentSizeState component, and only recomputes when those specific fields change, instead of on every change to EditableText (which also churns from unrelated fields like cursor blink/edits). EditableTextContentSizeState is wired up as a required component of bevy_text::EditableText via register_required_components in UiPlugin, since bevy_text doesn't depend on bevy_ui.
  • ColorSlider and ColorPlane now position their thumb via UiTransform instead of Node::left/Node::top. UiTransform is applied after layout and doesn't invalidate Taffy, so moving the thumb no longer forces a relayout.
  • ColorPlane's thumb positioning was split out of update_plane_color into its own system, update_plane_thumb_position, which runs unconditionally every frame so the thumb keeps tracking the parent node's actual size (e.g. on window resize) even when the color value hasn't changed. update_plane_color keeps its original Changed filter, since it mutates Assets<ColorPlaneMaterial>, which is comparatively expensive to touch every frame.

Testing

  • cargo test -p bevy_ui --lib: 66/66 passed.
  • cargo test -p bevy_feathers --lib: 10/10 passed.
  • cargo fmt --check and git diff --check pass.
  • Manually verified in feathers_gallery (debug profile) that the color slider and color plane thumbs track the mouse correctly and land at the right position during and after a drag.
  • Manually verified that resizing the window while the color plane is visible keeps its thumb tracking the container's size correctly, both with and without a value change in between.
  • Measured debug-profile FPS during continuous slider drag: ~11-14 FPS before this fix, ~59-63 FPS after, both at rest and while continuously dragging.
  • Tested on Linux + NVIDIA RTX 4070 + Vulkan only. Not tested on Windows/macOS or other GPU vendors; the fix is platform-agnostic (it removes unnecessary layout/material work), so I don't expect platform-specific regressions, but review/testing on other platforms is welcome.

EditableText re-triggered intrinsic-size recomputation and a full
Taffy layout pass every frame regardless of whether its rendered
size had actually changed, and ColorSlider/ColorPlane moved their
thumb via Node::left/top (layout-affecting properties), forcing a
relayout on every frame during drag. In debug builds this made the
color slider/plane update in visible discrete steps instead of
smoothly.

- Cache the inputs that determine EditableText's intrinsic size
  (visible_width/visible_lines) and only recompute when they change.
- Move ColorSlider and ColorPlane thumb positioning to UiTransform,
  which doesn't invalidate layout, instead of Node::left/top.
- Split ColorPlane's thumb positioning into its own unfiltered system
  so it keeps tracking the container's resized size every frame,
  while update_plane_color keeps its Changed filter to avoid
  needlessly mutating Assets<ColorPlaneMaterial>.

Debug-profile FPS during continuous slider drag went from ~11-14 FPS
to ~59-63 FPS on Linux + NVIDIA RTX 4070 + Vulkan.
@bugsweeper

bugsweeper commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor Author

The macOS build job did not report a test failure; it was cancelled after reaching the workflow’s 40-minute timeout while running cargo run -p ci -- test. Ubuntu and Windows builds passed, as did run-examples-macos-metal. I’ll update the branch against current main to trigger a fresh run. If macOS times out again, could a maintainer please rerun the failed job?

…sue-25500-slider-debug-perf

# Conflicts:
#	crates/bevy_feathers/src/controls/color_slider.rs
@Zeophlite Zeophlite added A-UI Graphical user interfaces, styles, layouts, and widgets D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Aug 25, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in UI Aug 25, 2026

@cookie1170 cookie1170 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested it for myself, debug-mode performance did improve a lot! sadly the normal slider is still laggy, though i imagine similar changes there might not work since it actually needs to change size
i'm not entirely sure how often this would matter in real setups, however, since even the quick start guide recommends you compile bevy with opt 3

@Zeophlite
Zeophlite requested review from ickshonpe and viridia August 30, 2026 04:48
Slider dragging in feathers_gallery causes visible frame drops in debug
builds (bevyengine#25500): the numeric caption's ContentSize is marked Changed
every frame even when its measured size is identical, forcing
ui_layout_system to re-sync the node with Taffy on every frame.

Cache the last computed NoWrap intrinsic size on TextNodeFlags and only
update ContentSize when it actually changes. Also switch the slider's
numeric caption to TextLayout::no_wrap() so it takes this cheaper fixed-
measure path instead of the wrapping text-measure path.
@bugsweeper

Copy link
Copy Markdown
Contributor Author

@cookie1170 Thanks for testing and pointing out the normal slider - it was using a different code path and was indeed still triggering a full UI layout while dragging.
The value label was remeasured on every update, and replacing its ContentSize invalidated Taffy even when the measured size had not changed. I’ve now made the label explicitly non-wrapping and cached its last intrinsic size, so glyphs are still updated every frame, but layout is only invalidated when the label’s dimensions actually change.
In the feathers_gallery debug build, this reduced the average ui_layout_system time from about 61 ms to 11 ms and increased the number of processed frames in the same profiling interval from 90 to 285. There may still be an occasional slower frame when the label genuinely changes width, such as 9.00 -> 10.00, but regular dragging should now be substantially smoother.
So this was worth fixing independently of the recommendation to use opt-level = 3: debug mode exposed a real case of unnecessarily invalidating the full UI layout.

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 2, 2026
@alice-i-cecile alice-i-cecile added this to the 0.20 milestone Sep 2, 2026
@alice-i-cecile alice-i-cecile added C-Performance A change motivated by improving speed, memory usage or compile times S-Merge-Conflicts Merge conflicts :( Add this label on top of other S- labels. labels Sep 2, 2026
@alice-i-cecile

Copy link
Copy Markdown
Member

I'd like to merge this for 0.20: are you able to resolve merge conflicts? :)

…sue-25500-slider-debug-perf

# Conflicts:
#	crates/bevy_feathers/src/controls/color_plane.rs
#	crates/bevy_feathers/src/controls/color_slider.rs
#	crates/bevy_feathers/src/controls/slider.rs
@bugsweeper

Copy link
Copy Markdown
Contributor Author

@alice-i-cecile

Sure - I've resolved the merge confilcts against the lastest main and all the tests passed.

@Zeophlite Zeophlite removed the S-Merge-Conflicts Merge conflicts :( Add this label on top of other S- labels. label Sep 2, 2026
@alice-i-cecile
alice-i-cecile added this pull request to the merge queue Sep 2, 2026
Merged via the queue into bevyengine:main with commit f36de6b Sep 2, 2026
38 checks passed
@github-project-automation github-project-automation Bot moved this from Needs SME Triage to Done in UI Sep 2, 2026
alice-i-cecile added a commit to viridia/bevy that referenced this pull request Sep 2, 2026
Bug introduced in interaction of bevyengine#25536 with this PR.
ewmb7701 pushed a commit to ewmb7701/bevy that referenced this pull request Sep 5, 2026
# Objective

- Fixes bevyengine#25500.
- On debug builds, dragging the color slider/color plane in
`feathers_gallery` updated in visible discrete steps instead of
smoothly. Measured on Linux + NVIDIA RTX 4070 + Vulkan: out of 138
consecutive frames during a slow, controlled drag, only 86 states were
unique (52 frames repeated the previous state) in debug, versus 129/138
unique in release. The slider's *value* updated correctly the whole time
— this was a rendering smoothness problem, not an input problem.
- Two causes were found:  
- `EditableText` triggered a full intrinsic-size recomputation (and thus
a full Taffy layout pass) every frame, regardless of whether its
rendered size had actually changed.
- `ColorSlider` and `ColorPlane` moved their thumb every frame by
writing `Node::left`/`Node::top`, which are layout-affecting properties
and force a Taffy relayout on every frame during a drag.
- Both are comparatively cheap in optimized builds but expensive enough
in debug builds to visibly drop frames.

## Solution

- `update_editable_text_content_size` now caches the inputs that
actually determine `EditableText`'s intrinsic size (`visible_width`,
`visible_lines`) in a new `EditableTextContentSizeState` component, and
only recomputes when those specific fields change, instead of on every
change to `EditableText` (which also churns from unrelated fields like
cursor blink/edits). `EditableTextContentSizeState` is wired up as a
required component of `bevy_text::EditableText` via
`register_required_components` in `UiPlugin`, since `bevy_text` doesn't
depend on `bevy_ui`.
- `ColorSlider` and `ColorPlane` now position their thumb via
`UiTransform` instead of `Node::left`/`Node::top`. `UiTransform` is
applied after layout and doesn't invalidate Taffy, so moving the thumb
no longer forces a relayout.
- `ColorPlane`'s thumb positioning was split out of `update_plane_color`
into its own system, `update_plane_thumb_position`, which runs
unconditionally every frame so the thumb keeps tracking the parent
node's actual size (e.g. on window resize) even when the color value
hasn't changed. `update_plane_color` keeps its original `Changed`
filter, since it mutates `Assets<ColorPlaneMaterial>`, which is
comparatively expensive to touch every frame.

## Testing

- `cargo test -p bevy_ui --lib`: 66/66 passed.
- `cargo test -p bevy_feathers --lib`: 10/10 passed.
- `cargo fmt --check` and `git diff --check` pass.
- Manually verified in `feathers_gallery` (debug profile) that the color
slider and color plane thumbs track the mouse correctly and land at the
right position during and after a drag.
- Manually verified that resizing the window while the color plane is
visible keeps its thumb tracking the container's size correctly, both
with and without a value change in between.
- Measured debug-profile FPS during continuous slider drag: ~11-14 FPS
before this fix, ~59-63 FPS after, both at rest and while continuously
dragging.
- Tested on Linux + NVIDIA RTX 4070 + Vulkan only. Not tested on
Windows/macOS or other GPU vendors; the fix is platform-agnostic (it
removes unnecessary layout/material work), so I don't expect
platform-specific regressions, but review/testing on other platforms is
welcome.
joelawm pushed a commit to joelawm/bevy that referenced this pull request Sep 8, 2026
# Objective

- Fixes bevyengine#25500.
- On debug builds, dragging the color slider/color plane in
`feathers_gallery` updated in visible discrete steps instead of
smoothly. Measured on Linux + NVIDIA RTX 4070 + Vulkan: out of 138
consecutive frames during a slow, controlled drag, only 86 states were
unique (52 frames repeated the previous state) in debug, versus 129/138
unique in release. The slider's *value* updated correctly the whole time
— this was a rendering smoothness problem, not an input problem.
- Two causes were found:  
- `EditableText` triggered a full intrinsic-size recomputation (and thus
a full Taffy layout pass) every frame, regardless of whether its
rendered size had actually changed.
- `ColorSlider` and `ColorPlane` moved their thumb every frame by
writing `Node::left`/`Node::top`, which are layout-affecting properties
and force a Taffy relayout on every frame during a drag.
- Both are comparatively cheap in optimized builds but expensive enough
in debug builds to visibly drop frames.

## Solution

- `update_editable_text_content_size` now caches the inputs that
actually determine `EditableText`'s intrinsic size (`visible_width`,
`visible_lines`) in a new `EditableTextContentSizeState` component, and
only recomputes when those specific fields change, instead of on every
change to `EditableText` (which also churns from unrelated fields like
cursor blink/edits). `EditableTextContentSizeState` is wired up as a
required component of `bevy_text::EditableText` via
`register_required_components` in `UiPlugin`, since `bevy_text` doesn't
depend on `bevy_ui`.
- `ColorSlider` and `ColorPlane` now position their thumb via
`UiTransform` instead of `Node::left`/`Node::top`. `UiTransform` is
applied after layout and doesn't invalidate Taffy, so moving the thumb
no longer forces a relayout.
- `ColorPlane`'s thumb positioning was split out of `update_plane_color`
into its own system, `update_plane_thumb_position`, which runs
unconditionally every frame so the thumb keeps tracking the parent
node's actual size (e.g. on window resize) even when the color value
hasn't changed. `update_plane_color` keeps its original `Changed`
filter, since it mutates `Assets<ColorPlaneMaterial>`, which is
comparatively expensive to touch every frame.

## Testing

- `cargo test -p bevy_ui --lib`: 66/66 passed.
- `cargo test -p bevy_feathers --lib`: 10/10 passed.
- `cargo fmt --check` and `git diff --check` pass.
- Manually verified in `feathers_gallery` (debug profile) that the color
slider and color plane thumbs track the mouse correctly and land at the
right position during and after a drag.
- Manually verified that resizing the window while the color plane is
visible keeps its thumb tracking the container's size correctly, both
with and without a value change in between.
- Measured debug-profile FPS during continuous slider drag: ~11-14 FPS
before this fix, ~59-63 FPS after, both at rest and while continuously
dragging.
- Tested on Linux + NVIDIA RTX 4070 + Vulkan only. Not tested on
Windows/macOS or other GPU vendors; the fix is platform-agnostic (it
removes unnecessary layout/material work), so I don't expect
platform-specific regressions, but review/testing on other platforms is
welcome.
pull Bot pushed a commit to VitalyAnkh/bevy that referenced this pull request Sep 20, 2026
# Objective

PR bevyengine#25536 fixed performance for the color sliders and plane, but not the
wheel as it had not merged yet.

## Solution

This PR replicates the solution by using `UiTransform` instead of
`Node::left`/`Node::top` to position the thumbs. This avoids a Taffy
relayout.

## Testing

Ran `feathers_example`, thumbs are still positioned correctly.
Performance is slightly better on my machine, around 50fps rather than
45.
gregcsokas pushed a commit to gregcsokas/bevy that referenced this pull request Sep 21, 2026
# Objective

PR bevyengine#25536 fixed performance for the color sliders and plane, but not the
wheel as it had not merged yet.

## Solution

This PR replicates the solution by using `UiTransform` instead of
`Node::left`/`Node::top` to position the thumbs. This avoids a Taffy
relayout.

## Testing

Ran `feathers_example`, thumbs are still positioned correctly.
Performance is slightly better on my machine, around 50fps rather than
45.
mockersf pushed a commit that referenced this pull request Sep 25, 2026
# Objective

PR #25536 fixed performance for the color sliders and plane, but not the
wheel as it had not merged yet.

## Solution

This PR replicates the solution by using `UiTransform` instead of
`Node::left`/`Node::top` to position the thumbs. This avoids a Taffy
relayout.

## Testing

Ran `feathers_example`, thumbs are still positioned correctly.
Performance is slightly better on my machine, around 50fps rather than
45.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-UI Graphical user interfaces, styles, layouts, and widgets C-Performance A change motivated by improving speed, memory usage or compile times D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

feathers_gallery poor performance in debug mode

5 participants