Repository navigation
Description
Steps to reproduce
ConfigureFBO in engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc decides how a texture is bound for a blit:
if (TextureGLES::Cast(*texture).IsWrapped()) {
// The texture is attached to the default FBO, so there's no need to
// create/configure one.
gl.BindFramebuffer(fbo_type, 0);
return 0;
}IsWrapped() is true for two different kinds of texture. TextureGLES::WrapFBO wraps a framebuffer: the onscreen surface, where binding framebuffer 0 is what the comment describes, or a caller's own FBO, where it binds the wrong one. TextureGLES::WrapTexture wraps an existing GL texture name, as ImpellerTextureCreateWithOpenGLTextureHandleNew in the interop toolkit does; such a texture has no framebuffer at all, and GetFBO() returns std::nullopt. The blit binds framebuffer 0 for both.
This can't be reproduced from Dart today: Flutter GPU doesn't take a GL texture it didn't create, so a wrapped texture name never reaches it. On main the path is open through the interop toolkit (ImpellerTextureCreateWithOpenGLTextureHandleNew) and skwasm (images_impeller.cc), and from Dart it opens with texture import (#194142), which is where I ran into it. So the repro is at the HAL level:
- Wrap a GL texture name with known contents with
TextureGLES::WrapTexture(same share group). - Encode a texture-to-buffer copy from it into a host-visible buffer and submit.
- Read the buffer.
The code sample below does this at the HAL level. With the mock GL proc table in impeller_unittests the same thing shows as glBindFramebuffer(GL_FRAMEBUFFER, 0) followed by glReadPixels, with no glFramebufferTexture2D for the texture name.
Expected results
The buffer holds the wrapped texture's pixels: the texture name is attached to a temporary framebuffer and read from there, as for any texture the engine made. A wrapped framebuffer other than 0 is read from that framebuffer.
Actual results
The buffer holds the pixels of whatever is bound as framebuffer 0, and the copy reports success, with no error or validation message. A texture-to-texture blit with a wrapped texture name as source or destination reads or writes framebuffer 0 the same way. RenderPassGLES has the same confusion: with a wrapped texture name as its color attachment it binds no framebuffer, turns off the y flip and draws into whatever is bound.
Suggested fix
Tell the two apart with a TextureGLES::IsWrappedFBO(). A wrapped framebuffer binds the FBO it wraps, and the caller's FBO is never deleted; a wrapped texture name is read through a temporary FBO.
I'd keep it read-only. Uploads and SetContents already refuse wrapped textures, so a blit into a wrapped texture name, or a render pass that uses one as its color attachment, should be refused too, with a validation error, instead of writing into the wrong framebuffer. Writing into textures the caller owns is what #192059 is about, and this issue doesn't try to cover it. I found the bug while reading back imported GL textures for #194142.
Code sample
HAL-level reproduction
GLuint name = /* a GL_TEXTURE_2D with known contents, same share group */;
TextureDescriptor desc;
desc.format = PixelFormat::kR8G8B8A8UNormInt;
desc.size = {16, 16};
desc.usage = TextureUsage::kShaderRead;
auto texture = TextureGLES::WrapTexture(
reactor, desc, reactor->CreateHandle(HandleType::kTexture, name));
auto buffer = context->GetResourceAllocator()->CreateBuffer(
{StorageMode::kHostVisible, 16 * 16 * 4});
auto cmd = context->CreateCommandBuffer();
auto blit = cmd->CreateBlitPass();
blit->AddCopy(texture, buffer);
blit->EncodeCommands();
context->GetCommandQueue()->Submit({cmd});
// buffer now holds the default framebuffer's pixels, not `name`'s.Flutter Doctor output
Found in an engine built from source: macOS arm64, host_debug_unopt_arm64, engine at f1ac3fe4b34; the code is the same on current main.
Doctor output
[✓] Flutter (Channel stable, 3.47.0, on macOS 27.2 26B5091g darwin-arm64)
• Framework revision 4cf2416426, 2026-08-11
• Engine revision 5f77625673
• Dart version 3.13.0
[✓] Xcode - develop for iOS and macOS (Xcode 27.0)
Activity
puneetkukreja98 commentedon Oct 12, 2026
@dzolotov Thanks for reporting this, routing this to team-engine for further investigation.
gaaclarke commentedon Oct 12, 2026
My understanding is that this issue is hypothetical. We currently don't accept issues against hypothetical usages. If you can come up with a reproduction, let us know.
github-actions commentedon Oct 26, 2026
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 -vand a minimal reproduction of the issue.