Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web: Poor performance when using Shadows + Animation #54507

Closed
esDotDev opened this issue Apr 10, 2020 · 35 comments
Closed

Web: Poor performance when using Shadows + Animation #54507

esDotDev opened this issue Apr 10, 2020 · 35 comments
Assignees
Labels
c: performance Relates to speed or footprint issues (see "perf:" labels) customer: crowd Affects or could affect many people, though not necessarily a specific customer. customer: web10 framework flutter/packages/flutter repository. See also f: labels. platform-web Web applications specifically

Comments

@esDotDev
Copy link

esDotDev commented Apr 10, 2020

The following spike runs quite poorly on Web, in release mode on latest Master.
https://dev.gskinner.com/flutter_perf/shadows/#/

Testing with Chrome:
Laptop with a 9750H CPU: 25-30fps
LG ThinQ Android: 7fps
PC, w Ryzen3700x, with 6x CPU throttle: 30fps

It seems to be related to the shadows, if I remove the shadows it runs 60fps on Android.

class WebPerformanceSpike extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.green.shade100,
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            FpsIndicator(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
          ],
        ),
      ),
    );
  }

  Container _Box([Widget child]) {
    return Container(
        margin: EdgeInsets.only(bottom: 10),
        decoration: BoxDecoration(
          color: Colors.white,
          boxShadow: [BoxShadow(spreadRadius: 4, blurRadius: 4, color: Colors.blue)],
        ),
        alignment: Alignment.center,
        width: double.infinity,
        height: 100,
        child: CircularProgressIndicator());
  }
}

class FpsIndicator extends StatelessWidget {
  int get nowMs => DateTime.now().millisecondsSinceEpoch;

  @override
  Widget build(BuildContext context) {
    int lastFps = nowMs;
    int ticks = 0;
    int fpsValue = 60;
    return Container(
      width: 50,
      alignment: Alignment.center,
      child: StatefulBuilder(builder: (_, setState) {
        return TweenAnimationBuilder<double>(
          tween: Tween(begin: 0, end: 1),
          duration: Duration(days: 1),
          builder: (_, value, __) {
            if (nowMs - lastFps > 1000) {
              fpsValue = ticks;
              ticks = 0;
              lastFps = nowMs;
            }
            ticks++;
            return Text("FPS: $fpsValue", softWrap: false, maxLines: 1);
          },
        );
      }),
    );
  }
}
@Mravuri96
Copy link

Mravuri96 commented Apr 11, 2020

@esDotDev Has this been run with the --dart-define=FLUTTER_WEB_USE_SKIA=true flag ?

@esDotDev
Copy link
Author

esDotDev commented Apr 11, 2020

Yes, and it runs much better there, but is not rendering correctly on Android.
https://dev.gskinner.com/flutter_perf/shadowskit/

Seems like this should run ok with Vanilla Flutter though, the actual repaint region is < 10% of the screen. Is the entire view getting repainted each frame? My understanding is only the AnimatedSpinners should be re-drawing here...

What's most confusing is that on some MBP machines, we're seeing 5fps in Chrome, while others it's 60. Very similar machines.

@asafyish
Copy link

asafyish commented Apr 11, 2020

8 fps
MacBook Pro (16-inch 3072 x 1920, 2019)
Catalina 10.15.4 (19E287)
2.6 GHz 6-Core Intel Core i7
16 GB 2667 MHz DDR4
Intel UHD Graphics 630 1536 MB
Chrome 81.0.4044.92

Could it be because of the Macbook high resolution?

@AAllport
Copy link

8FPS
Operating System: Windows 10 Pro 64-bit (10.0, Build 18363) (18362.19h1_release.190318-1202)
System Model: XPS 15 7590
Processor: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz (12 CPUs), ~2.6GHz
Memory: 16GB
--- GPU ---
Card name: Intel(R) UHD Graphics 630
Current Mode: 3840 x 2160 (32 bit) (60Hz)

Could be the GPU?

@Alex-A4
Copy link

Alex-A4 commented Apr 11, 2020

13 FPS
Device: Macbook Pro 13' 2015.
Memory: 8G DDR3 1867 MHz
Processor: Dual-Core Intel Core i5 2.7GHz
Video: Interl Iris Graphics 6100, 1536 MB
Chrome 80.0.3987.163

But with some starts of web-site there is 60 fps or 13-20fps or less

UPD: 2560 x 1600, Retina

@esDotDev
Copy link
Author

It looks like this might be related to 4k, could everyone make sure to note their screen resolutions?

@laimonas
Copy link

laimonas commented Apr 11, 2020

31 FPS
Device: Desktop PC
Memory: 32GB DDR4 3200Mhz
Processor: Ryzen 3700x
Video: ATI RX 580
Chrome: Latest
Resolution: 4K

@AAllport
Copy link

I've got 2 1080p's plugged in, did you want it trying on one of those?

@AAllport
Copy link

8FPS
Processor: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz (12 CPUs), ~2.6GHz
Card name: Intel(R) UHD Graphics 630

20~22 FPS

Just tried on the 1080p (one of 2 over a USB C dock)
Stil maxing out the intel GPU

@esDotDev
Copy link
Author

Stil maxing out the intel GPU

Interesting, no dedicated GPU on that XPS?

@Mravuri96
Copy link

Mravuri96 commented Apr 11, 2020

Yes, and it runs much better there, but is not rendering correctly on Android.
https://dev.gskinner.com/flutter_perf/shadowskit/

Seems like this should run ok with Vanilla Flutter though, the actual repaint region is < 10% of the screen. Is the entire view getting repainted each frame? My understanding is only the AnimatedSpinners should be re-drawing here...

What's most confusing is that on some MBP machines, we're seeing 5fps in Chrome, while others it's 60. Very similar machines.

may be some of them have the Use hardware acceleration when available settings turned off. I am getting non-skia framerates when i go to https://dev.gskinner.com/flutter_perf/shadowskit/ with hardware acceleration turned off.

Chrome Version 81.0.4044.92 -- No Extensions

skia disabled and hardware acceleration disabled - 8fps
skia enabled and harware acceleration disabled - 15 fps

skia disabled and hardware acceleration enabled - 20fps
skia enabled and harware acceleration enabled - 60 fps

Safari Version 13.1 (15609.1.20.111.8) -- No Extensions

skia disabled - 60fps
skia enabed - 60fps

MacBook Pro (16-inch 3584 x 2240, 2019) scaling 1:1
Catalina 10.15.4 (19E287)
2.4 GHz 8-Core Intel Core i9-9980HK
32 GB 2667 MHz DDR4
Intel UHD Graphics 630 1536 MB
AMD Radeon Pro 5500M 8192MB

@AAllport
Copy link

Stil maxing out the intel GPU

Interesting, no dedicated GPU on that XPS?

Yeah, it's got a NVIDIA GeForce GTX 1650
But task manager is saying it's doing "GPU 0 - 3D" which would be the intel UHD

Worth noting, VT-X is off, since I need to be able to use VirtualBox

@juancastillo0
Copy link

  • Acer Switch SA5-271
    CPU: Intel i3-6100U 2.3 GHz
    GPU: Intel HD Graphics 520 (Integrated)
    4 GB RAM
    Windows 10
    2160x1440

Chrome: 10 fps
Firefox: 35 fps

  • Samsung Galaxy S6 Edge+

Chrome: 6 fps

@rjcalifornia
Copy link

17 FPS

Device: HP Envy
Memory: 16 GB RAM
Processor: Intel Core i7 2.60 GHz
Chrome 80.0.3987.163
Windows 10

@rjcalifornia
Copy link

Yes, and it runs much better there, but is not rendering correctly on Android.
https://dev.gskinner.com/flutter_perf/shadowskit/

Seems like this should run ok with Vanilla Flutter though, the actual repaint region is < 10% of the screen. Is the entire view getting repainted each frame? My understanding is only the AnimatedSpinners should be re-drawing here...

What's most confusing is that on some MBP machines, we're seeing 5fps in Chrome, while others it's 60. Very similar machines.

It killed my Samsung S2

It runs at 60 fps on my laptop

@AAllport
Copy link

Just to add to the data here.
8fps on the pixel 3a

@maginc
Copy link

maginc commented Apr 11, 2020

6-8 fps
Model Identifier: MacBookPro8,1
Processor Name: Dual-Core Intel Core i7
Processor Speed: 2,8 GHz
Memory: 10 GB
Catalina 10.15.1

@harriseldon
Copy link

Model: Switch SA5-271 V1.03
Kernel: 5.3.0-46-generic
CPU: Intel i5-6200U (4) @ 2.800GHz
GPU: Intel Skylake GT2 [HD Graphics 520]
Memory: 3068MiB / 7842MiB
Resolution: 2160x1440
DE: KDE

12 FPS
Browser: Chrome Version 81.0.4044.92 (Official Build) (64-bit)

7 FPS
Browser: Firefox 75

@kgrigorian
Copy link

kgrigorian commented Apr 11, 2020

fps: 16
browser: latest chrome
specs: i7-6700k, gtx1070ti, windows 10

@Shewebi
Copy link

Shewebi commented Apr 11, 2020

"14" fps, but the CircularProgressIndicators are clearly animating perfectly smoothly
OS: Windows 10
Browser: Firefox 75
CPU: AMD Ryzen R7 3700x
RAM: 2x 8GB @ 3800MHz
GPU: Nvidia RTX 2070 Super
Display: 1440p 144Hz

@somnus74
Copy link

On a 2013 MacBook Pro (2.6 GHz Dual-Core Intel Core i5, 8 GB 1600 MHz DDR3):
Chrome gets 8-10 FPS
Safari gets 55-60 FPS

@mogenson
Copy link

7-8 FPS on PixelBook i5 (Chrome OS version 80)

@satvikpendem
Copy link

satvikpendem commented Apr 12, 2020

Looks like fps is proportional to screen size, maybe specifically width, not sure yet. I'm seeing 15-20 fps when the window is maximized (monitor resolution is 1920x1080) but I hit 60 fps when i shrink the window (maybe to 1/3 the width, around 600 px).

Chrome
Windows 10
i7 4790k 4-core
32 GB RAM

@spauldhaliwal
Copy link

spauldhaliwal commented Apr 12, 2020

I'm getting ~10fps when fullscreen on chrome. 2019 Macbook pro. If I resize the window to 1/3 width and height, it will reach 60fps.

On safari, however, I get 60 fps fullscreen, and weirdly while continuously resizing it jumps up to ~90fps.

MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)
Processor: 1.4 GHz Quad-Core Intel Core i5
Ram: 16 GB 2133 MHz LPDDR3
Graphics: Intel Iris Plus Graphics 645 1536 MB

@eseidelGoogle
Copy link
Contributor

18 fps. Pixelbook, using an attached second monitor and lots of tabs open.
Chrome/80.0.3987.162
GPU: VENDOR= 0x8086 [Intel Open Source Technology Center], DEVICE=0x591e [Mesa DRI Intel(R) HD Graphics 615 (Kaby Lake GT2) ] ACTIVE

@steptan
Copy link

steptan commented Apr 12, 2020

8fps, internal or external display, Chrome (not smooth)

MacBook Pro (15-inch, 2017)
Processor 2.9 GHz Quad-Core Intel Core i7
Memory: 16 GB 2133 MHz LPDDR3
Graphics: Radeon Pro 560 4 GB
Intel HD Graphics 630 1536 MB

(60 fps on safari, very smooth animation)

@fmatosqg
Copy link
Contributor

fmatosqg commented Apr 12, 2020

Chrome: 8fps when full screen, goes to 17fps using quarter of screen. Also it's about 8fps when using half-width or half-height.

It consumes very little CPU but makes my typing lag real bad. This is a kinda known issue in Cinnamon, but you don't see it often unless you're really stressing the laptop. My average load is 0.66 and cpu goes between 10-20% for all 4 cpus.

Firefox: can't go higher than 10 fps not even with quarter screen.

Dell 5520 (similar to XPS 15 2017 model), 4k screen.

Lowering the res to HD (with hi-dpi) the FPS goes way higher, but so it does if I shrink the screen.

> inxi -GCS
System:
  Host: fabio-mint-5520 Kernel: 5.3.0-42-generic x86_64 bits: 64 
  Desktop: Cinnamon 4.4.8 Distro: Linux Mint 19.3 Tricia 
CPU:
  Topology: Quad Core model: Intel Core i5-7440HQ bits: 64 type: MCP 
  L2 cache: 6144 KiB 
  Speed: 1553 MHz min/max: 800/3800 MHz Core speeds (MHz): 1: 885 2: 801 
  3: 801 4: 801 
Graphics:
  Device-1: Intel driver: i915 v: kernel 
  Display: x11 server: X.Org 1.19.6 driver: modesetting unloaded: fbdev,vesa 
  resolution: 3840x2160~60Hz 
  OpenGL: renderer: Mesa DRI Intel HD Graphics 630 (Kaby Lake GT2) 
  v: 4.5 Mesa 19.2.8 

Edit: chrome is kinda the same, slightly worse with kernel 5.0.0-43-generic, but system lag is waay worse - which is the opposite of my overall experience while usiing kernel 5.0 on this laptop.

I can safely say I'll actively steer away of any web pages presenting this problem while I'm using this laptop, it makes the whole laptop unbearably slow.

=======================

Android Oneplus 6 (still in Pie) 1080 x 2280 pixels, 19:9 aspect ratio.

Chrome portrait: 23 fps landscape: 26 fps
That was surprising since I can see 7 spinning animations in portrait but only 2.5 in landscape.

Chrome Canary portrait: 26 fps landscape: 29 fps.

Firefox portrait: 7 fps landscape: 8 fps

@TahaTesser TahaTesser added customer: crowd Affects or could affect many people, though not necessarily a specific customer. framework flutter/packages/flutter repository. See also f: labels. platform-web Web applications specifically labels Apr 13, 2020
@TahaTesser TahaTesser added the c: performance Relates to speed or footprint issues (see "perf:" labels) label Apr 13, 2020
@TahaTesser
Copy link
Member

15 fps, Chrome
MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)
1.4 GHz Quad-Core Intel Core i5
8 GB 2133 MHz LPDDR3
Intel Iris Plus Graphics 645 1536 MB

@yjbanov
Copy link
Contributor

yjbanov commented Apr 13, 2020

Thanks for filing the issue and providing sample code! We should definitely look into this use-case.

In the meantime, consider replacing Column with a ListView (if you expect the number of _Box widgets to be large then use ListView.builder). In my testing switching to ListView improved performance considerably. This is because as part of doing lazy rendering the ListView will composite each list item into its own layer rather than plopping all widgets into one big picture then repainting the whole picture on every frame.

Here's the code for the WebPerformanceSpike widget:

class WebPerformanceSpike extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.green.shade100,
        body: ListView(
          children: <Widget>[
            FpsIndicator(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
            _Box(),
          ],
        ),
      ),
    );
  }

  Container _Box([Widget child]) {
    return Container(
        margin: EdgeInsets.only(bottom: 10),
        decoration: BoxDecoration(
          color: Colors.white,
          boxShadow: [BoxShadow(spreadRadius: 4, blurRadius: 4, color: Colors.blue)],
        ),
        alignment: Alignment.center,
        width: double.infinity,
        height: 100,
        child: CircularProgressIndicator());
  }
}

@esDotDev
Copy link
Author

esDotDev commented Apr 13, 2020

Nice, good to know. This is just a test case to highlight this general performance issue. In our production app it's more like 4 or 5 shadowed panels / cards, that are positioned around the app, and there would be no ability to put those in a List. When we trigger some sort of animation on that page, like a SlidingTab, we can drop to single digit FPS on devices with 4k monitors.

@yjbanov
Copy link
Contributor

yjbanov commented Apr 13, 2020

Nice, good to know. This is just a test case to highlight this general performance issue. In our production app it's more like 4 or 5 shadowed panels / cards, that are positioned around the app, and there would be no ability to put those in a List. When we trigger some sort of animation on that page, like a SlidingTab, we can drop to single digit FPS on devices with 4k monitors.

We should totally optimize this. I'm only pointing out possible solutions that you can apply today without waiting for the Web engine fix. If ListView is not usable, another solution might be to wrap individually repainting parts into RepaintBoundary, e.g.:

  RepaintBoundary _Box([Widget child]) {
    return RepaintBoundary(child: Container(
        margin: EdgeInsets.only(bottom: 10),
        decoration: BoxDecoration(
          color: Colors.white,
          boxShadow: [BoxShadow(spreadRadius: 4, blurRadius: 4, color: Colors.blue)],
        ),
        alignment: Alignment.center,
        width: double.infinity,
        height: 100,
        child: CircularProgressIndicator()));
  }

RepaintBoundary is a double-edged sword though. You don't want to wrap things that are too small. Otherwise you risk generating too many layers. Looking that this sample app though, it seems a repaint boundary would be appropriate per card.

@esDotDev
Copy link
Author

esDotDev commented Apr 14, 2020

Cool, just wanted to be sure we weren't focusing too narrowly on items within a list.

We've put together another test for you which is much more configurable:
http://dev.gskinner.com/flutter_perf/desktop_scaffold/#/

The good news is that RepaintBoundaries works like a charm! On a 2019 Retina MBP:

  • 1 spinner, w/o repaint boundary: 8fps
  • 1 spinner, w/ repaint boundary: 58+fps

Hopefully this fix will hold when we port back to the main application!

Some feedback:

  • Seems odd that CircularProgressIndicator does not have it's own built in RepaintBoundary.
  • The team might want to give some stronger guidance on this, especially around implicit widgets like TweenAnimationBuilder and AnimatedOpacity. Unless this is just temporary web-specific behavior?
bool enableShadows = true;
double spinCount = 2;
bool useRepaintBoundaries = false;

class WebPerformanceSpike2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StatsFl(
      child: StatefulBuilder(
        builder: (_, setState) => Stack(
          children: <Widget>[
            //Left
            Positioned(top: 0, bottom: 0, left: 0, width: 200, child: _ShadowBox(spinCount > 0)),
            //Top
            Positioned(top: 0, left: 200, right: 200, height: 200, child: _ShadowBox(spinCount > 1)),
            //Right
            Positioned(top: 0, bottom: 0, right: 0, width: 200, child: _ShadowBox(spinCount > 2)),
            Positioned(
                top: 200,
                bottom: 0,
                left: 200,
                right: 200,
                child: ListView(
                  children: <Widget>[
                    //Bottom
                    _ShadowBox(spinCount > 3),
                    _ShadowBox(spinCount > 4),
                    _ShadowBox(spinCount > 5),
                    _ShadowBox(spinCount > 6),
                    _ShadowBox(spinCount > 7),
                  ],
                )),
            Positioned(
              left: 250,
              top: 30,
              width: 600,
              child: Row(
                children: <Widget>[
                  Text("Shadows:"),
                  Checkbox(tristate: false, value: enableShadows, onChanged: (v) => setState(() => enableShadows = v)),
                  SizedBox(
                    width: 20,
                  ),
                  Text("RepaintBoundary:"),
                  Checkbox(
                      tristate: false,
                      value: useRepaintBoundaries,
                      onChanged: (v) => setState(() => useRepaintBoundaries = v)),
                  SizedBox(
                    width: 20,
                  ),
                  Text("Spin Count:"),
                  Slider(
                      min: 1, max: 8, divisions: 7, value: spinCount, onChanged: (v) => setState(() => spinCount = v)),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _ShadowBox extends StatelessWidget {
  final bool showIndicator;

  const _ShadowBox(this.showIndicator, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    BoxShadow shadow = BoxShadow(spreadRadius: 5, blurRadius: 5, color: Colors.black.withOpacity(.05));
    Widget spinner = !useRepaintBoundaries
        ? CircularProgressIndicator()
        : RepaintBoundary(child: CircularProgressIndicator(backgroundColor: Colors.red));
    return Container(
      margin: EdgeInsets.all(10),
      padding: EdgeInsets.all(10),
      decoration: BoxDecoration(color: Colors.white, boxShadow: enableShadows ? [shadow] : null),
      alignment: Alignment.center,
      width: double.infinity,
      height: 100,
      child: SizedBox(width: 10, height: 10, child: showIndicator ?? true ? spinner : Container()),
    );
  }
}

@ferhatb ferhatb added this to the April 2020 milestone Apr 16, 2020
@satvikpendem
Copy link

On Firefox I'm getting rock solid FPS, from both the first and second examples, around 60-100 for the first one and then 45-60 for the second one without repaint boundaries, and then a solid 60 with it. It seems the second one locks the FPS to 60 so it could be a lot higher potentially without the lock.

@ferhatb
Copy link
Contributor

ferhatb commented May 13, 2020

@esDotDev a round of optimizations landed related to this. If you remove FpsIndicator (with TweenAnimationBuilder constantly refreshing) and turning on FPS meter in Chrome Devtools rendering, i see consistent 60fps. Closing issue.

@ferhatb ferhatb closed this as completed May 13, 2020
@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
c: performance Relates to speed or footprint issues (see "perf:" labels) customer: crowd Affects or could affect many people, though not necessarily a specific customer. customer: web10 framework flutter/packages/flutter repository. See also f: labels. platform-web Web applications specifically
Projects
None yet
Development

No branches or pull requests