Steps to reproduce
This is an engine/Flutter GPU issue rather than a framework app bug.
- Run a Flutter GPU app on Android using the Impeller OpenGLES backend.
- Before the first Flutter frame has started building, create a Flutter GPU texture and call
Texture.overwrite.
- 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:
- Make GLES command submission mean "accepted by the backend for eventual reactor execution", not "synchronously reacted right now".
- 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.
- Add a completion sentinel operation to the reactor queue so Flutter GPU completion callbacks fire after queued GLES work actually drains.
- Distinguish "cannot react now" from "reacted and GL encoding failed". Today both collapse into
false.
- 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.
Steps to reproduce
This is an engine/Flutter GPU issue rather than a framework app bug.
Texture.overwrite.A minimal shape of the Dart-side operation is:
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.overwritebefore 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.overwriterecords a blit pass and attempts to submit it on the raster thread. GLES command buffer submission is implemented asCommandBufferGLES::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::Overwriterecords a blit pass and callscontext.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 justreactor_->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 lifecyclekDidMakeCurrent.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:
MTLCommandQueueup front and commitsMTLCommandBuffers directly.vkQueueSubmitwithout a per-thread current-context requirement.Code sample
Code sample
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:
CommandBufferGLES::OnSubmitCommandssoReact() == falsebecause no worker/context is current is treated as deferred work, not failed submission.false.As a startup optimization, Android GLES could also prime the raster worker with a pbuffer-backed current context during
SetupImpellerSurfacewhen 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.