Repository navigation
Downscale oversized images before atlas upload - #19
chris-addison merged 2 commits into
Conversation
The sprite atlas has no mipmaps and every renderer samples it bilinearly, so an image drawn far below its pixel size reads only the 2x2 texels nearest each screen pixel and skips the rest. A 512px editor badge drawn at 32px on a 100% Windows display read 4 of every 256 source pixels and came out jagged; Retina hid most of it at 8:1. paint_image now picks a whole number of halvings that brings the frame within 2x of its device-pixel draw size, box-filters a copy on the CPU (alpha-weighted, so transparent edges do not darken) and uploads that. RenderImageParams carries the level, so each image keeps at most a handful of variants however its draw size changes, and drop_image frees all of them. Images drawn at or near their own size upload as before.
chris-addison
left a comment
There was a problem hiding this comment.
This takes the same approach as Chromium: upload a copy shrunk by whole halvings, then let the GPU do the last step of up to 2:1.
| Check | What I saw | |
|---|---|---|
| ✅ | How many halvings | Matches Chromium: stays at or above the draw size, and the axis shrunk less decides |
| ✅ | Cache key | The halving count is part of the key, as in Chromium |
| ✅ | Edge blending | Weighting by alpha matches Chromium's premultiplied averaging, so transparent edges don't darken |
| ✅ | Whole halvings, not exact size | Right call. Chromium also switches to mipmaps when it shrinks an image |
| ❌ | Paint cost | The shrink runs on the UI thread during paint. A large avatar could stall one frame the first time it's drawn at each size. Chromium does this on worker threads |
| ❌ | Extra copy | downscale_bgra copies the whole source with to_vec() before the first halving. The first pass could read the borrowed slice directly |
Not covered: Windows. Badges at 100% scale there, where the bug was reported, still need a check before the Hub moves its pin. An image shrunk by an element transform still aliases, but the Hub probably doesn't do that.
Approving: neither ❌ blocks this, and both can be follow-ups.
|
Thanks Chris. The extra copy is gone in 04cf99f, and I measured the paint cost: badges are cheap, but very large avatars can drop a frame. Extra copy: the first halving now reads the borrowed source slice, so nothing is copied up front. Paint cost: the first draw at each size, release build on an M5 Max, median of 41 runs:
Almost all the time is the filter loop, not the copy. A badge costs under a millisecond once, but a 2048px avatar takes about one 60 Hz frame, and slower Windows machines will take longer. Moving the shrink to a background thread will be a follow-up PR, as you suggested. |
Images drawn well below their pixel size now come out smooth instead of jagged. Found in the native Unity Hub on Windows: the editor version badges (512px PNGs drawn at 32px) and the account avatar looked pixelated.
Window::paint_imagehalves the frame on the CPU until it is within 2x of its device-pixel draw size, then uploads that copy. The filter is a 2x2 box weighted by alpha, so transparent pixels don't darken edges.downscale_levelfield onRenderImageParams, so an image keeps at most a few variants however its size changes during a resize or animation.drop_imagefrees all of them, andhas_image_atlas_entrycounts any of them.Rounding to whole halvings is a trade-off: the GPU still does the last step of up to 2:1, so a small icon can look slightly soft. Exact-size resampling would make a new atlas entry for every size an image passes through.
Screenshots
Hub visual scenes rendered headless at scale 1 on the same Mac, before (left) and after (right), zoomed.
Editor badge, Installs list:
Legacy Unity cube, Welcome page:
Template icon, New Project (the softest case):
Testing
assets::testscover the level choice (32px at 100%, 125%, 150% and 200% scale), odd sides, the one-pixel floor, a checkerboard averaging to grey, and alpha weighting.elements::img::tests: a 512px image drawn at 32px uploads a tile between 32 and 64px anddrop_imagefrees it; an image drawn at its own size uploads the original.cargo test --workspace, clippy (all targets) andcargo fmt --checkpass on macOS.gpui_ce_windowstype-checks forx86_64-pc-windows-msvc(all targets,render-to-image,test-support), with shader compilation stubbed; it was not run on Windows.This branch is
unity/hub-consolidatedplus one commit. The Hub's current pin isb117a40da3(#17 on top of this base), and the commit cherry-picks onto it cleanly.🤖 Generated with Claude Code