Skip to content

Shrink large images off the UI thread - #20

Draft
toddbeauchamp wants to merge 1 commit into
unity/HUB-0000-downscale-oversized-imagesfrom
unity/HUB-0000-async-image-downscale
Draft

toddbeauchamp wants to merge 1 commit into
unity/HUB-0000-downscale-oversized-imagesfrom
unity/HUB-0000-async-image-downscale

Conversation

@toddbeauchamp

Copy link
Copy Markdown

Taude Taude:

Follow-up to #19 (stacked on it): large images are now shrunk off the UI thread, so the first frame that draws a big avatar no longer drops.

  • Before: Window::paint_image box-filtered an oversized image inline, inside the atlas upload, the first time it was drawn at each downscale level. That was 11 ms for a 2048px avatar on an M5 Max, a dropped frame at 60 Hz, and slower Windows machines are worse.
  • Now: images over 128x128 pixels are shrunk on the background executor, every frame of the image in one task. Until the copy lands, paint draws the nearest copy already in the atlas (so a resize keeps showing the old size), or nothing on first sight. The views that painted the image are notified when it lands, so cached views repaint too.
  • Small images (128x128 and under, about 10 µs) are still shrunk during paint, so icons never pop in.
  • Faster filter: fixed 2x2 blocks over chunked rows, and no per-channel division for fully opaque blocks. Output is bit-identical to Downscale oversized images before atlas upload #19's filter.
  • Unchanged: the downscale_level cache key, drop_image freeing every level (it now also cancels a pending copy), and has_image_atlas_entry reporting what is in the atlas.

Timing

Filter time, release build, M5 Max, best of 30, shrinking to 32px:

Source #19 (on the UI thread) This PR (off the UI thread)
512px 0.78 ms 0.14 ms
1024px 2.72 ms 0.49 ms
2048px 10.98 ms 1.99 ms

Images with transparency run about 30% slower than opaque ones on the new path (2.66 ms at 2048px). Paint itself now only does an atlas lookup and a task spawn for these images.

Testing

  • New elements::img tests: first paint draws nothing and the next frame after the task draws a 32px tile; a resize keeps the 256px copy until the 32px one is ready; drop_image cancels a pending copy; an animated image gets every frame's copy at once; a 128px image is still shrunk during paint; a cached child view repaints when its copy lands (fails if the notify is removed).
  • Old and new filters compared on random opaque and translucent images, including odd and 1px sides, at levels 0 to 7: identical output.
  • cargo test --workspace, cargo clippy --workspace --all-targets and cargo fmt --all --check pass on macOS.
  • The Hub's headless visual capture draws 3 frames with run_until_parked between them, which covers decode plus shrink, so badges and avatars still appear in captures.

Paint used to box-filter an oversized image inline the first time it was
drawn at each downscale level, which took 11 ms for a 2048px avatar on an
M5 Max. Images over 128x128 pixels are now shrunk on the background
executor, every frame at once. Until the copy is uploaded, paint draws the
nearest copy already in the atlas, or nothing, and the views that painted
the image are notified when it lands. drop_image cancels a pending copy.

The filter itself is about 5x faster with bit-identical output: fixed-size
2x2 blocks over chunked rows, and no per-channel division for fully
opaque blocks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant