Skip to content

[Linux] GTK embedder leaks native memory every frame on an embedded ARM GLES driver (Wayland, sibling texture path); the replacement subsurface renderer on master faults the GPU on the same device #192603

Open

Description

@ZhoufangErqiangu

Summary

On an embedded ARM board (Rockchip RK3576, Mali-G52 MC3 Bifrost, proprietary ARM GLES driver "libmali" g24p0, vendor kernel 6.1 with bifrost_kbase, Debian 12, weston 10 kiosk-shell, GTK 3.24.38), a Flutter Linux app on Wayland grows its resident memory by roughly 5.7 KB per presented frame for as long as anything animates. A screen with one repeating AnimationController grows at ≈20 MB/min and reaches 5.7 GB in about four hours, after which the 8 GB board swaps for two hours and the kernel OOM-killer ends it. Leaving the animating screen stops the growth immediately.

The growth is not in the Dart heap. It is glibc main-arena memory allocated on the platform/UI thread by the GLES driver, one small set of bookkeeping objects per fl_framebuffer_create_sibling() + gdk_cairo_draw_from_gl() + delete round trip in fl_compositor_opengl_render(). The same app on x86_64 Mesa does not leak.

This is the same behaviour that #182192 (comment by richardexfo, 2026-02-26: embedded ARM + OpenGL ES, "any animation causes an increase in memory") and PR #183653 ("Linux reuse sibling") addressed; that fix was reverted in #183871 and the whole path was removed in #192150 in favour of FlViewRendererSubsurface. On this device the subsurface renderer (master f1ac3fe4, 3.48.0-1.0.pre-696) does not work either: the window is black, the first three launches SIGSEGV within 3 s, and the kernel logs ~1700 mali … Unhandled Page fault in AS1 at VA 0x0 / JOB_READ_FAULT lines. Devices with this class of driver currently have no working hardware-accelerated path on any channel.

Environment

SoC / GPU Rockchip RK3576, Mali-G52 3 cores r1p0 (0x07040002), DT compatible arm,mali-bifrost
Kernel Linux 6.1.99 (vendor BSP), GPU kernel driver bifrost_kbase g25p0-00eac0 (UK 11.47); no panfrost/panthor
Userspace GPU proprietary libmali-bifrost-g52-g24p0-x11-wayland-gbm 1.9 (provides libEGL, libGLESv2, libgbm, libwayland-egl)
Compositor weston 10.0.1, kiosk-shell, drm-backend, single DSI output 1080×1240
GTK / GLib gtk3 3.24.38, glib 2.74.6, libepoxy 1.5.10, wayland 1.21.0 (Debian 12 bookworm, glibc 2.36)
Embedder env GDK_BACKEND=wayland, GDK_GL=gles; FLUTTER_LINUX_RENDERER unset (OpenGL)
Flutter (leak) 3.41.8 stable
Flutter (GPU fault) master f1ac3fe4b34cbece23031cc78b864614aa5b5a43 (3.48.0-1.0.pre-696, Dart 3.14.0-214.0.dev, engine d062219657405470009c3884acd719c82ea0d42b)
App release build of a full-screen GTK window; the app only presents frames, no platform views

Part 1 — per-frame native leak (stable, OpenGL renderer)

Steps to reproduce

  1. On the device above, run a release build of an app whose screen contains any repeating animation. A minimal one:

    import 'package:flutter/material.dart';
    
    void main() => runApp(const MaterialApp(home: Scaffold(body: Center(child: Spinner()))));
    
    class Spinner extends StatefulWidget {
      const Spinner({super.key});
      @override
      State<Spinner> createState() => _SpinnerState();
    }
    
    class _SpinnerState extends State<Spinner> with SingleTickerProviderStateMixin {
      late final AnimationController _c =
          AnimationController(vsync: this, duration: const Duration(milliseconds: 1200))..repeat();
      @override
      void dispose() { _c.dispose(); super.dispose(); }
      @override
      Widget build(BuildContext context) => AnimatedBuilder(
            animation: _c,
            builder: (_, __) => Container(width: 80, height: 14, color: Color.lerp(Colors.teal, Colors.white, _c.value)),
          );
    }
  2. Sample every 10 s:

    P=$(pgrep -f my_app)
    awk '/VmRSS/{print $2}' /proc/$P/status
    awk '/\[heap\]/{f=1} f&&/^Rss:/{print $2; exit}' /proc/$P/smaps
  3. Replace the screen with a static one (or stop the controller) and keep sampling.

Expected results

Resident memory settles after warm-up regardless of whether something animates.

Actual results

With exactly the sample above (release build, nothing else on screen), sampled every 20 s on the device:

09:49:22 rss=221992 kB  [heap]=43044 kB   anon=70276 kB   threads=22
09:50:22 rss=249840 kB  [heap]=69564 kB   anon=97916 kB
09:51:23 rss=276940 kB  [heap]=96456 kB   anon=124044 kB
09:52:23 rss=310080 kB  [heap]=128428 kB  anon=157180 kB
09:53:23 rss=339252 kB  [heap]=155388 kB  anon=185848 kB
09:54:03 rss=356184 kB  [heap]=171812 kB  anon=202632 kB

[heap] +128.8 MB in 281 s ≈ 27.5 MB/min ≈ 7.6 KB per frame at 60 fps, linear, thread count constant. Process CPU averaged 82 % of one core (platform/UI thread 69 %, raster 12 %, driver's mali-cmar-backend 12 %) for an 80×14 px colour change.

The same growth, at ≈20 MB/min, is what took a production kiosk app (a screen with one repeating progress meter) to 5.7 GB in four hours. In that app, navigating to a static screen freezes [heap] to the byte within one sample and CPU drops to 0 %:

10:24:21 rss=727140 kB  heap=303552 kB
10:26:23 rss=726400 kB  heap=303552 kB   (flat for the rest of the run)

What is leaking

Reading /proc/<pid>/mem over the newly grown [heap] range of the production app and walking glibc chunks (in-use = next chunk's PREV_INUSE), live growth over 30 s (≈10.7 MB) is:

chunk size live chunks added in 30 s total
832 B +2119 1.7 MB
131088 B (128 KB) +13 1.6 MB
240 B +5964 1.4 MB
16400 B +35 0.6 MB
176 B +2317 0.4 MB
32 B +5555 0.2 MB

Every 240 B object holds a pointer into libmali.so at the same offset (one object type); the 128 KB blocks hold libmali pointers too; libflutter_linux_gtk.so pointers are negligible. Over the process lifetime the 193 MB heap is 185 MB live, dominated by these three sizes (128 KB ×439, 832 B ×33455, 240 B ×90025). The only large freed chunk is 5,356,816 B = 1080×1240×4+16, i.e. the per-frame CPU readback buffer, which is reused, not leaked.

So the leaked memory is the GLES driver's bookkeeping for GL objects created and deleted on the platform thread once per frame. The embedder path (3.41.8 fl_compositor_opengl.cc, shareable == TRUE on Wayland):

draw_cb (GTK main thread)
  → fl_compositor_opengl_render()
    → fl_framebuffer_create_sibling()      // glGenTextures + glEGLImageTargetTexture2DOES
                                           // + glGenFramebuffers + glFramebufferTexture2D
    → gdk_cairo_draw_from_gl(texture)      // GTK3 falls back to glReadPixels + cairo here
    → g_autoptr releases the sibling        // glDeleteTextures / glDeleteFramebuffers

The same build on an x86_64 Mesa (radeonsi) desktop presents at 60 fps through the same code and its [heap] stays flat. With FLUTTER_LINUX_RENDERER=software on the ARM device the growth disappears entirely (RSS flat at 273 MB over 3 min on a video screen that grows ≈5 MB/min under OpenGL), which matches richardexfo's observation in #182192 — but the software renderer has no external-texture callback, so Texture widgets render nothing, and CPU rasterising a 1240×1080 panel costs ~14 % of a core at 1 fps.

PR #183653 kept the sibling alive until the next frame instead of deleting it right after gdk_cairo_draw_from_gl, and its author reported that #183539 (EGLImage GObject) alone did not stop this leak. That matches what we see: it is the delete-while-still-referenced pattern per frame, not the EGLImage lifetime.

Part 2 — master's FlViewRendererSubsurface faults the GPU on the same device

Steps to reproduce

  1. Build the same app with master f1ac3fe4 (contains Add FlViewRendererSubsurface #191389, Fix black window in the subsurface view renderer #191850, [Linux] Use fences to synchronize frames between OpenGL contexts #192098, [Linux] Remove frame sharing between OpenGL contexts #192150). The engine selects FlViewRendererSubsurface for every Wayland display; there is no way to choose FlViewRendererOpenGL.
  2. Install and start under weston 10 kiosk-shell.
  3. Watch journalctl -k -f | grep gpu and take a compositor screenshot (weston-screenshooter).

Expected results

The window renders as it does with 3.41.8.

Actual results

  • Launches 1–3: Main process exited, code=killed, status=11/SEGV about 3 s after start (systemd restarts it).
  • Launch 4 onwards: process stays alive (21 threads, RSS 257 MB, 0 % CPU) but the screen is black. No output at all from the app or GTK, even with G_MESSAGES_DEBUG=all.
  • Kernel log, ~1700 lines within 2 s of the surviving instance starting (none before that day):
mali 27800000.gpu: Unhandled Page fault in AS1 at VA 0x0000000000000000
                   Reason: Memory is not mapped on the GPU
mali 27800000.gpu: error detected from slot 1, job status 0x00000004 (TERMINATED)
mali 27800000.gpu: t6xx: GPU fault 0x04 from job slot 1
mali 27800000.gpu: t6xx: GPU fault 0x04 from job slot 0
mali 27800000.gpu: error detected from slot 1, job status 0x00000042 (JOB_READ_FAULT)
mali 27800000.gpu: t6xx: GPU fault 0x42 from job slot 1
mali 27800000.gpu: t6xx: GPU fault 0x42 from job slot 0
(repeats)

AS1 is the second GPU address space, i.e. the Flutter process (weston holds AS0). The jobs read through a null/unmapped descriptor on both vertex and fragment slots. weston's log shows no client or protocol error. Downgrading to the 3.41.8 build restores the display immediately.

libwayland-egl.so.1 resolves to the driver's own copy; wl_egl_window_create and the shared EGL context are new in this path compared with the OpenGL renderer, so the fault is somewhere in "engine EGL context shared with a second context + wl_egl_window surface" on this driver. We have no user-space backtrace yet (no gdb on the device); we can add one if it helps.

Why this matters

Boards with ARM's proprietary Bifrost/Valhall GLES driver are common in kiosk/HMI products. On them:

  • every stable from 3.38 to 3.47 leaks per frame on Wayland through the sibling path;
  • master replaces that path with one that faults the GPU;
  • the only leak-free option, FLUTTER_LINUX_RENDERER=software, drops Texture support and burns CPU.

A build that keeps FlViewRendererOpenGL available on Wayland (or an env switch) together with the deferred sibling release from #183653 would give these devices a working path while the subsurface renderer is sorted out. Happy to test patches on this hardware and to provide a user-space backtrace or apitrace capture for either part.

Related

#182192, #183539, #183653, #183871, #184028, #192150, #191389, #191850, #192098, #73402 (2026-08-31 comment: Pi 5 / Mesa / X11 +4.4 MB/h at 1 Hz), #152099 / #166753 (ARM driver and glBlitFramebuffer).

Flutter Doctor output

flutter doctor is not available on the device (no SDK there); the builds come from an arm64 Linux CI runner. Versions from the build logs:

Flutter 3.41.8 • channel stable                                              (Part 1)
Flutter 3.48.0-1.0.pre-696 • channel [user-branch] • master f1ac3fe4
Engine d062219657405470009c3884acd719c82ea0d42b • Dart 3.14.0-214.0.dev       (Part 2)

Activity

  1. suryaKommana2662 commented on Sep 11, 2026

    @suryaKommana2662
    Contributor

    @ZhoufangErqiangu Thanks for the investigation and reproduction data. I noticed that the report was tested on Flutter 3.41.8 stable and a master/pre-release build. Could you please verify whether the issue is still reproducible on the latest stable Flutter release and provide the Flutter version used for that verification

  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    and removed
    in triagePresently being triaged by the triage team
    on Sep 11, 2026
  3. ZhoufangErqiangu commented on Sep 11, 2026

    @ZhoufangErqiangu
    Author

    Verified on the latest stable today, same board, same minimal sample as above (release builds from a Linux arm64 CI runner, flutter precache --linux engine artifacts):

    Flutter Engine Result on the device
    3.47.3 (latest stable) 06a2e2a110089dff50fe635cffd2a61e1b24fbcd Crashes 4 s after start, every launch — no frame is ever presented, so the leak cannot be measured on it
    3.44.9 5a2a6a42cce67f965cf540fcecf616faca624aa1 Runs; [heap] 47.8 MB → 177.5 MB in 281 s ≈ 27.7 MB/min, thread count constant (22), process CPU 82 % (UI/platform thread 75 %, raster 12 %)
    3.41.8 (stable) Runs; ≈ 27.5 MB/min — the numbers in the report

    The 3.47.3 crash is on the raster thread inside the GLES driver, with debug.exception-trace enabled:

    io.flutter.rast[274241]: unhandled exception: DABT (lower EL), ESR 0x0000000092000005,
        level 1 translation fault in libmali.so.1.9.0[7f8a650000+343d000]
    pc : 0000007f8a9a9bb8   (libmali.so.1.9.0 + 0x359bb8)
    lr : 0000007f8a9aa9fc
    

    systemd: Main process exited, code=killed, status=11/SEGV ×10 within 45 s, then the start limit stops it. No kernel GPU faults this time (unlike the master build), nothing on stderr even with G_MESSAGES_DEBUG=all. Rolling back to the 3.44.9/3.41.8 build restores the display immediately. engine/src/flutter/shell/platform/linux/fl_framebuffer.cc, fl_compositor_opengl.cc and fl_opengl_manager.cc have no GL-format changes between 3.44.5 and 3.47.3, so the regression is elsewhere in the raster path; I can capture a user-space backtrace (gdb on the device) if that would help narrow it.

    So on this driver: 3.41–3.44 leak per frame at the same rate through the sibling path (which is unchanged in 3.47.3: fl_compositor_opengl.cc lines 274–295 still create and delete the sibling texture every frame), 3.47.3 does not start, and master faults the GPU in the subsurface renderer

  4. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Sep 11, 2026
  5. added
    in triagePresently being triaged by the triage team
    platform-linuxBuilding on or for Linux specifically
    team-linuxOwned by the Linux platform team
    and removed
    in triagePresently being triaged by the triage team
    on Sep 11, 2026
  6. robert-ancell commented on Oct 16, 2026

    @robert-ancell
    Contributor

    @ZhoufangErqiangu can you try on master with Impeller disabled by setting fl_dart_project_set_enable_impeller(project, FALSE); in linux/runner/my_application.cc?

  7. added
    P1High-priority issues at the top of the work list
    triaged-linuxTriaged by the Linux platform team
    platform-linuxBuilding on or for Linux specifically
    team-linuxOwned by the Linux platform team
    and removed
    platform-linuxBuilding on or for Linux specifically
    P1High-priority issues at the top of the work list
    team-linuxOwned by the Linux platform team
    on Oct 16, 2026
  8. ZhoufangErqiangu commented on Oct 16, 2026

    @ZhoufangErqiangu
    Author

    @robert-ancell Tried it on our own RK3576 bench (same Mali-G52 Bifrost / libmali g24p0-00eac0 family as the report). Built master (framework 132e1c65bc, engine 5db6dbc8f3d4dd0de9d49f396310ea01af5d2bd5) with fl_dart_project_set_enable_impeller(project, FALSE) in my_application.cc.

    No crash, no GPU fault. Our full kiosk app ran clean under weston kiosk-shell — no SIGSEGV, dmesg had zero JOB_READ_FAULT/page-fault lines, NRestarts=0, and the compositor screenshot shows real rendered UI, not black. So the fault does look like it's coming from the Impeller backend, not the FlViewRendererSubsurface presentation path itself.

    And the leak is gone too. I re-ran the exact minimal repro from this issue (the Spinner/AnimationController.repeat() snippet) with the same build, sampling VmRSS/[heap] every 20 s for 5 minutes the same way:

    12:48:58 rss=168656kB heap=27728kB
    12:49:18 rss=168656kB heap=27728kB
    12:49:38 rss=168800kB heap=27732kB
    ...
    12:53:39 rss=168392kB heap=27732kB
    

    Flat — [heap] moved by ≤4 kB over the whole 5 minutes, versus +128.8 MB/281s (≈27.5 MB/min) for the identical repro on 3.41.8 stable reported above. top -H during the run showed io.flutter.raster and mali-cmar-backend continuously busy (not idle), so this isn't just a case of the animation silently not running — frames were being submitted and presented throughout.

    So on this driver it looks like disabling Impeller gets you both: no GPU fault and the per-frame leak is fixed (makes sense — the subsurface renderer doesn't do the create/delete-sibling-texture dance that was the original leak source).

    One separate thing I noticed, possibly unrelated: in both tests the GTK window didn't fill the 1080×1240 output on master — content was confined to a sub-region with black filling the rest. Haven't root-caused it yet (window/surface sizing against the compositor, not a crash), flagging in case it's a known regression.

    Happy to pull a fuller trace (WAYLAND_DEBUG, apitrace) if useful — let me know what would help narrow down the Impeller-specific fault.

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: desktopRunning on desktopplatform-linuxBuilding on or for Linux specificallyteam-linuxOwned by the Linux platform teamtriaged-linuxTriaged by the Linux platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions