Skip to content

Impeller causes significant FPS drop on PowerVR BXM GPU (Moto G73, Flutter 3.38.2) #181315

Closed

Description

@Ashupaldeora

Steps to reproduce

Environment
Flutter version: 3.38.2 (stable)
Platform: Android
Device: Moto G73
SoC: Dimensity 930
GPU: PowerVR BXM
Android version: 14
Build mode: Profile

Description
On a Moto G73 with PowerVR BXM GPU, running the same Flutter app in profile mode shows a significant FPS difference depending on Impeller.

  • With Impeller enabled: ~80 FPS
  • With Impeller disabled (Skia): ~117 FPS

This is measured in profile mode using devtools.
No Vulkan fallback warnings are shown.
The performance drop is visually noticeable as stutter/jank.

Expected results

Impeller performance should be comparable to or better than Skia.

Actual results

Impeller shows ~30% lower frame rate than Skia on this device.

Code sample

Code sample
[Paste your code here]

Screenshots or Video

Screenshots / Video demonstration

[Upload media here]

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
[Paste your output here]

Activity

  1. tirth-patel-nc commented on Jan 22, 2026

    @tirth-patel-nc
    Member

    @Ashupaldeora For us to further investigate this issue, please provide a completed and minimal reproducible code sample that doesn’t include 3rd party plugins or complex production code. Also, please share the output flutter doctor -v.

    the request is unclear and lacks enough detail to act on. please provide a clearer description and the expected behavior.

    this tracker is for sdk-related issues only. for general help, please use stackoverflow or flutter community channels.

    Thank you!

  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Jan 22, 2026
  3. Ashupaldeora commented on Jan 22, 2026

    @Ashupaldeora
    Author

    Below is a single-file, SDK-only minimal repro.
    It can be pasted directly into lib/main.dart of a new Flutter project.
    No third-party plugins, assets, or network requests are used.

    import 'dart:math' as math;
    import 'dart:ui' as ui;
    
    import 'package:flutter/material.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      final images = await _SyntheticImages.generate();
      runApp(_App(images: images));
    }
    
    /// Generates a few large raster images at startup (no assets/network),
    /// then reuses them in a heavy scrolling UI with shadows + clips + blur.
    final class _SyntheticImages {
      final List<ui.Image> images;
      const _SyntheticImages(this.images);
    
      static Future<_SyntheticImages> generate() async {
        const w = 1400;
        const h = 900;
    
        Future<ui.Image> one(int seed) async {
          final recorder = ui.PictureRecorder();
          final canvas = Canvas(recorder,  Rect.fromLTWH(0, 0, w.toDouble(), h.toDouble()));
    
          // Background gradient.
          final bg = Paint()
            ..shader = ui.Gradient.linear(
              const Offset(0, 0),
               Offset(w.toDouble(), h.toDouble()),
              [
                Color.lerp(const Color(0xFF101827), const Color(0xFF3B1D5A), (seed % 7) / 6)!,
                Color.lerp(const Color(0xFF0B2B3A), const Color(0xFF2B4C7E), ((seed + 3) % 7) / 6)!,
                const Color(0xFF0A0A12),
              ],
              [0.0, 0.55, 1.0],
            );
          canvas.drawRect(Rect.fromLTWH(0, 0, w.toDouble(), h.toDouble()), bg);
    
          // Lots of soft blobs.
          final rnd = math.Random(seed);
          for (int i = 0; i < 80; i++) {
            final cx = rnd.nextDouble() * w;
            final cy = rnd.nextDouble() * h;
            final r = 40.0 + rnd.nextDouble() * 220.0;
            final c = HSVColor.fromAHSV(
              0.55,
              rnd.nextDouble() * 360,
              0.65,
              0.95,
            ).toColor();
            final p = Paint()
              ..color = c.withOpacity(0.22)
              ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 28);
            canvas.drawCircle(Offset(cx, cy), r, p);
          }
    
          // Some sharp lines / shapes to increase sampling cost.
          final linePaint = Paint()
            ..style = PaintingStyle.stroke
            ..strokeWidth = 6
            ..shader = ui.Gradient.linear(
              const Offset(0, 0),
               Offset(w.toDouble(), 0),
              [Colors.white.withOpacity(0.16), Colors.transparent],
            );
          for (int i = 0; i < 16; i++) {
            final y = (i + 1) * (h / 18.0);
            canvas.drawPath(
              Path()
                ..moveTo(-40, y)
                ..cubicTo(w * 0.35, y - 70, w * 0.6, y + 70, w + 60, y),
              linePaint,
            );
          }
    
          final picture = recorder.endRecording();
          return picture.toImage(w, h);
        }
    
        final imgs = await Future.wait([
          one(1),
          one(7),
          one(42),
          one(99),
        ]);
    
        return _SyntheticImages(imgs);
      }
    }
    
    class _App extends StatelessWidget {
      final _SyntheticImages images;
      const _App({required this.images});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData.dark(useMaterial3: true),
          home: _Home(images: images),
        );
      }
    }
    
    class _Home extends StatelessWidget {
      final _SyntheticImages images;
      const _Home({required this.images});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: const Color(0xFF070813),
          body: CustomScrollView(
            slivers: [
              SliverAppBar(
                pinned: true,
                expandedHeight: 150,
                backgroundColor: const Color(0xFF070813),
                flexibleSpace: FlexibleSpaceBar(
                  titlePadding: const EdgeInsetsDirectional.only(start: 16, bottom: 12),
                  title: const Text('Heavy Home Feed Repro'),
                  background: Stack(
                    fit: StackFit.expand,
                    children: [
                      // Big blur behind appbar.
                      ImageFiltered(
                        imageFilter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18),
                        child: Container(
                          decoration: const BoxDecoration(
                            gradient: LinearGradient(
                              colors: [Color(0xFF151A33), Color(0xFF070813)],
                              begin: Alignment.topLeft,
                              end: Alignment.bottomRight,
                            ),
                          ),
                        ),
                      ),
                      DecoratedBox(
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                            colors: [
                              const Color(0xFF070813).withOpacity(0.0),
                              const Color(0xFF070813).withOpacity(0.9),
                            ],
                            begin: Alignment.topCenter,
                            end: Alignment.bottomCenter,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              SliverList.builder(
                itemCount: 60,
                itemBuilder: (context, index) {
                  return _FeedCard(
                    index: index,
                    image: images.images[index % images.images.length],
                    onTap: () {
                      Navigator.of(context).push(
                        MaterialPageRoute(
                          builder: (_) => _Details(images: images, startIndex: index),
                        ),
                      );
                    },
                  );
                },
              ),
              const SliverToBoxAdapter(child: SizedBox(height: 24)),
            ],
          ),
        );
      }
    }
    
    class _FeedCard extends StatelessWidget {
      final int index;
      final ui.Image image;
      final VoidCallback onTap;
    
      const _FeedCard({
        required this.index,
        required this.image,
        required this.onTap,
      });
    
      @override
      Widget build(BuildContext context) {
        final title = 'Session ${index + 1}';
        final subtitle = 'Shadows + clip + blur + high-quality sampling';
    
        return Padding(
          padding: const EdgeInsets.fromLTRB(16, 14, 16, 0),
          child: GestureDetector(
            onTap: onTap,
            child: ClipRRect(
              borderRadius: BorderRadius.circular(26),
              child: Stack(
                children: [
                  // Multiple large shadows (done via outer container to emulate "card" styling).
                  // Shadow under the clipped content:
                  Positioned.fill(
                    child: DecoratedBox(
                      decoration: BoxDecoration(
                        color: const Color(0xFF0C0E1F),
                        boxShadow: [
                          BoxShadow(
                            color: Colors.black.withOpacity(0.65),
                            blurRadius: 42,
                            spreadRadius: 8,
                            offset: const Offset(0, 18),
                          ),
                          BoxShadow(
                            color: const Color(0xFF6D4CFF).withOpacity(0.18),
                            blurRadius: 50,
                            spreadRadius: 2,
                            offset: const Offset(0, 10),
                          ),
                        ],
                      ),
                    ),
                  ),
    
                  // The "image" (synthetic) with high filter quality.
                  SizedBox(
                    height: 240,
                    width: double.infinity,
                    child: FittedBox(
                      fit: BoxFit.cover,
                      child: SizedBox(
                        width: image.width.toDouble(),
                        height: image.height.toDouble(),
                        child: RawImage(
                          image: image,
                          filterQuality: FilterQuality.high,
                        ),
                      ),
                    ),
                  ),
    
                  // A subtle color overlay to add blending work.
                  Positioned.fill(
                    child: DecoratedBox(
                      decoration: BoxDecoration(
                        gradient: LinearGradient(
                          colors: [
                            const Color(0xFF070813).withOpacity(0.10),
                            const Color(0xFF070813).withOpacity(0.85),
                          ],
                          begin: Alignment.topCenter,
                          end: Alignment.bottomCenter,
                        ),
                      ),
                    ),
                  ),
    
                  // A blurred "glass" info bar.
                  Positioned(
                    left: 12,
                    right: 12,
                    bottom: 12,
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(18),
                      child: BackdropFilter(
                        filter: ui.ImageFilter.blur(sigmaX: 22, sigmaY: 22),
                        child: DecoratedBox(
                          decoration: BoxDecoration(
                            color: Colors.white.withOpacity(0.10),
                            border: Border.all(color: Colors.white.withOpacity(0.10)),
                          ),
                          child: Padding(
                            padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
                            child: Row(
                              children: [
                                Container(
                                  width: 34,
                                  height: 34,
                                  decoration: BoxDecoration(
                                    shape: BoxShape.circle,
                                    gradient: LinearGradient(
                                      colors: [
                                        const Color(0xFF6D4CFF).withOpacity(0.95),
                                        const Color(0xFF00D4FF).withOpacity(0.75),
                                      ],
                                    ),
                                    boxShadow: [
                                      BoxShadow(
                                        color: const Color(0xFF6D4CFF).withOpacity(0.45),
                                        blurRadius: 18,
                                        spreadRadius: 1,
                                        offset: const Offset(0, 8),
                                      ),
                                    ],
                                  ),
                                ),
                                const SizedBox(width: 12),
                                Expanded(
                                  child: Column(
                                    crossAxisAlignment: CrossAxisAlignment.start,
                                    children: [
                                      Text(
                                        title,
                                        maxLines: 1,
                                        overflow: TextOverflow.ellipsis,
                                        style: const TextStyle(
                                          fontSize: 16,
                                          fontWeight: FontWeight.w700,
                                          letterSpacing: 0.2,
                                        ),
                                      ),
                                      const SizedBox(height: 2),
                                      Text(
                                        subtitle,
                                        maxLines: 1,
                                        overflow: TextOverflow.ellipsis,
                                        style: TextStyle(
                                          fontSize: 12.5,
                                          color: Colors.white.withOpacity(0.75),
                                        ),
                                      ),
                                    ],
                                  ),
                                ),
                                const SizedBox(width: 10),
                                const Icon(Icons.chevron_right, color: Colors.white70),
                              ],
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class _Details extends StatelessWidget {
      final _SyntheticImages images;
      final int startIndex;
      const _Details({required this.images, required this.startIndex});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: const Color(0xFF070813),
          appBar: AppBar(
            title: Text('Details (from ${startIndex + 1})'),
            backgroundColor: const Color(0xFF070813),
          ),
          body: ListView.builder(
            itemCount: 40,
            itemBuilder: (context, i) {
              final index = startIndex + i;
              return _FeedCard(
                index: index,
                image: images.images[index % images.images.length],
                onTap: () {},
              );
            },
          ),
        );
      }
    }
    

    Observed results (Moto G73, PowerVR BXM, Flutter 3.38.2, profile mode):

    Impeller enabled: ~20 FPS average (Flutter DevTools)

    Impeller disabled (Skia): ~70 FPS average

    The difference is immediately visible during scroll and navigation.

    Expected behavior:
    Impeller performance should be comparable to Skia for common UI patterns involving images, shadows, and blur.

    Actual behavior:
    Impeller shows a severe performance regression (~3× lower FPS) on this device.

    flutter doctor logs

    [✓] Flutter (Channel stable, 3.38.2, on macOS 15.7.2 24G325 darwin-arm64, locale en-IN) [482ms]
    • Flutter version 3.38.2 on channel stable at /Users/ashupalsmac/Documents/development/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision f5a8537 (9 weeks ago), 2025-11-18 09:27:21 -0500
    • Engine revision b5990e5
    • Dart version 3.10.0
    • DevTools version 2.51.1
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations,
    enable-native-assets, omit-legacy-version-file, enable-lldb-debugging

    [✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [5.8s]
    • Android SDK at /Users/ashupalsmac/Library/Android/sdk
    • Emulator version 36.2.12.0 (build_id 14214601) (CL:N/A)
    • Platform android-36.1, build-tools 36.0.0
    • ANDROID_HOME = /Users/ashupalsmac/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    This is the JDK bundled with the latest Android Studio installation on this machine.
    To manually set the JDK path, use: flutter config --jdk-dir="path/to/jdk".
    • Java version OpenJDK Runtime Environment (build 21.0.8+-14196175-b1038.72)
    • All Android licenses accepted.

    [✓] Xcode - develop for iOS and macOS (Xcode 16.4) [1,493ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 16F6
    • CocoaPods version 1.16.2

    [✓] Chrome - develop for the web [5ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

    [✓] Connected device (5 available) [6.4s]
    • moto g73 5G (mobile) • ZD2229HRVB • android-arm64 • Android 14 (API 34)
    • iPhone (wireless) (mobile) • 00008030-001E6D421A9A802E • ios • iOS 18.4.1 22E252
    • iPhone 17 Pro Max (mobile) • AD796097-DB88-47DD-9C18-B37ADB59F1BC • ios • com.apple.CoreSimulator.SimRuntime.iOS-26-1 (simulator)
    • macOS (desktop) • macos • darwin-arm64 • macOS 15.7.2 24G325 darwin-arm64
    • Chrome (web) • chrome • web-javascript • Google Chrome 144.0.7559.60

    [✓] Network resources [3.4s]
    • All expected network resources are available.

    • No issues found!

  4. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Jan 22, 2026
  5. tirth-patel-nc commented on Jan 23, 2026

    @tirth-patel-nc
    Member

    @Ashupaldeora could you please share the flutter run -v & performance dump json via devtools?

  6. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Jan 23, 2026
  7. Ashupaldeora commented on Jan 23, 2026

    @Ashupaldeora
    Author

    Hi @tirth-patel-nc , thanks for looking into this.

    I’ve attached the requested artifacts:

    Verbose run logs

    impeller_profile_run.txt — flutter run --profile -v (Impeller enabled)

    skia_profile_run.txt — flutter run --profile --no-enable-impeller -v

    DevTools performance json

    impeller_performance.json

    skia_performance.json

    The timelines capture scrolling and navigation interactions where the FPS difference is most visible (Impeller ~20 FPS vs Skia ~70 FPS on Moto G73 / PowerVR BXM, Android 14).

  8. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Jan 23, 2026
  9. added
    engineflutter/engine related. See also e: labels.
    e: impellerImpeller rendering backend issues and features requests
    c: performanceRelates to speed or footprint issues (see "perf:" labels)
    perf: speedPerformance issues related to (mostly rendering) speed
    and removed
    in triagePresently being triaged by the triage team
    on Jan 26, 2026
  10. 6 remaining items

  11. Ashupaldeora commented on Jan 27, 2026

    @Ashupaldeora
    Author

    @gaaclarke At the moment I only have access to this device (Moto G73 / PowerVR BXM). I don’t have other Android devices available to test on right now.
    That said, I did share a release build with a few friends on different Android devices, and they reported normal performance there.

  12. flutter-triage-bot commented on Jun 3, 2026

    @flutter-triage-bot

    This issue is marked P1 but has had no recent status updates.

    The P1 label indicates high-priority issues that are at the top of the work list. This is the highest priority level a bug can have if it isn't affecting a top-tier customer or breaking the build. Bugs marked P1 are generally actively being worked on unless the assignee is dealing with a P0 bug (or another P1 bug). Issues at this level should be resolved in a matter of months and should have monthly updates on GitHub.

    Please consider where this bug really falls in our current priorities, and label it or assign it accordingly. This allows people to have a clearer picture of what work is actually planned. Thanks!

  13. enoiu commented on Jun 18, 2026

    @enoiu

    Hi, As I commented on #179921, I wanted to share some additional reports from my users that might be related to the PowerVR BXM GPU.

    Unfortunately I couldn't capture the logs, but my users have been experiencing severe rendering bugs (fragmented pages, missing UI, shaking) on the following devices:

    • Teclast T60AI (SoC: Allwinner A733, GPU: IMG BXM-4-64 MC1) — reported on Flutter 3.38.7
    • Acer Iconia Tab A14 (SoC: Allwinner A733, GPU: IMG BXM-4-64 MC1) — reported on Flutter 3.44.2

    Since disabling Impeller causes huge performance drops for my drawing app, my only workaround was to downgrade to Flutter 3.35.7, which completely resolved the issue for them.

    Since these devices also use PowerVR BXM GPUs, I thought this information might be helpful for your investigation!

  14. gaaclarke commented on Jun 18, 2026

    @gaaclarke
    Member

    @Ashupaldeora if you have a setup that reproduces the problem you can try forcing the app into OpenGLES. You can add the following flag to you manifest (it only works in profile and debug builds).

      <meta-data
        android:name="io.flutter.embedding.android.ImpellerBackend"
        android:value="opengles" />
    

    If that fixes the problem we can look into blocking those devices from using vulkan. The most likely explanation for what you are seeing is inferior vulkan drivers.

  15. Ashupaldeora commented on Jul 13, 2026

    @Ashupaldeora
    Author

    @gaaclarke
    Yes, forcing the app to use OpenGLES improves the performance noticeably on the same device.

    I don’t have exact FPS numbers right now, but with Vulkan the app feels much slower/jankier, while with OpenGLES it feels significantly smoother.

    So it does look like the issue is specific to the Vulkan backend on this PowerVR BXM device.

  16. perret123 commented on Aug 19, 2026

    @perret123

    Same GPU family, with the exact FPS numbers @gaaclarke asked for when suggesting the ImpellerBackend=opengles test.

    Device: Acer Iconia A16 (A16-11MN_EEA) — SoC Allwinner A733, GPU PowerVR B-Series BXM-4-64, GLES 3.2 driver 24.2@6643903, Android 15 (API 35). Same A733 + BXM-4-64 MC1 combination @enoiu reported for the Teclast T60AI and Acer Iconia Tab A14.
    Flutter 3.47.0. ImpellerBackend toggled via manifest in --profile builds; Skia via EnableImpeller=false in --release.

    The collapse is orientation-dependent

    The panel's native orientation is landscape (1920x1200 @60Hz, density 160, dpr 1.0). Each run is 5x navigating into a heavy screen and back, identical content and gestures, frame counts from dumpsys SurfaceFlinger --timestats:

    Build Orientation Frames avg FPS
    Impeller Vulkan (profile) portrait 262 15.8
    Impeller OpenGLES (profile) portrait 602 39.4
    Impeller Vulkan (release) portrait 261 16.2
    Impeller Vulkan (release) landscape 556 35.7
    Skia (release) portrait 611 39.7
    Skia (release) landscape 637 42.0

    Forcing OpenGLES fully fixes it — 39.4 vs 15.8 FPS, a 2.5x difference in the same build type, landing exactly on Skia's number. In the device's native orientation Vulkan is only ~15% behind Skia; in the rotated orientation it drops 55%.

    Mechanism

    dumpsys SurfaceFlinger HWC layer table for the Flutter surface, portrait:

    Backend Comp Type Transform Source Crop
    Impeller Vulkan CLIENT ROT_90 1200x1920
    Impeller OpenGLES CLIENT 0 1920x1200
    Skia CLIENT 0 1920x1200

    In landscape every layer composites as DEVICE. In portrait this display engine (Allwinner SUNXI HWC, DE.0) loses hardware overlay support and the whole screen falls back to CLIENT (GPU) composition. The OpenGLES and Skia backends pre-rotate into a display-oriented buffer; the Vulkan backend hands the compositor a portrait buffer tagged ROT_90, and that rotated blit is what this driver handles badly.

    This likely also explains the "fragmented pages / missing UI / shaking" reports — at ~16 FPS with full-screen GPU composition, partial and stale frames are exactly what a user sees. A screencap of the same screen is pixel-perfect, so the rendered content is correct; it just doesn't reach the display in time.

    Denylist note

    driver_info_vk.cc currently blocks only powervr_gpu_ < PowerVRGPU::kBXE, so BXM passes (since #173561) — while the enum itself marks kAXE..kBXT as "Vulkan may work, but not tested".

    There is already a PowerVR DDK version floor in IsKnownBadDriver() (25.1 / 6794074), but it is gated on the Pixel 10 device ID. This tablet ships 24.2@6643903, below that floor, and is not covered by it. A version-based rule rather than a device-ID-based one would catch this class of device.

    Happy to run further measurements on this hardware if that helps.

  17. github-actions commented on Sep 10, 2026

    @github-actions

    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.

  18. locked as resolved and limited conversation to collaborators on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority issues at the top of the work lista: productionIssues experienced in live production appsc: performanceRelates to speed or footprint issues (see "perf:" labels)e: device-specificOnly manifests on certain devicese: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.needs repro infoAutomated crash report whose cause isn't yet knownperf: speedPerformance issues related to (mostly rendering) speedplatform-androidAndroid applications specificallyteam-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions