Skip to content

[Flutter GPU] Make GLES command submission before first frame behave like Metal/Vulkan #187290

Description

@bdero

Steps to reproduce

This is an engine/Flutter GPU issue rather than a framework app bug.

  1. Run a Flutter GPU app on Android using the Impeller OpenGLES backend.
  2. Before the first Flutter frame has started building, create a Flutter GPU texture and call Texture.overwrite.
  3. Observe the GLES backend path that posts the overwrite work to the raster thread and submits the blit pass.

A minimal shape of the Dart-side operation is:

final texture = gpu.gpuContext.createTexture(
  gpu.StorageMode.hostVisible,
  2,
  2,
);
texture.overwrite(
  Int32List.fromList(<int>[
    0xffff0000,
    0xff00ff00,
    0xff00ff00,
    0xffff0000,
  ]).buffer.asByteData(),
);

The problematic timing is the important part: the upload is submitted before the raster thread has an available/current GLES context for the first Flutter frame.

Expected results

Flutter GPU command submission should have the same observable behavior across Impeller backends.

Submitting a Texture.overwrite before the first frame should be accepted and should execute once the backend can legally process the GPU work. This should match Metal and Vulkan, where queue submission does not depend on an EGL context being current on the raster thread at that moment.

Actual results

On the GLES backend, Texture.overwrite records a blit pass and attempts to submit it on the raster thread. GLES command buffer submission is implemented as CommandBufferGLES::OnSubmitCommands -> ReactorGLES::React(). ReactorGLES::React() returns false when no reactor worker can report that the current thread already has a current GL context.

Before the first Flutter frame, the Impeller GLES context object can exist while the raster thread still has no current onscreen GLES context. In that state, the blit pass submission can fail even though the operation is conceptually valid and should be able to run once the GLES context becomes available.

Relevant implementation points:

  • flutter/lib/gpu/texture.cc: Texture::Overwrite records a blit pass and calls context.GetCommandQueue()->Submit(...).
  • flutter/lib/gpu/command_buffer.cc: Flutter GPU special-cases GLES by posting submissions to the raster thread.
  • impeller/renderer/backend/gles/command_buffer_gles.cc: GLES submit is just reactor_->React().
  • impeller/renderer/backend/gles/reactor_gles.cc: React() returns false if no worker can react on the current thread.
  • shell/platform/android/android_context_gl_impeller.cc: the Android GLES worker only allows reactions after EGL lifecycle kDidMakeCurrent.
  • shell/platform/android/android_surface_gl_impeller.cc: the onscreen GL context is made current only once an onscreen surface exists; before first-frame setup, that may not have happened yet.

Metal and Vulkan differ structurally:

  • Metal creates an MTLCommandQueue up front and commits MTLCommandBuffers directly.
  • Vulkan creates command buffers and submits them to vkQueueSubmit without a per-thread current-context requirement.

Code sample

Code sample
import 'dart:typed_data';
import 'package:flutter_gpu/gpu.dart' as gpu;

void uploadBeforeFirstFrame() {
  final texture = gpu.gpuContext.createTexture(
    gpu.StorageMode.hostVisible,
    2,
    2,
  );
  texture.overwrite(
    Int32List.fromList(<int>[
      0xffff0000,
      0xff00ff00,
      0xff00ff00,
      0xffff0000,
    ]).buffer.asByteData(),
  );
}

Proposal / recommendation

We should solve this structurally in the GLES backend rather than requiring Flutter GPU callers to know about GLES context timing.

Recommended direction:

  1. Make GLES command submission mean "accepted by the backend for eventual reactor execution", not "synchronously reacted right now".
  2. Add a GLES-specific command queue or adjust CommandBufferGLES::OnSubmitCommands so React() == false because no worker/context is current is treated as deferred work, not failed submission.
  3. Add a completion sentinel operation to the reactor queue so Flutter GPU completion callbacks fire after queued GLES work actually drains.
  4. Distinguish "cannot react now" from "reacted and GL encoding failed". Today both collapse into false.
  5. Wake or post to the raster thread when deferred GLES work is queued and a worker later becomes able to react.

As a startup optimization, Android GLES could also prime the raster worker with a pbuffer-backed current context during SetupImpellerSurface when no native window exists yet. This would reduce first-use latency, but should be complementary rather than the only fix because the underlying footgun remains whenever code submits while no GL context is current.

The goal is for Flutter GPU to be backend-parity-safe: enqueueing command submissions before the first Flutter frame build has started should work on GLES just as it does on Metal and Vulkan.

Logs

Logs

No app-side logs attached. This issue is based on local engine investigation of the GLES submission path described above.

Flutter Doctor output

Doctor output

Not applicable; this is an upstream engine behavior issue in the Impeller GLES backend.

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listc: parityWorks on one platform but not anothere: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.flutter-gpuplatform-androidAndroid applications specificallyteam-fluttergpuOwned by Flutter GPU team

Type

No type
No fields configured for issues without a type.

Projects

Status
✅ Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions