Skip to content

[skwasm][canvaskit] Honor Paint.filterQuality in Canvas.drawAtlas#186108

Merged
auto-submit[bot] merged 9 commits into
flutter:masterfrom
Ortes:skwasm-drawatlas-filterquality
Jul 10, 2026
Merged

[skwasm][canvaskit] Honor Paint.filterQuality in Canvas.drawAtlas#186108
auto-submit[bot] merged 9 commits into
flutter:masterfrom
Ortes:skwasm-drawatlas-filterquality

Conversation

@Ortes

@Ortes Ortes commented May 6, 2026

Copy link
Copy Markdown
Contributor

Description

Skwasm's Canvas.drawAtlas previously hardcoded SamplingOptionsForQuality(FilterQuality::medium) regardless of Paint.filterQuality, so pixel-art tilemaps (e.g. flame_tiled atlases at FilterQuality.none) rendered blurry on web/skwasm while every other backend correctly honored the paint.

Use case / why this matters

Canvas.drawAtlas exists to batch many sprite quads sourced from a single atlas image into ~one GPU draw call (one vertex buffer, one texture bind, one shader pipeline). For a Flame + Tiled pixel-art game, a typical 64x64 tilemap forwards ~4 096 sprites/frame: rendering them via drawImageRect would mean 4 096 draw calls + state changes/frame, while drawAtlas collapses that to roughly one. That's the path flame_tiled takes for tilemap rendering, and it's the canonical reason an app reaches for this API.

Paint.filterQuality is a no-op for most callers (the default linear/mipmap sampling is what they want), but pixel art is the explicit exception: low-resolution art scaled by non-integer camera factors needs FilterQuality.none (nearest-neighbor) to keep hard pixel edges. Native iOS/Android already honor this; on web/skwasm the hardcoded medium was overriding it and softening every tile.

Implementation

The native iOS/Android path was already correct: lib/ui/painting/canvas.cc:Canvas::drawAtlas translates paint.filterQuality -> DlImageSampling via ImageFilter::SamplingFromIndex and threads it through DisplayListBuilder::DrawAtlas (which carries DlImageSampling in DrawAtlasBaseOp). Both display_list/skia/dl_sk_canvas.cc and Impeller's dl_dispatcher.cc honor it. Skwasm was the only engine that ignored the paint.

This PR threads paint.filterQuality through skwasm's existing FFI in the same shape that canvas_drawImage and canvas_drawImageRect already use:

  • skwasm/canvas.cc:canvas_drawAtlas now takes Skwasm::FilterQuality quality and passes SamplingOptionsForQuality(quality) to DrawAtlas instead of the hardcoded medium.
  • skwasm_impl/raw/raw_canvas.dart:canvasDrawAtlas FFI gains an int filterQuality slot in the trailing position, matching canvasDrawImage/canvasDrawImageRect.
  • skwasm_impl/canvas.dart:drawAtlas and drawRawAtlas extract paint.filterQuality.index and pass it through.
  • skwasm/helpers.h adds static_asserts pinning Skwasm::FilterQuality's integer values to dart:ui's FilterQuality.index ordering, so any future drift in either enum becomes a compile error rather than a silently wrong sampling mode.

Two golden tests in lib/web_ui/test/ui/draw_atlas_golden_test.dart lock the behavior in:

  • A matrix test over all four FilterQuality values, drawing a 2x2 checker at a non-integer scale chosen so bilinear sampling emits one mathematically perfect 50/50 gray pixel that nearest-neighbour cannot produce — a strong discriminator between the modes.
  • A null-colors regression test (see below) covering the second fix in this PR.

A second commit on this branch addresses a pre-existing null-colors deref in canvas_drawAtlas that was flagged in review (the Dart wrapper passes nullptr when the caller's colors list is null; the C++ side was looping colors[i] regardless and was only saved from crashing by WASM reads at address 0 returning zeros). Kept in this PR rather than split out — it sits in the same function as the filterQuality fix and the C++ change is small enough that bundling stays reviewable.

CanvasKit

CanvasKit is now fixed the same way (the earlier "out of scope" reasoning was wrong: CanvasKit's Canvas.drawAtlas JS binding has accepted an optional sampling argument since CanvasKit 0.25.1 (2021), dispatching internally to _drawAtlasCubic/_drawAtlasOptions — no Skia CL needed).

  • canvaskit/canvas.dart:_drawAtlas now passes toSkFilterOptions(paint.filterQuality) to SkCanvas.drawAtlas, the same FilterQuality -> sampling mapping drawImage/drawImageRect already use.
  • canvaskit_api.dart threads the CkFilterOptions sampling argument through the drawAtlas binding.

All four FilterQuality values now resolve to identical SkSamplingOptions on both renderers (including high -> Mitchell B=C=1/3), so the drawAtlas goldens converge across CanvasKit and skwasm instead of diverging per renderer.

Refs

Pre-launch Checklist

@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-web Web applications specifically labels May 6, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the Skwasm engine to respect the FilterQuality parameter in drawAtlas calls, replacing a previously hardcoded value. The changes include updates to the Dart Skwasm implementation, the C++ bindings, and the addition of regression tests. Review feedback identifies a potential null pointer dereference in the C++ implementation when the colors array is null and points out several memory leaks in the new tests where ui.Picture and ui.Image objects are not properly disposed.

Comment thread engine/src/flutter/skwasm/canvas.cc
Comment thread engine/src/flutter/lib/web_ui/test/ui/draw_atlas_golden_test.dart
Comment thread engine/src/flutter/lib/web_ui/test/ui/draw_atlas_golden_test.dart
@Ortes
Ortes marked this pull request as draft May 6, 2026 00:33
Ortes added a commit to Ortes/flutter that referenced this pull request May 6, 2026
The Dart drawAtlas/drawRawAtlas API allows colors to be null, in which
case the Skwasm Dart wrapper passes nullptr down to the FFI. The C++
side then unconditionally read colors[i] for every sprite, which was
saved from crashing only by WASM linear memory reads at address 0
returning zeros (and the resulting all-black DlColor list being
ignored when blendMode is the default).

Move the color conversion inside a null check and pass nullptr through
to DisplayListBuilder::DrawAtlas when the caller didn't supply colors,
matching the Dart contract.

Reported by gemini-code-assist on flutter#186108.

Signed-off-by: Ortes <[email protected]>
Ortes added a commit to Ortes/flutter that referenced this pull request May 6, 2026
The Dart drawAtlas/drawRawAtlas API allows colors to be null, in which
case the Skwasm Dart wrapper passes nullptr down to the FFI. The C++
side then unconditionally read colors[i] for every sprite, which was
saved from crashing only by WASM linear memory reads at address 0
returning zeros (and the resulting all-black DlColor list being
ignored when blendMode is the default).

Move the color conversion inside a null check and pass nullptr through
to DisplayListBuilder::DrawAtlas when the caller didn't supply colors,
matching the Dart contract.

Reported by gemini-code-assist on flutter#186108.

Signed-off-by: Ortes <[email protected]>
@Ortes
Ortes force-pushed the skwasm-drawatlas-filterquality branch from 603d341 to d4bb834 Compare May 6, 2026 16:31
Ortes added a commit to Ortes/flutter that referenced this pull request May 6, 2026
The Dart drawAtlas/drawRawAtlas API allows colors to be null, in which
case the Skwasm Dart wrapper passes nullptr down to the FFI. The C++
side then unconditionally read colors[i] for every sprite, which was
saved from crashing only by WASM linear memory reads at address 0
returning zeros (and the resulting all-black DlColor list being
ignored when blendMode is the default).

Move the color conversion inside a null check and pass nullptr through
to DisplayListBuilder::DrawAtlas when the caller didn't supply colors,
matching the Dart contract.

Reported by gemini-code-assist on flutter#186108.

Signed-off-by: Ortes <[email protected]>
@Ortes
Ortes marked this pull request as ready for review May 6, 2026 16:32

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modifies the skwasm drawAtlas implementation to honor the FilterQuality from the Paint object instead of using a hardcoded value. The changes include updating the FFI signatures, adding a null check for the colors array in the C++ engine, and implementing regression tests. A performance improvement was suggested for the C++ implementation to use reserve and emplace_back when populating the color vector to avoid double-initialization.

I am having trouble creating individual review comments. Click here to see my feedback.

engine/src/flutter/skwasm/canvas.cc (365-371)

medium

While the current implementation correctly fixes the null dereference, using std::vector::resize followed by a loop results in double-initialization of the DlColor elements (once by resize and once by assignment). For performance-sensitive drawing operations like drawAtlas, it is more efficient to use reserve and emplace_back or push_back to avoid unnecessary work, especially when sprite_count is large.

  std::vector<flutter::DlColor> dl_colors;
  if (colors != nullptr) {
    dl_colors.reserve(sprite_count);
    for (int i = 0; i < sprite_count; i++) {
      dl_colors.emplace_back(colors[i]);
    }
  }

@gaaclarke gaaclarke added the team-web Owned by Web platform team label May 11, 2026
@flutter-zl
flutter-zl requested a review from harryterkelsen May 13, 2026 18:13
@Ortes
Ortes force-pushed the skwasm-drawatlas-filterquality branch from d4bb834 to 2b0dd4c Compare May 14, 2026 16:23
Ortes added a commit to Ortes/flutter that referenced this pull request May 14, 2026
The Dart drawAtlas/drawRawAtlas API allows colors to be null, in which
case the Skwasm Dart wrapper passes nullptr down to the FFI. The C++
side then unconditionally read colors[i] for every sprite, which was
saved from crashing only by WASM linear memory reads at address 0
returning zeros (and the resulting all-black DlColor list being
ignored when blendMode is the default).

Move the color conversion inside a null check and pass nullptr through
to DisplayListBuilder::DrawAtlas when the caller didn't supply colors,
matching the Dart contract.

Reported by gemini-code-assist on flutter#186108.

Signed-off-by: Ortes <[email protected]>
@github-actions github-actions Bot removed the team-web Owned by Web platform team label May 14, 2026
@harryterkelsen harryterkelsen added engine flutter/engine related. See also e: labels. platform-web Web applications specifically CICD Run CI/CD and removed engine flutter/engine related. See also e: labels. platform-web Web applications specifically CICD Run CI/CD labels May 14, 2026
@fluttergithubbot

Copy link
Copy Markdown
Contributor

An existing Git SHA, 2b0dd4c4f4effb28e65b807bd3e9ef0c38532a5c, was detected, and no actions were taken.

To re-trigger presubmits after closing or re-opeing a PR, or pushing a HEAD commit (i.e. with --force) that already was pushed before, push a blank commit (git commit --allow-empty -m "Trigger Build") or rebase to continue.

@harryterkelsen harryterkelsen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a small nit. I agree with Gemini that we should dispose of the created Pictures, but since the other tests don't do that then it's fine not to in this PR. We can do a cleanup PR later since I'm sure this isn't the only file that neglects to clean up its resources.

Comment thread engine/src/flutter/lib/web_ui/test/ui/draw_atlas_golden_test.dart Outdated
@harryterkelsen harryterkelsen added CICD Run CI/CD and removed CICD Run CI/CD labels May 14, 2026
@flutter-dashboard

Copy link
Copy Markdown

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #186108 at sha 8707279

@harryterkelsen harryterkelsen added the autosubmit Merge PR when tree becomes green via auto submit App label Jun 25, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jun 25, 2026
@auto-submit

auto-submit Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/186108, because This PR has not met approval requirements for merging. The PR author is not a member of flutter-hackers and needs 1 more review(s) in order to merge this PR.

  • Merge guidelines: A PR needs at least one approved review if the author is already part of flutter-hackers or two member reviews if the author is not a member of flutter-hackers before re-applying the autosubmit label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Jul 7, 2026
@harryterkelsen harryterkelsen added the CICD Run CI/CD label Jul 7, 2026
@harryterkelsen
harryterkelsen requested a review from flutter-zl July 7, 2026 21:13
@harryterkelsen harryterkelsen added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 7, 2026
@auto-submit

auto-submit Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/186108, because - The status or check suite Linux tool_integration_tests_4_7 has failed. Please fix the issues identified (or deflake) before re-applying this label.

@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jul 7, 2026
@harryterkelsen harryterkelsen added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 7, 2026
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Jul 8, 2026
@harryterkelsen harryterkelsen added the CICD Run CI/CD label Jul 8, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jul 8, 2026
@auto-submit

auto-submit Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/186108, because - The status or check suite Linux mac_android_aot_engine has failed. Please fix the issues identified (or deflake) before re-applying this label.

@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Jul 9, 2026
@harryterkelsen harryterkelsen added CICD Run CI/CD autosubmit Merge PR when tree becomes green via auto submit App labels Jul 9, 2026
@auto-submit

auto-submit Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/186108, because - The status or check suite Linux tool_integration_tests_4_7 has failed. Please fix the issues identified (or deflake) before re-applying this label.

@auto-submit

auto-submit Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/186108, because - The status or check suite Linux web_canvaskit_tests_7_last has failed. Please fix the issues identified (or deflake) before re-applying this label.

  • The status or check suite Linux web_canvaskit_tests_1 has failed. Please fix the issues identified (or deflake) before re-applying this label.

@auto-submit

auto-submit Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/186108, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team will affect goldens Changes to golden files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants