Repository navigation
[Impeller] Sample the render target rect in offscreen advanced blends - #192988
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request updates the blend filter contents in Impeller to snap coverage to the pixel grid using Rect::RoundOut in both AdvancedBlend and PipelineBlend paths, ensuring that texture coordinates, subpass extent, and composite translation align. It also adds unit tests to verify pixel alignment. Feedback is provided to restore the original capabilities at the end of the AdvancedBlendSubpassIsPixelAligned test to prevent global state pollution and potential test flakiness.
d30451d to
b3ec9da
Compare
Advanced and pipeline blends that take the offscreen path derive three
things from the blend coverage, and they do not agree:
* the texture coordinates, from the raw float coverage;
* the subpass render target extent, from `ISize(coverage.GetSize())`,
whose converting constructor truncates rather than rounds;
* the composite translation, from the fractional coverage origin, drawn
with the default nearest sampler.
An integer quad therefore displays a float sized region, scaling the
contents by trunc(w)/w. The factor changes whenever the coverage crosses
a pixel boundary, so animated content is resampled differently every
frame.
Snap the coverage to the device pixel grid once with `Rect::RoundOut` and
derive all three from that single integral rect. `Canvas::SaveLayer`
already does this for save layer subpasses, for the same nearest sampling
reason; the blend filter path never got the same treatment.
Only configurations without framebuffer fetch take this path: the iOS
simulator, PowerVR, and Adreno on GLES. Devices with framebuffer fetch
blend in pass via `ApplyFramebufferBlend`, which is exact.
Adds a regression test per offscreen path, asserting the composited
entity lands on the pixel grid when the input coverage is fractional.
Both fail without this change.
Fixes flutter#192980
b3ec9da to
df2b025
Compare
|
There are golden failures that look like a clipping issue. |
Replace the RoundOut approach with one that leaves the subpass geometry untouched: the render target size, composite origin and composited rect are as on master. Only the texture coordinates change, derived from the rect the render target actually represents so that the quad and the sampled region agree exactly. Rounding the subpass out changed the composited rect, and the earlier unit tests asserted that alignment; both are dropped. The ColorFilterAdvancedBlendNoFbFetch golden, which stacks thirty of these blends and visibly captures the resampling error, is the regression coverage.
|
You were right that it changes that golden — but digging in, it's a correction rather than a regression. That test stacks 30 full-frame blends, so the resampling error the PR fixes accumulates into a smear: the current golden bleeds orange ~4px into the empty gap before each cell. Rendering the same test through framebuffer fetch puts the edge exactly where this PR does. I've also reworked the fix to be much smaller — it just makes the texture coords match the render target rect, with no change to the subpass geometry. The PR description has the pixel numbers. The golden will need triage. |
|
It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging. If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group. |
DlGoldenTest.StackedOffscreenAdvancedBlendsDoNotResample renders a hard backdrop edge, then blends over it thirty times with kLighten on a mocked context without framebuffer fetch, and requires the result to match a single blend. Lighten is idempotent, so the only thing that can differ is resampling error accumulated across the stacked subpasses. A 0.4x scale on the canvas makes the full-pass coverage fractional the same way the ColorFilterAdvancedBlendNoFbFetch golden does, so the subpass render target is truncated. Without the fix the edge drifts and the RMSE is about 5.75; with it the two images are identical.
|
CI had a failure that stopped further tests from running. We need to investigate to determine the root cause. SHA at time of execution: ec76295. Possible causes:
A blank commit, or merging to head, will be required to resume running CI for this PR. Error Details: Stack trace: |
|
Merging main to resolve .ci.yaml issue |
|
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
b-luk
left a comment
There was a problem hiding this comment.
This looks good to me. Just one suggested nit
render_target_coverage shares its origin with subpass_coverage, so the translation between them is always zero.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request updates the advanced blend filter in Impeller to snap the subpass render target to an integral size when framebuffer fetch is unavailable, preventing scaling drift and resampling issues on fractional boundaries. It also adds corresponding golden and unit tests. Feedback on the changes identifies a critical issue where the vertex buffer generation still uses coverage.GetSize() instead of render_target_size after the removal of the translation matrix from frame_info.mvp, which can lead to rendering distortion when a coverage hint is present.
|
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
I approved the updated goldens |
…13104) Manual roll requested by [email protected] flutter/flutter@d649d2b...e89fd0a 2026-10-01 [email protected] Manual roll ICU from 6ebb40c59477 to 5aa526207171 (2 revisions) (flutter/flutter#193661) 2026-10-01 [email protected] fix(web): hash source maps before rewriting sourceMappingURL with --web-content-hash (flutter/flutter#193614) 2026-10-01 [email protected] [flutter_tools] Bootstrap DeviceManager in ToolDependencies (flutter/flutter#193524) 2026-10-01 [email protected] [wasm] Set flutter_runtime_mode in tools/gn to_gn_wasm_args (flutter/flutter#193407) 2026-10-01 [email protected] Remove remaining `download_jdk` references (flutter/flutter#193562) 2026-10-01 [email protected] Synchronize dart2wasm feature probes in roll script and flutter_bootstrap.js (flutter/flutter#193271) 2026-10-01 [email protected] [web] Unskip web tests that now pass in gestures, services, and widgets (flutter/flutter#193512) 2026-10-01 [email protected] Roll Fuchsia Test Scripts from whZAV1mfT-Rdc0rQ8... to fCNknap4ZI0AMcvIf... (flutter/flutter#193640) 2026-10-01 [email protected] Roll Skia from 0575a37d0619 to f2d68e0b8863 (2 revisions) (flutter/flutter#193621) 2026-10-01 [email protected] perf(widgets): use const <Never>{} in InheritedModelElement and avoid redundant casts in RenderObjectElement.update (flutter/flutter#192913) 2026-10-01 [email protected] [web] Unskip and strengthen missing glyph tofu test on Wimp (flutter/flutter#193426) 2026-10-01 [email protected] [flutter_tools] Fix `flutter drive -d chrome` hanging on web (flutter/flutter#193459) 2026-10-01 [email protected] [web] Fix hang when Chromium cannot bind remote debugging port (flutter/flutter#193461) 2026-10-01 [email protected] [flutter_tools] Bundle data assets from hooks in flutter test (flutter/flutter#193435) 2026-10-01 [email protected] Roll Packages from 0ba9a82 to d5ec6db (11 revisions) (flutter/flutter#193641) 2026-10-01 [email protected] Revert: [flutter_tools] Fix DTD "Stream already subscribed" error and wait for analysis over DTD in widget preview (flutter/flutter#193637) 2026-10-01 [email protected] Roll Skia from 6182a3cf8f98 to 0575a37d0619 (5 revisions) (flutter/flutter#193613) 2026-10-01 [email protected] [flutter_tools] Fix DTD "Stream already subscribed" error and wait for analysis over DTD in widget preview (flutter/flutter#192930) 2026-09-30 [email protected] [Impeller] Sample the render target rect in offscreen advanced blends (flutter/flutter#192988) 2026-09-30 [email protected] Roll Skia from 01524014dff5 to 6182a3cf8f98 (11 revisions) (flutter/flutter#193594) 2026-09-30 [email protected] [web] Use Xvfb -displayfd and clean up Xvfb after Firefox test suites (flutter/flutter#193510) 2026-09-30 [email protected] Fixes bug where sibling node fail to update size (flutter/flutter#191220) 2026-09-30 [email protected] Roll Skia from c7b323126bf0 to 01524014dff5 (3 revisions) (flutter/flutter#193577) 2026-09-30 [email protected] Remove some Undefined Behavior from run_customer_testing_tests.dart. (flutter/flutter#193582) 2026-09-30 [email protected] Fix pointer id/coordinate mismatch in Android platform view multi-touch (flutter/flutter#191631) 2026-09-30 [email protected] Roll Packages from 3c6ce59 to 0ba9a82 (11 revisions) (flutter/flutter#193578) 2026-09-30 [email protected] Fix unresolved doc comment references in dev/ tooling (flutter/flutter#193501) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Problem
In the offscreen advanced blend path, the subpass render target is allocated with an integral size —
ISize(subpass_coverage.GetSize()), a truncating conversion — and the quad spans that render target. But the texture coordinates are derived from the full, fractional coverage. The quad therefore displays a slightly larger region than it covers, scaling the contents bytrunc(w)/w.That factor changes whenever the coverage crosses a pixel boundary, so animated content is resampled differently every frame, which reads as shaking and crawling. The coverage does not need to be meaningfully fractional: in the per-draw path the blend runs over the whole pass (
FlipBackdrop), and floating-point residue in the transform chain is enough — instrumenting master while running the Rive artboard below, the extent truncated on 29% of blend subpasses and the origin was fractional on 34%.Only configurations without framebuffer fetch take this path — the iOS simulator, PowerVR, and Adreno on GLES. Devices with framebuffer fetch blend in-pass via
ApplyFramebufferBlend, which is exact.Fix
Derive the texture coordinates from the rect the render target actually represents, so the quad and the sampled region agree exactly. The render target size, the composite origin and the composited rect are all unchanged from master;
PipelineBlenddraws each input at its own size and transform and never had this mismatch, so it is not touched.Verification
Measured on an iPhone 17 Pro simulator against a Rive artboard exercising Multiply, Screen, Overlay, Soft Light and Color Dodge, loaded with
Factory.flutterso drawing goes through Flutter's canvas and into Impeller's blend paths. The landmark is the left edge of static artboard content that never moves in the animation, so every pixel of movement is the bug. 40 consecutive frames:lib/main.dart
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-09-18.at.14.39.40.mov
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-09-18.at.14.38.35.mov
(The recordings were made with an earlier revision of this change; the measured result for the final revision is identical.)
Golden change
This changes exactly one engine golden (of 466 Metal goldens compared locally):
ColorFilterAdvancedBlendNoFbFetch. It is a correction, not a regression. That test stacks thirty full-frame blends, so the per-frame resampling error accumulates into a visible smear of every cell edge. Cell 2's image begins at x=400 (400 px pitch, 320 px images); row y=220:The current golden bleeds orange four pixels into the empty gap before the cell and blurs the edge over ~7 px; this PR matches the exact in-pass rendering to within 1/255. The golden will need triage.
Tests
DlGoldenTest.StackedOffscreenAdvancedBlendsDoNotResamplerenders a hard backdrop edge and blends over it thirty times withkLightenon a context without framebuffer fetch, then requires the result to match a single blend. Lighten is idempotent, so the only thing that can differ is resampling error accumulated across the stacked subpasses. A 0.4x canvas scale makes the full-pass coverage fractional the same way the golden does. Fails on master (RMSE 5.75), passes with this change (RMSE 0).impeller_unittests*Blend*/*Filter*: 791 passing, 0 failing. TheColorFilterAdvancedBlendNoFbFetchgolden exercises the same path with thirty stacked blends and visibly captures the defect.Fixes #192980
Pre-launch Checklist
///).