Open Bug 1826576 Opened 2 years ago Updated 3 months ago

CSS filter() with invert() and hue-rotate() results in sluggish scrolling on Wikipedia

Categories

(Core :: Graphics: WebRender, defect)

Firefox 113
Unspecified
Android
defect

Tracking

()

Performance Impact high
Tracking Status
firefox113 --- affected

People

(Reporter: denschub, Unassigned)

References

(Blocks 3 open bugs, )

Details

(Keywords: webcompat:platform-bug)

User Story

webcompat:blocked-resources

​​### Basic information

Scrolling the Wikipedia on Fenix is slow if the native dark mode is enabled.

How to enable dark mode:

  1. Go to https://en.m.wikipedia.org
  2. Log into your account
  3. Go to settings/user preferences (Open preferences).
  4. Scroll down and access "Gadgets".
  5. Enable "dark mode toggle" from "Appearance" section.
  6. Enable "Core styling for dark mode..." from "Utility modules" section.

Performance recording (profile)

Profile URL: https://share.firefox.dev/3zx0ncq

System configuration:

OS version: Android 13
GPU model: Adreno 506
Number of cores: 4+4 (Qualcomm SDM632 Snapdragon 632)
Amount of memory (RAM): 4 GiB

More information

Instead of re-defining colors for their dark mode, Wikimedia is using a global filter. The problematic rule is

html,
html img,
html video,
html ogvjs,
html svg,
html iframe,
html .mw-no-invert,
html td .diffchange,
html .mwe-math-element,
html .wvui-typeahead-suggestion__thumbnail,
html .skin-minerva .mw-notification-visible .mw-notification-content,
html .cdx-menu-item__thumbnail,
html .cx-slitem__image,
html .mw-mmv-overlay,
html .mw-mmv-pre-image,
html .media-viewer .image img,
html .media-viewer .mw-file-description img,
html .mw-kartographer-map,
html .mw-kartographer-mapDialog-map,
html .list-thumb,
html .ext-related-articles-card-list .ext-related-articles-card-thumb {
  filter: invert(1) hue-rotate(180deg);
}

Having this filter on the entire page makes scrolling very sluggish.

This might just be a duplicate of bug 1193882 or other bugs, but since this applies nested filters to multiple elements, it could also be similar to bug 1600296 or something else, so I figured filing a new bug is a good idea.


Thanks so much for your help.

Seems like a CSS parsing issue.

The Performance Impact Calculator has determined this bug's performance impact to be high. If you'd like to request re-triage, you can reset the Performance Impact flag to "?" or needinfo the triage sheriff.

Platforms: Android
Impact on site: Causes noticeable jank
Configuration: Specific but common
Page load impact: Some
Websites affected: Major
[x] Able to reproduce locally

Performance Impact: --- → high
Component: Performance → CSS Parsing and Computation

The severity field is not set for this bug.
:jfkthame, could you have a look please?

For more information, please visit BugBot documentation.

Flags: needinfo?(jfkthame)

Bas, I'm curious why you see this as a CSS parsing issue? In the profile in comment 0, it looks to me like it's primarily the Renderer thread in the GPU process that's the issue, with its composites often taking over 20ms; that'll definitely prevent us getting anywhere near 60fps, won't it?

Flags: needinfo?(jfkthame) → needinfo?(bas)

(In reply to Jonathan Kew [:jfkthame] from comment #3)

Bas, I'm curious why you see this as a CSS parsing issue? In the profile in comment 0, it looks to me like it's primarily the Renderer thread in the GPU process that's the issue, with its composites often taking over 20ms; that'll definitely prevent us getting anywhere near 60fps, won't it?

You are correct. I missed the profile. Sorry about that! I should have realized this before. Moving components. I'm guessing the inversion filter causes WebRender to hit some kind of slow path, that probably isn't exclusive to Android but may affect it disproportionately. I'll let Jeff & Jamie comment on that.

Component: CSS Parsing and Computation → Graphics: WebRender
Flags: needinfo?(jnicol)
Flags: needinfo?(jmuizelaar)
Flags: needinfo?(bas)

Jamie's the better person to answer this.

Flags: needinfo?(jmuizelaar)

We got a report about another site doing the same, https://ponepaste.org/8805.

Blocks: 1838019

This seems very much an @ahale thing!
In particular, what do you think the severity should be? I feel like an estimated S2.8 rounds to S3, especially if you're working on it already?

Flags: needinfo?(ahale)

The profile shows some really slow weird stuff in the Renderer thread, in SwapBuffers and QuerySurface, so this feels very Jamie-targetted. :)

Flags: needinfo?(ahale)

I'm working on making all filter graphs native in WebRender https://bugzilla.mozilla.org/show_bug.cgi?id=1409486 which would avoid any possible blob fallback happening here, however this bug may be something else and I'll let Jamie assess that.

If this filter chain is not being accelerated properly in WebRender we can fix that, but it looks like something else may be going on in the profile.

As for the severity, I can imagine an argument that this is S2 because accessibility features are being used on a popular website (Wikipedia), there is a mechanistic quirk here - on desktop people who need this type of dark mode experience would tend to use an extension (and the extension may be doing this slightly differently), but on mobile it's common to use site features such as this to get a dark mode experience, so the scope of impact may be largely specific to mobile users of wikipedia. It's hard to say whether S3 or S2 is more appropriate in this case.

Severity: -- → S3

I don't think we have any blob fallback: all the worker threads are completely idle.

The reporter had an Adreno 506 which is on the less-powerful end of the spectrum. I can reproduce on Samsung A51 (Mali G72), but not a Pixel 5 (Adreno 620) or Pixel 7 (Mali G710). So this isn't specific to a model of GPU, but rather occurs on less powerful devices. On the more powerful devices I do see the frame time increase by a couple of milliseconds, but we're still comfortably in budget.

The provided profile is consistent with being GPU or perhaps more specifically memory bandwidth bound. Given the page is quite simple I would lean towards the latter. Here is a profile on the A51 which is much more concentrated on glClear() clearing picture cache targets, which would validate that theory.

Enabling dark mode on both wikipedia and ponepaste.org results in picture caching no longer working. Instead of a single scrollable slice we appear to get 2 non-scrolling slices, meaning the entire page is invalidated when we scroll. On less powerful devices we cannot afford to do that. This is also the case on desktop. The question for those who understand picture caching slightly better is why we fail to create a scrollable slice, and can we fix that?

Flags: needinfo?(jnicol)

The severity field for this bug is set to S3. However, the Performance Impact field flags this bug as having a high impact on the performance.
:gw, could you consider increasing the severity of this performance-impacting bug? Alternatively, if you think the performance impact is lower than previously assessed, could you request a re-triage from the performance team by setting the Performance Impact flag to ??

For more information, please visit BugBot documentation.

Flags: needinfo?(gwatson)

The picture-cache issue sounds quite similar to bug 1836063 where CSS filters (opacity in that case) cause unnecessary picture-cache invalidation and slow scrolling with weak GPUs. A fix for that should be landing imminently

Excellent, hopefully it's the same issue! Thanks!

FWIW I don't think the performance impact flag here is accurate. Wikipedia is a major website but it only occurs if the user is signed in and enables dark mode. But I'll wait and see if bug 1836063 fixes this before requesting retriage.

I can replicate this issue on Raspberry Pi 4. However, I've just tested and sadly my fix for 1836063 doesn't resolve this.

Flags: needinfo?(gwatson)

Is this possibly the same cause as the abominable performance cost of the Dark Reader extension on desktop?

Blocks: perf-android

I did a bit of investigation in to this. Here's a simple testcase: https://codepen.io/jamienicol/pen/gOyvopr

To render this webrender first renders the text in to a render target, then renders that with a filter in to another render target, then renders that (I assume with the second filter) in to the picture cache tiles. The key thing is that as the filter is on the root html node, the spatial node used for these offscreen pictures is the root reference frame rather than the scroll frame. Meaning when we build the tile cache we see a prim list containing a single picture with the root reference frame as its spatial node, and therefore choose the root reference frame as the scroll root.

Here's an alternative implementation that attaches the filter to the body node: https://codepen.io/jamienicol/pen/QWPmZwx . In this case the offscreen pictures have the scroll frame as their spatial node, meaning we choose that as the scroll root when building the tile cache.

In terms of the display list, the broken case (filter on html root) looks like this:

  PushStackingContext(PushStackingContextDisplayItem { origin: (0.0, 0.0), spatial_id: SpatialId(1, PipelineId(1, 3)), prim_flags: IS_BACKFACE_VISIBLE, stacking_context: StackingContext { transform_style: Flat, mix_blend_mode: Normal, clip_chain_id: None, raster_space: Screen, flags: 0x0 } })
  SolidColor p=0x7f5a45b42600 f=0x7f5a461d9020(Viewport(-1)) key=50 bounds(0,0,61440,40440) componentAlpha(0,0,0,0) clip() asr() clipChain() uniform  (opaque 0,0,61440,40440) reuse-state(None) (rgba 255,255,255,255)
    Rectangle(RectangleDisplayItem { common: CommonItemProperties { clip_rect: Box2D((0.0, 0.0), (1024.0, 674.0)), clip_chain_id: ClipChainId(18446744073709551615, PipelineId(4294967295, 4294967295)), spatial_id: SpatialId(1, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE | CHECKERBOARD_BACKGROUND }, bounds: Box2D((0.0, 0.0), (1024.0, 674.0)), color: Value(ColorF { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }) })
  CompositorHitTestInfo p=0x7f5a45b3d020 f=0x7f5a461d9198(HTMLScroll(html)(-1)) key=22 bounds(0,0,0,0) componentAlpha(0,0,0,0) clip() asr() clipChain()  hitTestInfo(0x1) hitTestArea(0,0,61440,40440) reuse-state(None)
    HitTest(HitTestDisplayItem { rect: Box2D((0.0, 0.0), (1024.0, 674.0)), clip_chain_id: ClipChainId(18446744073709551615, PipelineId(4294967295, 4294967295)), spatial_id: SpatialId(1, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE, tag: (0, 1) })
  AsyncZoom p=0x7f5a45b41d08 f=0x7f5a461d9198(HTMLScroll(html)(-1)) key=2 bounds(0,0,61440,40440) componentAlpha(420,900,60559,107520) clip(0,0,61440,40440) asr() clipChain(0x7f5a45b3d170 <0,0,61440,40440> [root asr])  reuse-state(None) (flags 0x0) (scrolltarget 0)
    RectClip(RectClipDisplayItem { id: ClipId(1, PipelineId(1, 3)), spatial_id: SpatialId(1, PipelineId(1, 3)), clip_rect: Box2D((0.0, 0.0), (1024.0, 674.0)) })
    ClipChain(ClipChainItem { id: ClipChainId(0, PipelineId(1, 3)), parent: None })
    PushReferenceFrame(ReferenceFrameDisplayListItem)
    PushStackingContext(PushStackingContextDisplayItem { origin: (0.0, 0.0), spatial_id: SpatialId(2, PipelineId(1, 3)), prim_flags: IS_BACKFACE_VISIBLE, stacking_context: StackingContext { transform_style: Flat, mix_blend_mode: Normal, clip_chain_id: Some(ClipChainId(0, PipelineId(1, 3))), raster_space: Screen, flags: 0x0 } })
    Filter p=0x7f5a45b41b98 f=0x7f5a461d9198(HTMLScroll(html)(-1)) key=27 bounds(0,0,61440,40440) componentAlpha(420,900,60559,107520) clip() asr() clipChain()  reuse-state(None) effects=(filter)
      RectClip(RectClipDisplayItem { id: ClipId(2, PipelineId(1, 3)), spatial_id: SpatialId(2, PipelineId(1, 3)), clip_rect: Box2D((0.0, 0.0), (1024.0, 674.0)) })
      ClipChain(ClipChainItem { id: ClipChainId(1, PipelineId(1, 3)), parent: None })
      ClipChain(ClipChainItem { id: ClipChainId(2, PipelineId(1, 3)), parent: None })
      SetFilterOps
      PushStackingContext(PushStackingContextDisplayItem { origin: (0.0, 0.0), spatial_id: SpatialId(2, PipelineId(1, 3)), prim_flags: IS_BACKFACE_VISIBLE, stacking_context: StackingContext { transform_style: Flat, mix_blend_mode: Normal, clip_chain_id: None, raster_space: Screen, flags: 0x0 } })
      CompositorHitTestInfo p=0x7f5a45b3d0d0 f=0x7f5a461d90c8(Canvas(html)(-1)) key=278 bounds(0,0,0,0) componentAlpha(0,0,0,0) clip() asr(<0x7f5a461d9238>) clipChain(0x7f5a45b3d170 <0,0,61440,40440> [root asr])  hitTestInfo(0x1) hitTestArea(0,0,61440,175680) reuse-state(None)
        ClipChain(ClipChainItem { id: ClipChainId(3, PipelineId(1, 3)), parent: None })
        HitTest(HitTestDisplayItem { rect: Box2D((0.0, 0.0), (1024.0, 2928.0)), clip_chain_id: ClipChainId(3, PipelineId(1, 3)), spatial_id: SpatialId(3, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE, tag: (2, 1) })
      CanvasBackgroundColor p=0x7f5a45b3d1c8 f=0x7f5a461d90c8(Canvas(html)(-1)) key=14 bounds(0,0,61440,175680) componentAlpha(0,0,0,0) clip(0,0,61440,107520) asr(<0x7f5a461d9238>) clipChain(0x7f5a45b3d268 <0,0,61440,107520> [0x7f5a461d9238], 0x7f5a45b3d170 <0,0,61440,40440> [root asr]) uniform  (opaque 0,0,61440,175680) reuse-state(None) (rgba 255,255,255,255)
        ClipChain(ClipChainItem { id: ClipChainId(4, PipelineId(1, 3)), parent: None })
        Rectangle(RectangleDisplayItem { common: CommonItemProperties { clip_rect: Box2D((0.0, 0.0), (1024.0, 1792.0)), clip_chain_id: ClipChainId(4, PipelineId(1, 3)), spatial_id: SpatialId(3, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE }, bounds: Box2D((0.0, 0.0), (1024.0, 2928.0)), color: Value(ColorF { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }) })
      ... Lots of hit test and text items

And with the filter on the body:

  PushStackingContext(PushStackingContextDisplayItem { origin: (0.0, 0.0), spatial_id: SpatialId(1, PipelineId(1, 3)), prim_flags: IS_BACKFACE_VISIBLE, stacking_context: StackingContext { transform_style: Flat, mix_blend_mode: Normal, clip_chain_id: None, raster_space: Screen, flags: 0x0 } })
  SolidColor p=0x7fe2460450f8 f=0x7fe2467d9020(Viewport(-1)) key=50 bounds(0,0,61440,40440) componentAlpha(0,0,0,0) clip() asr() clipChain() uniform  (opaque 0,0,61440,40440) reuse-state(None) (rgba 0,0,0,255)
    Rectangle(RectangleDisplayItem { common: CommonItemProperties { clip_rect: Box2D((0.0, 0.0), (1024.0, 674.0)), clip_chain_id: ClipChainId(18446744073709551615, PipelineId(4294967295, 4294967295)), spatial_id: SpatialId(1, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE | CHECKERBOARD_BACKGROUND }, bounds: Box2D((0.0, 0.0), (1024.0, 674.0)), color: Value(ColorF { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }) })
  CompositorHitTestInfo p=0x7fe246043930 f=0x7fe2467d9198(HTMLScroll(html)(-1)) key=22 bounds(0,0,0,0) componentAlpha(0,0,0,0) clip() asr() clipChain()  hitTestInfo(0x1) hitTestArea(0,0,61440,40440) reuse-state(None)
    HitTest(HitTestDisplayItem { rect: Box2D((0.0, 0.0), (1024.0, 674.0)), clip_chain_id: ClipChainId(18446744073709551615, PipelineId(4294967295, 4294967295)), spatial_id: SpatialId(1, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE, tag: (0, 1) })
  AsyncZoom p=0x7fe246043d18 f=0x7fe2467d9198(HTMLScroll(html)(-1)) key=2 bounds(0,0,61440,40440) componentAlpha(420,900,60559,107520) clip(0,0,61440,40440) asr() clipChain(0x7fe24603f170 <0,0,61440,40440> [root asr])  (opaque 0,0,61440,175680) reuse-state(None) (flags 0x0) (scrolltarget 0)
    RectClip(RectClipDisplayItem { id: ClipId(1, PipelineId(1, 3)), spatial_id: SpatialId(1, PipelineId(1, 3)), clip_rect: Box2D((0.0, 0.0), (1024.0, 674.0)) })
    ClipChain(ClipChainItem { id: ClipChainId(0, PipelineId(1, 3)), parent: None })
    PushReferenceFrame(ReferenceFrameDisplayListItem)
    PushStackingContext(PushStackingContextDisplayItem { origin: (0.0, 0.0), spatial_id: SpatialId(2, PipelineId(1, 3)), prim_flags: IS_BACKFACE_VISIBLE, stacking_context: StackingContext { transform_style: Flat, mix_blend_mode: Normal, clip_chain_id: Some(ClipChainId(0, PipelineId(1, 3))), raster_space: Screen, flags: 0x0 } })
    CompositorHitTestInfo p=0x7fe246043520 f=0x7fe2467d90c8(Canvas(html)(-1)) key=278 bounds(0,0,0,0) componentAlpha(0,0,0,0) clip() asr(<0x7fe2467d9238>) clipChain(0x7fe24603f170 <0,0,61440,40440> [root asr])  hitTestInfo(0x1) hitTestArea(0,0,61440,175680) reuse-state(None)
      RectClip(RectClipDisplayItem { id: ClipId(2, PipelineId(1, 3)), spatial_id: SpatialId(2, PipelineId(1, 3)), clip_rect: Box2D((0.0, 0.0), (1024.0, 674.0)) })
      ClipChain(ClipChainItem { id: ClipChainId(1, PipelineId(1, 3)), parent: None })
      ClipChain(ClipChainItem { id: ClipChainId(2, PipelineId(1, 3)), parent: None })
      ClipChain(ClipChainItem { id: ClipChainId(3, PipelineId(1, 3)), parent: None })
      HitTest(HitTestDisplayItem { rect: Box2D((0.0, 0.0), (1024.0, 2928.0)), clip_chain_id: ClipChainId(3, PipelineId(1, 3)), spatial_id: SpatialId(3, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE, tag: (2, 1) })
    CanvasBackgroundColor p=0x7fe24603f1c8 f=0x7fe2467d90c8(Canvas(html)(-1)) key=14 bounds(0,0,61440,175680) componentAlpha(0,0,0,0) clip(0,0,61440,107520) asr(<0x7fe2467d9238>) clipChain(0x7fe24603f268 <0,0,61440,107520> [0x7fe2467d9238], 0x7fe24603f170 <0,0,61440,40440> [root asr]) uniform  (opaque 0,0,61440,175680) reuse-state(None) (rgba 0,0,0,255)
      ClipChain(ClipChainItem { id: ClipChainId(4, PipelineId(1, 3)), parent: None })
      Rectangle(RectangleDisplayItem { common: CommonItemProperties { clip_rect: Box2D((0.0, 0.0), (1024.0, 1792.0)), clip_chain_id: ClipChainId(4, PipelineId(1, 3)), spatial_id: SpatialId(3, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE }, bounds: Box2D((0.0, 0.0), (1024.0, 2928.0)), color: Value(ColorF { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }) })
    CompositorHitTestInfo p=0x7fe246043110 f=0x7fe2467d9958(Block(html)(-1)) key=22 bounds(0,0,0,0) componentAlpha(0,0,0,0) clip(0,0,61440,107520) asr(<0x7fe2467d9238>) clipChain(0x7fe24603f268 <0,0,61440,107520> [0x7fe2467d9238], 0x7fe24603f170 <0,0,61440,40440> [root asr])  hitTestInfo(0x1) hitTestArea(0,0,61440,175680) reuse-state(None)
      HitTest(HitTestDisplayItem { rect: Box2D((0.0, 0.0), (1024.0, 1792.0)), clip_chain_id: ClipChainId(4, PipelineId(1, 3)), spatial_id: SpatialId(3, PipelineId(1, 3)), flags: IS_BACKFACE_VISIBLE, tag: (2, 1) })
    Filter p=0x7fe246043ab8 f=0x7fe2467d9a20(Block(body)(2)) key=27 bounds(480,960,60480,173760) componentAlpha(420,900,60559,107520) clip(0,0,61440,107520) asr(<0x7fe2467d9238>) clipChain(0x7fe24603f268 <0,0,61440,107520> [0x7fe2467d9238], 0x7fe24603f170 <0,0,61440,40440> [root asr])  reuse-state(None) effects=(filter)
      RectClip(RectClipDisplayItem { id: ClipId(3, PipelineId(1, 3)), spatial_id: SpatialId(3, PipelineId(1, 3)), clip_rect: Box2D((0.0, 0.0), (1024.0, 1792.0)) })
      ClipChain(ClipChainItem { id: ClipChainId(5, PipelineId(1, 3)), parent: None })
      SetFilterOps
      PushStackingContext(PushStackingContextDisplayItem { origin: (0.0, 0.0), spatial_id: SpatialId(3, PipelineId(1, 3)), prim_flags: IS_BACKFACE_VISIBLE, stacking_context: StackingContext { transform_style: Flat, mix_blend_mode: Normal, clip_chain_id: Some(ClipChainId(5, PipelineId(1, 3))), raster_space: Screen, flags: 0x0 } })
      ... Lots of hit test and text items

Markus, Glenn, can you think of anything we can do from the gecko or webrender end to help with this situation? Could we in some way detect when all of the filtered items have a certain spatial node and do better? In the broken case here everything that is filtered has the same spatial node, except for the CanvasBackgroundColor, but perhaps we can handle that in the simple case (ie solid color)

Sorry forgot to needinfo. See above comment

Flags: needinfo?(mstange.moz)
Flags: needinfo?(gwatson)

Yes, I think we should be able to handle the simple (common) case you mention, where the fixed content is a known solid color.

There are ways to produce content where you do need to rasterize every scroll (e.g. if the fixed content is a gradient or image), but they are rare compared to the fixed color case.

I'll need to ponder a bit the simplest way to detect this and how we would implement it, but it seems like a reasonable optimization to have that would handle most common cases where this occurs.

Flags: needinfo?(gwatson)

The severity field for this bug is set to S3. However, the Performance Impact field flags this bug as having a HIGH impact on performance.
:gw, could you consider increasing the severity of this performance-impacting bug? Alternatively, if you think the performance impact is lower than previously assessed, could you request a re-triage from the performance team by setting the Performance Impact flag to ? ?

If this bug is considered an S3 despite the perf impact, can you please provide rationale? Thank you!

Flags: needinfo?(gwatson)
Performance Impact: high → ?
Flags: needinfo?(gwatson)

Hey Dennis, can you see if this is still happening for you? I don't reproduce on my Android.
Can you especially check if Wikipedia changed their CSS so that they don't use this property anymore?
Thanks!

Performance Impact: ? → pending-needinfo
Flags: needinfo?(dschubert)
Flags: needinfo?(mstange.moz)

My experience with this
I recently realized that I’ve been living with this bug on both my laptop and computer for years. Since I have powerful machines, the performance drop wasn’t very noticeable, I just assumed that CSS processing in Firefox was slightly slower than in Chrome.
However, when OpenStreetMap implemented a dark mode with complex filters applied to map tiles, I finally noticed something was wrong.

The easiest way to test if you have this bug is to visit https://issviewer.com/index.html which uses this filter in dark mode: brightness(0.6) invert(1) contrast(3) hue-rotate(200deg) saturate(0.3) brightness(0.7)
Try moving the map really fast in light mode and dark mode, for me dark mode was extremely laggy, running at around 10 FPS, while the light mode was perfectly smooth at 60 FPS.

I managed to fix my performance issue by disabling style customization with userChrome.css (which I use to change bookmark icon in the toolbar) in about:config toolkit.legacyUserProfileCustomizations.stylesheets, then restart Firefox, and the best part is that I can re-enable it, restart, and now everything works perfectly!

Interestingly, I never had to do this on my laptop, style customization and hardware acceleration were always enabled and working, it seems the issue fixed itself in a recent update but not on my computer.

Now I also notice performance improvement on x.com, it wasn't bad enough before to suspect anything was wrong, scrolling was okay around 30 FPS, I thought it was because of all the videos in the timeline, but now it's super smooth at 60 FPS.

Performance Impact: pending-needinfo → ?

(In reply to Julien Wajsberg [:julienw] from comment #20)

Hey Dennis, can you see if this is still happening for you? I don't reproduce on my Android.
Can you especially check if Wikipedia changed their CSS so that they don't use this property anymore?
Thanks!

They still use this in various palces. One example, here,

@media screen {
  html.skin-theme-clientpref-night .branding-box img {
    filter:invert(1) hue-rotate(180deg)
  }
}

I no longer have the original device I filed this on. It's fine on my new device, but that's significantly ore powerful. Given comment 21, I assume this is still an issue that's affecting some users.

Flags: needinfo?(dschubert)
User Story: (updated)
User Story: (updated)

Consider raising the priority of this for webcompat, because it has the potential to affect a significant number of users, specifically:

  • Accessibility addons that create high-contrast views may use this functionality (or similar that will also be affected by this bug).
  • Some dark-mode functionality is implemented using this functionality, which is likely to see increased usage in future.
Severity: S3 → --

FWIW this at least no longer affects Wikipedia's dark mode.

This was fixed by the SVGFE filter code path when using hardware acceleration. I'm not sure what the situation is for making this fast in software rendering.

Hardware accelerated SVGFE presumably made rendering the filter faster, but I don't expect it fixed the picture caching limitation I described in comment 16? (Or could it have?)

User Story: (updated)

The title of this bug is saying it is sluggish, it is no longer sluggish due to native SVGFE graph rendering in WebRender, instead we have a resolution problem on high res displays, so in one sense this bug is fixed, however the discussion focuses on resolution degradation.

On my Windows desktop the test case in comment #16 shows the resolution degrades when the window is made wider until I select text, then it gets sharp again for the affected text (but not the parts that were not selected), I'm not entirely sure what is going on there but I am more inclined to say it is an invalidation problem than anything else at a glance.

The SVGFE graph rendering code is entirely agnostic to the size of invalidation rects and correctly calculates minimal invalidation rects based on the provided one, so it doesn't automatically force the entire source picture to be full size, this means it reacts nicely to invocation on a per-PictureCacheTile basis, since it does not assume the entire picture is relevant, which conversely means it isn't tied to the idea of caching pictures between tile renders, and I suspect that's related to why invalidation looks this way in this test case, but I wouldn't assume that using the entire source picture would improve things because that is way more likely to run into this resolution degradation everywhere on the web.

The easiest fix for the resolution degradation (in this and a handful of other situations) is to make the maximum RenderTask size larger based on display information, but obviously it would still hit bad cases somewhere.

It's not sluggish on higher end GPUs, but it's still slow on lower end devices. Even with the SVG filter being applied in hardware, the main issue is the picture caching invalidation mentioned by Jamie in comment 27. There are a few potential options on how we can make this better, I will prototype a couple of them to see which works best.

See Also: → 1976674

I have spent a few days investigating this and considering a couple of options we have to solve this.

One option would be to add support during compositing of picture cache tiles to handle a limited subset of filters. That would work in this specific case, but wouldn't handle all similar cases. It's also significant complexity to implement given the number of compositor implementations we currently have (Draw/Layer, DirectComposition, CoreAnimation, SWGL, SWGL + D3D11, SWGL + OGL).

The preferred option is to add support for tiling and caching of child pictures. This also has significant complexity to implement, however it comes with a number of other advantages:

  • Will be able to handle the general case of scroll roots within a filter chain (e.g. for more complex filters such as blurs and drop-shadows) efficiently.
  • Will work with cases that wouldn't be handled by the compositing option (e.g. complex transforms + filters on child surfaces).
  • Will also fix some other performance bugs we currently have (e.g. complex filter chains on nested child pictures).
  • Will either improve, or unlock the ability in future to improve, performance of our current backdrop-filter implementation.
  • Speculative - Make it easier in future to support unconditional surface promotion (e.g. when we encounter a HDR or DRM video that has a filter, complex mask etc applied to it).
  • Speculative - I think this will improve performance in general of our invalidation logic, which affects all pages.

Given those other advantages, it seems worth the time to implement caching of child pictures. My best current estimate for implementing all of the above is 3-6 months work to get it shipped. I'm hoping it would be closer to 3 months, but there are some unknowns that may push this work out. We'll have a much better idea of this once we spend an initial 2-4 weeks on it.

User Story: (updated)
User Story: (updated)
User Story: (updated)

The Performance Impact Calculator has determined this bug's performance impact to be high. If you'd like to request re-triage, you can reset the Performance Impact flag to "?" or needinfo the triage sheriff.

Platforms: Android
Impact on browser: Causes noticeable jank
Impact on site: Causes noticeable jank
Configuration: Specific but common
Websites affected: Major

Performance Impact: ? → high

The severity field is not set for this bug.
:gw, could you have a look please?

For more information, please visit BugBot documentation.

Flags: needinfo?(mozilla)
Severity: -- → S2
Flags: needinfo?(mozilla)
Severity: S2 → S3
You need to log in before you can comment on or make changes to this bug.