Skip to content
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

Add Context::request_discard for multi-pass layouts #5059

Merged
merged 20 commits into from
Sep 13, 2024
Merged

Conversation

emilk
Copy link
Owner

@emilk emilk commented Sep 2, 2024

Background

Some widgets (like Grid and Table) needs to know the width of future elements in order to properly size themselves. For instance, the width of the first column of a grid may not be known until all rows of the grid has been added, at which point it is too late. Therefore these widgets store sizes from the previous frame. This leads to "first-frame jitter", were the content is placed in the wrong place for one frame, before being accurately laid out in subsequent frames.

What

This PR adds the function ctx.request_discard which discards the visual output and does another pass, i.e. calls the whole app UI code once again (in eframe this means calling App::update again). This will thus discard the shapes produced by the wrongly placed widgets, and replace it with new shapes. Note that only the visual output is discarded - all other output events are accumulated.

Calling ctx.request_discard should only be done in very rare circumstances, e.g. when a Grid is first shown. Calling it every frame will mean the UI code will become unnecessarily slow.

Two safe-guards are in place:

  • Options::max_passes is by default 2, meaning egui will never do more than 2 passes even if request_discard is called on every pass
  • If multiple passes is done for multiple frames in a row, a warning will be printed on the screen in debug builds:

image

Breaking changes

A bunch of things that had "frame" in the name now has "pass" in them instead:

  • Functions called begin_frame and end_frame are now called begin_pass and end_pass
  • FrameState is now PassState
  • etc

TODO

  • Figure out good names for everything (ctx.request_discard)
  • Add API to query if we're gonna repeat this frame (to early-out from expensive rendering)
  • Clear up naming confusion (pass vs frame) e.g. for FrameState
  • Figure out when to call this
  • Show warning on screen when there are several frames in a row with multiple passes
  • Document
  • Default on or off?
  • Change Context::frame_nr name/docs
  • Rename Context::begin_frame/end_frame and deprecate the old ones
  • Test with Rerun
  • Document breaking changes

@emilk emilk added feature New feature or request egui labels Sep 2, 2024
@emilk emilk marked this pull request as draft September 2, 2024 12:43
@emilk emilk added this to the Next Major Release milestone Sep 2, 2024
@emilk emilk mentioned this pull request Sep 10, 2024
@emilk emilk changed the title WIP: ask for a repeated pass WIP: discard visual output Sep 10, 2024
@emilk emilk changed the title WIP: discard visual output Add Context::request_discard Sep 12, 2024
@emilk emilk marked this pull request as ready for review September 13, 2024 09:21
@emilk emilk merged commit 6607610 into master Sep 13, 2024
37 checks passed
@emilk emilk deleted the emilk/skip-frame branch September 13, 2024 12:20
@emilk emilk changed the title Add Context::request_discard Add Context::request_discard for multi-pass layouts Sep 13, 2024
@emilk emilk mentioned this pull request Sep 13, 2024
6 tasks
@juancampa
Copy link
Contributor

Very simple and elegant solution. I appreciate the guardrails too (frame limit and warning) 👌

Wumpf pushed a commit to rerun-io/rerun that referenced this pull request Sep 16, 2024
### What
Uses the latest egui containing this gem:
* emilk/egui#5059

So far `request_discard` (running a second pass) is only used in these
situations:
* For when a `Grid` first shows up
* When an egui_table first shows up
* When asked to auto-size an `egui_table` row

Of course, running a second pass has some costs. Luckily, we already
execute our most expensive stuff (the viewport) last, and so we can
easily early-out from things like point cloud upload/rendering, as shown
in this puffin-capture of a frame with two passes in it:


![image](https://github.com/user-attachments/assets/8fecdc38-191e-4396-afaf-61abaa06437d)

NOTE: this flamegraph is of a DEBUG build - the point is not the
relative times, but that `pass 0` becomes very cheap, if the
`request_discard` is called from one of the non-viewport panels (e.g.
the selection panel).

There might still be cases where we do expensive stuff twice, e.g. if
something calls `request_discard` in a space view tooltip.

If we come to regret this, we can disable all second passes by setting
`egui::Options::max_passes` to `1`.

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7418?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7418?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!
* [x] If have noted any breaking changes to the log API in
`CHANGELOG.md` and the migration guide

- [PR Build Summary](https://build.rerun.io/pr/7418)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.
hacknus pushed a commit to hacknus/egui that referenced this pull request Oct 30, 2024
* Closes emilk#4976
* Part of emilk#4378 
* Implements parts of emilk#843

### Background
Some widgets (like `Grid` and `Table`) needs to know the width of future
elements in order to properly size themselves. For instance, the width
of the first column of a grid may not be known until all rows of the
grid has been added, at which point it is too late. Therefore these
widgets store sizes from the previous frame. This leads to "first-frame
jitter", were the content is placed in the wrong place for one frame,
before being accurately laid out in subsequent frames.

### What
This PR adds the function `ctx.request_discard` which discards the
visual output and does another _pass_, i.e. calls the whole app UI code
once again (in eframe this means calling `App::update` again). This will
thus discard the shapes produced by the wrongly placed widgets, and
replace it with new shapes. Note that only the visual output is
discarded - all other output events are accumulated.

Calling `ctx.request_discard` should only be done in very rare
circumstances, e.g. when a `Grid` is first shown. Calling it every frame
will mean the UI code will become unnecessarily slow.

Two safe-guards are in place:

* `Options::max_passes` is by default 2, meaning egui will never do more
than 2 passes even if `request_discard` is called on every pass
* If multiple passes is done for multiple frames in a row, a warning
will be printed on the screen in debug builds:


![image](https://github.com/user-attachments/assets/c2c1e4a4-b7c9-4d7a-b3ad-abdd74bf449f)

### Breaking changes
A bunch of things that had "frame" in the name now has "pass" in them
instead:

* Functions called `begin_frame` and `end_frame` are now called
`begin_pass` and `end_pass`
* `FrameState` is now `PassState`
* etc


### TODO
* [x] Figure out good names for everything (`ctx.request_discard`)
* [x] Add API to query if we're gonna repeat this frame (to early-out
from expensive rendering)
* [x] Clear up naming confusion (pass vs frame) e.g. for `FrameState`
* [x] Figure out when to call this
* [x] Show warning on screen when there are several frames in a row with
multiple passes
* [x] Document
* [x] Default on or off?
* [x] Change `Context::frame_nr` name/docs
* [x] Rename `Context::begin_frame/end_frame` and deprecate the old ones
* [x] Test with Rerun
* [x] Document breaking changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
egui feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ability to skip rendering a frame / ask for second pass
2 participants