fix(rendering): size gaussian blur and bloom in css pixels - #694
Merged
Merged
Conversation
Since rendering moved to the display's device pixel ratio, render targets are sized in device pixels, and both effects sized their kernels in texels of those targets, so the same settings looked different on every display. - Gaussian blur steps one CSS pixel per tap. On a high-DPI display it box-downsamples the scene to CSS-pixel resolution, blurs there, and upsamples on the last pass, rather than spacing taps pixelRatio texels apart (which skips texels and stripes thin details). - Bloom's downsample block is 4 CSS pixels (4 * pixelRatio texels, rounded). The threshold pass averages every texel in the block, and the blur step is computed from the full-resolution target so rounding the block doesn't change the glow's reach. - Adds an e2e spec comparing each effect's on-screen luminance profile at device scale factors 1 and 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BiAVgvaR1vQhqx1xN871Np
stormmuller
enabled auto-merge (squash)
October 3, 2026 10:09
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Since 0.25.6 the canvas renders at the display's
devicePixelRatio, so render targets are sized in device pixels. The Gaussian blur and bloom systems both sized their kernels in texels of those targets. The same settings therefore looked different on every display: at 200%, blur and glow spread less far on screen, and bloom was much brighter close to small bright sprites. Both effects are now sized in CSS pixels. Output at a pixel ratio of 1 is unchanged.Gaussian blur. Each kernel tap is now one CSS pixel apart. The issue suggested multiplying
u_texelSizebypixelRatio, but that spaces taps 2 texels apart at a ratio of 2. Odd and even device columns would then never mix, which is the bandinggaussian-blur.mdalready warns about. Instead, whenpixelRatio > 1:box-downsample.frag;renderTarget.This also makes HiDPI blur cheaper, since its cost now stays close to what it is at a ratio of 1.
The blend path was restructured as a side effect. A fractional
intensitynow keeps the sharp scene inrenderTarget, and the last pass leaves the blur in the ping-pong pair. That removes the separate snapshot copy, so blending costs 2 extra draws instead of 3.Bloom.
round(4 * pixelRatio)texels, which is 4×4 CSS pixels.bloom-threshold.fragtakes au_blockSizeuniform and thresholds every texel in the block before averaging. Before, it took a fixed 4×4 set of samples.Not done: the issue's optional idea of also scaling with window size, by taking the kernel step as a fraction of the target's height. CSS-pixel sizing matches how the rest of the engine sizes things, such as text outlines and UI
constantPixelSize.Evidence
A new e2e spec (
e2e/specs/post-process-pixel-ratio.spec.ts) renders a small white square with each effect at device scale factors 1 and 2. It compares the on-screen luminance profiles in CSS pixels, as a relative, same-run measurement.While writing this, the e2e also caught a read/write feedback loop in my first version of the blur loop. A new unit test now checks that no draw samples the texture it is drawing into.
Related issue(s)
Defect 008: "Blur and bloom are sized in device pixels, so their look changes with the display's pixel ratio".
Verification checklist
npm run check-typespasses with 0 errorsnpm testpasses (1747 tests)npm run lintpasses with 0 errors (2 existing TODO warnings)npm run cspellpasses with 0 errorsnpm run check-exportspassesindex.ts:boxDownsampleFragmentShaderis exported from the shaders module and registered increateShaderCache/documentation-site/docs/docsis updated:bloom.mdandgaussian-blur.md(new "Same look on every display" section, updated performance notes)npm run buildat the repo root;documentation-site/,npm run buildandnpm run typecheckpass;Changelog
## [Unreleased]inCHANGELOG.md(#### Fixed)🤖 Generated with Claude Code
https://claude.ai/code/session_01BiAVgvaR1vQhqx1xN871Np
Generated by Claude Code