[web] RenderParagraph needs paint after a DPR change#186968
Conversation
There was a problem hiding this comment.
Code Review
This pull request adds a devicePixelRatio property to RenderParagraph and updates RichText to propagate this value from MediaQuery. On the web, changes to devicePixelRatio trigger a repaint to prevent blurry text. Review feedback recommends adding View.maybeOf(context)?.devicePixelRatio as a fallback when MediaQuery is unavailable, and reverting an awkward test name modification in table_test.dart.
| if (kIsWeb) { | ||
| // The `WebParagraph` implementation renders the paragraph as an image. After a | ||
| // `devicePixelRatio` change, the image needs to be regenerated or it would look blurry. | ||
| markNeedsPaint(); |
There was a problem hiding this comment.
The device pixel ratio isn't passed into lower level. How does the painter get the correct dpr, as the application can override that in the tree?
EDIT: oh nvm. It looks like overriding devicePixelRatio doesn't do anything since the scaling transform is applied by RenderView.
There was a problem hiding this comment.
So the reason we're doing this is because webparagraph rasterizes the paragraph in the paint call?
There was a problem hiding this comment.
That's right. WebParagraph generates an image during the paint call. When the DPR changes, we need to regenerate the image using the new DPR, otherwise it will look blurry. The problem I was facing is that the framework sometimes skips repainting the paragraph even when DPR has changed. That made the text look bad.
The solution I came up with was to call markNeedsPaint() every time the DPR changes. If there's a better way to do it, I'm open to suggestions.
harryterkelsen
left a comment
There was a problem hiding this comment.
What do we do if there is no MediaQuery ancestor?
There was a problem hiding this comment.
Hmm TBH not a big fan of triggering a repaint based on DPR (and the web-only markNeedsRepaint). What if I have a paragraph inside of a Transform widget (something like InteractiveViewer)? When I zoom in on the text would it appear blurry due to the pre-rasterization? Is it possible to do this based on the scale value of the CTM?
| final Color? selectionColor; | ||
|
|
||
| double _getDevicePixelRatio(BuildContext context) => | ||
| MediaQuery.maybeDevicePixelRatioOf(context) ?? View.maybeOf(context)?.devicePixelRatio ?? 1.0; |
There was a problem hiding this comment.
nit: use a fallback value that's outside of valid DPR range? So we can distinguish "no View" vs "has View ancestor but its dpr is exactly 1.0". But I thought it was safe to assume there's always a View ancestor in an active widget subtree?
There was a problem hiding this comment.
Also View.maybeOf probably doesn't notify listeners on DPR change. It is only for monitoring the identity of the View: https://main-api.flutter.dev/flutter/widgets/View/of.html
There was a problem hiding this comment.
nit: use a fallback value that's outside of valid DPR range? So we can distinguish "no View" vs "has View ancestor but its dpr is exactly 1.0".
Why do we need to distinguish between "no View" and "View has dpr 1.0"? I don't think we care about this distinction.
But I thought it was safe to assume there's always a View ancestor in an active widget subtree?
I wasn't sure about this, so I opted to play it safe and assume that View could be null. If there's always a View, then shouldn't we remove the View.maybeOf method?
Also
View.maybeOfprobably doesn't notify listeners on DPR change.
That's right. There's no way to listen to View.dpr changes. That said, in regular apps a MediaQuery should be present and we do listen to DPR changes on it. That should cover the majority of cases. Using View.maybeOf(context).dpr is only a fallback that shouldn't happen often.
You're right, the paragraph will look blurry if it's scaled by a
Fixing the DPR case is more straightforward and more valuable because it affects all apps. |
At the time you asked this question, the answer would've been: We default to Now the answer is: We try to get the DPR from the nearest |
I think this is doable using a composition callback (although there is going to be an one-frame delay if the text must be rasterized in a paint call, in which case the markNeedsPaint must happen in a post frame callback), example: flutter/packages/flutter/lib/src/widgets/editable_text.dart Lines 147 to 172 in 4578896
I would think the text needs to be re-rendered when the CTM changes on non-web platforms too? If that's the case is the problem that we usually do rasterization on the raster thread so it doesn't block UI events (does the web engine have a dedicated raster thread?) but for Oh according to gemini the glyphs will be cached (using the physical size as one of the cache keys).
That makes sense but I think (On a different note is there a place that I can read more about how WebParagraph works?) |
|
Should we do something here that is similar to how the native engine handles its raster cache? When the native engine paints a picture in a scene, it may cache the rasterized picture so it doesn't need to re-rasterize it if it hasn't changed. But when it caches the rastered picture, it stores the current transform matrix. That way it can invalidate its cache if the transform matrix changes (with, IIRC, some checks to make sure that it didn't just do a translation). Could we do something similar with the cached WebParagraph rasters? |
LongCatIsLooong
left a comment
There was a problem hiding this comment.
In-app transforms will be addressed separately.
|
@LongCatIsLooong @harryterkelsen thank you both for bringing up good points! I created an issue for better CMT handling: #188025 |
|
autosubmit label was removed for flutter/flutter/186968, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR. |
flutter/flutter@5827d5f...3a0420c 2026-06-16 Rusino@users.noreply.github.com Implement font fallback (flutter/flutter#187520) 2026-06-16 amhurtado@protonmail.com Add FlatBuffers Verifier checks to Impeller asset loading (flutter/flutter#187878) 2026-06-16 engine-flutter-autoroll@skia.org Roll Packages from aa964a3 to 8286d39 (1 revision) (flutter/flutter#188067) 2026-06-16 engine-flutter-autoroll@skia.org Roll Skia from 9c2b83788409 to d7196b0b4939 (1 revision) (flutter/flutter#188066) 2026-06-16 engine-flutter-autoroll@skia.org Roll Skia from ef17057bb776 to 9c2b83788409 (1 revision) (flutter/flutter#188061) 2026-06-16 engine-flutter-autoroll@skia.org Roll Skia from 500025456bb5 to ef17057bb776 (1 revision) (flutter/flutter#188058) 2026-06-16 engine-flutter-autoroll@skia.org Roll Skia from cb1035ff14bf to 500025456bb5 (5 revisions) (flutter/flutter#188057) 2026-06-16 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from TbB86Po_HDe1dvXvT... to VeLhhlDcod09NR4Hb... (flutter/flutter#188055) 2026-06-16 engine-flutter-autoroll@skia.org Roll Skia from 70acf6a5e7c9 to cb1035ff14bf (3 revisions) (flutter/flutter#188054) 2026-06-16 41930132+hellohuanlin@users.noreply.github.com [pv]skip non-tappable web view workaround on ios 26.4 (flutter/flutter#185424) 2026-06-16 mdebbar@google.com [web] RenderParagraph needs paint after a DPR change (flutter/flutter#186968) 2026-06-16 30870216+gaaclarke@users.noreply.github.com Adds gamma correction to windows text. (flutter/flutter#187871) 2026-06-15 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Add a platform view test to android_hardware_smoke_test (#187913)" (flutter/flutter#188051) 2026-06-15 awolff@google.com Add a platform view test to android_hardware_smoke_test (flutter/flutter#187913) 2026-06-15 codefu@google.com feat: linux_analyze in a workflow (flutter/flutter#187889) 2026-06-15 mdebbar@google.com [web] Changes to WebParagraph configuration (flutter/flutter#187188) 2026-06-15 matt.boetger@gmail.com Fail gracefully on Android AVD lock errors during startup (flutter/flutter#187200) 2026-06-15 bkonyi@google.com [flutter_tools] Fix flakiness in widget_preview_detection_test (flutter/flutter#187938) 2026-06-15 jason-simmons@users.noreply.github.com Exclude fuchsia-sdk/sdk/.build-id from the builder cache archive (flutter/flutter#187826) 2026-06-15 engine-flutter-autoroll@skia.org Roll Skia from c8d9f80f13e4 to 70acf6a5e7c9 (4 revisions) (flutter/flutter#188020) 2026-06-15 engine-flutter-autoroll@skia.org Roll Packages from b78ad83 to aa964a3 (7 revisions) (flutter/flutter#188021) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC bmparr@google.com,stuartmorgan@google.com 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
Currently, there are situation where a `ui.Paragraph` is not repainted, for example when it's inside a `RepaintBoundary`. When `devicePixelRatio` changes due to zooming in or out, and the paragraph isn't repainted, it results in `WebParagraph`s becoming blurry/choppy because they are painted as images. The image needs to be regenerated for the new `devicePixelRatio` in order to become clear. This PR adds the `devicePixelRatio` property to `RenderParagraph` and, on the web, a change in this property causes the paragraph to be repainted. The cost of this PR on performance should be minimal because: 1. It's limited to web. 2. It only costs an extra paint when the dpr changes, which is not a common case (on the web, it happens when zooming in/out or when the window is moved to a different display). Part of flutter#172561
Currently, there are situation where a
ui.Paragraphis not repainted, for example when it's inside aRepaintBoundary. WhendevicePixelRatiochanges due to zooming in or out, and the paragraph isn't repainted, it results inWebParagraphs becoming blurry/choppy because they are painted as images.The image needs to be regenerated for the new
devicePixelRatioin order to become clear.This PR adds the
devicePixelRatioproperty toRenderParagraphand, on the web, a change in this property causes the paragraph to be repainted.The cost of this PR on performance should be minimal because:
Part of #172561