Sitelet https://github.com/flutter/flutter/issues/191978
Skip to content

[Windows] Regression in 3.47.0: black window on GPUs limited to D3D11 feature level 9_3 — compositor passes sized internal formats on an ES 2.0 context #191978

Description

@gaabrielricci

Steps to reproduce

  1. Build any Flutter Windows app with Flutter 3.47.0 (or 3.47.1) in release mode. A stock flutter create app is enough — no app code is involved.
  2. Run it on a machine whose GPU driver caps D3D11 at feature level 9_3, so ANGLE falls back to its FL9_3 display.
    • Confirmed machine: Intel HD Graphics "Ironlake" (vendor=0x8086 device=0x0042), 64 MB, WDDM 1.1, no Vulkan, Windows 10.
  3. Look at the window.

Expected results

The app renders normally, as the identical source does when built with Flutter 3.44.2.

Actual results

The window is completely black, while the app is fully alive:

  • the engine starts and Dart runs — our own logging, HTTP and WebSocket code all execute normally;
  • input still works, hitting the right targets;
  • nothing is logged as an error — not by the engine, not by the framework.

Forcing either renderer makes no difference: black with Impeller and black with Skia (via DartProject::set_impeller_switch). That rules out the renderer default flip in 3.47.0 (#188140) as the cause and points at the shared Windows compositor.

Regression range

  • 3.44.2 — works. Field-validated: rebuilding the exact same app source with 3.44.2 and installing it on the affected end-user machine fixed it.
  • 3.47.0 / 3.47.1 — black.

Two commits rewrote engine/src/flutter/shell/platform/windows/compositor_opengl.cc in that window, neither reachable from 3.44.2 (verified with git tag --contains):

commit PR first release tag containing it
dda3f94c84e5614353d555af5fca89809d0f9f7c — [Impeller][Windows] fix black screen on OpenGL fallback #187288 3.46.0-0.1.pre
c7926ac4578db43158c42c8eb86be76e71913129 — [CP-beta][windows]: Uses offscreen MSAA when implicit msaa isn't available. #190374 (cherry-pick of #190256, c8a3c2bc3810) 3.47.0

Analysis

The Windows embedder only ever creates an OpenGL ES 2.0 context. shell/platform/windows/egl/manager.cc:

const EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};

There is no ES 3 attempt with an ES 2 fallback — it is ES 2.0 on every machine.

CompositorOpenGL::Initialize negotiates a color format behind a single extension check:

if (gl_->GetDescription()->HasExtension("GL_EXT_texture_format_BGRA8888")) {
  format_.sized_format   = GL_BGRA8_EXT;
  format_.general_format = GL_BGRA_EXT;
} else {
  format_.sized_format   = GL_RGBA8;
  format_.general_format = GL_RGBA;
}

c7926ac then switched the backing-store texture allocation from the unsized internal format to the sized one, in all three new branches — including the no-MSAA fallback, which is the branch weak drivers land in:

-  gl_->TexImage2D(GL_TEXTURE_2D, 0, format_.general_format, config.size.width,
-                  config.size.height, 0, format_.general_format,
+  gl_->TexImage2D(GL_TEXTURE_2D, 0, format_.sized_format, config.size.width,
+                  config.size.height, 0, format_.general_format,
                   GL_UNSIGNED_BYTE, nullptr);

That is why both renderers go black rather than only Impeller.

Two things look wrong on an ES 2.0 context:

  1. Sized internal formats are not valid for glTexImage2D in ES 2.0. ES 2.0 requires internalformat == format; sized internal formats for TexImage2D arrive with ES 3.0. GL_RGBA8 / GL_BGRA8_EXT as internalformat against GL_RGBA / GL_BGRA_EXT as format is out of spec for the context the engine actually created.
  2. The extension guard does not cover the token it gates. GL_EXT_texture_format_BGRA8888 defines the unsized GL_BGRA_EXT as an accepted internalformat; the sized GL_BGRA8_EXT token comes from EXT_texture_storage. So the presence of GL_EXT_texture_format_BGRA8888 does not establish that GL_BGRA8_EXT is a legal TexImage2D internalformat.

Same file, same ES 2.0 vs ES 3.0 mismatch, no capability guard:

  • RenderbufferStorageMultisample (core ES 3.0, no EXT/ANGLE suffix) is called in the supports_offscreen_msaa_ branch;
  • GL_DEPTH24_STENCIL8 is used unconditionally — on ES 2.0 that needs GL_OES_packed_depth_stencil;
  • format_.sized_format (GL_RGBA8) as a renderbuffer format on ES 2.0 needs GL_OES_rgb8_rgba8.

Why the fallback branch is the one that matters here. In capabilities_gles.cc, both MSAA flags stay false on an ES 2.0 context without GL_EXT_multisampled_render_to_texture:

if (desc->HasExtension(kMultisampledRenderToTextureExt)) {
  supports_implicit_msaa_ = true;
  ...
} else if (desc->GetGlVersion().major_version >= 3 && desc->IsES()) {
  ...  // never taken: the context is ES 2.0
}

So the affected machine takes CreateBackingStore's plain-texture else branch — the path deliberately written for drivers that cannot do MSAA — and that branch is exactly the one that regressed from unsized to sized internalformat.

Hypothesis for why only some machines break: ANGLE's D3D11 backend at a full feature level supports a wide format set and tolerates these calls; the FL9_3 fallback has a reduced set. There the texture is never allocated, the FBO ends up incomplete, and the blitFramebuffer in CompositorOpenGL::Present copies nothing → black window over a perfectly healthy app. This last step is the part I could not verify directly (see What I could not capture).

Secondary issue: the failure is completely silent. compositor_opengl.cc calls neither glGetError nor CheckFramebufferStatus after building the backing store (git grep finds zero occurrences of either in that file). A failed allocation therefore produces a black window with no diagnostic at all, which is what made this take days to narrow down across end-user machines. A CheckFramebufferStatus + FML_LOG(ERROR) after CreateBackingStore would turn this whole class of bug into one log line.

Suggested fixes

  • Pass format_.general_format as the TexImage2D internalformat on the ES 2.0 context (restoring pre-c7926ac behavior), or request an ES 3 context in egl/manager.cc with a fallback to ES 2 and choose sized vs. unsized formats from the context version actually obtained;
  • guard GL_DEPTH24_STENCIL8, the GL_RGBA8 renderbuffer format, and RenderbufferStorageMultisample on the corresponding ES 2.0 extensions;
  • independently of the fix: check and log CheckFramebufferStatus after creating the backing store.

What I could not capture

I do not have the affected machine on hand — it belongs to an end user of our product — so I have no glGetError / CheckFramebufferStatus output or ANGLE debug log from it, and I could not test against master. What I do have is the version bisect (3.44.2 works, 3.47.0 black, same source), the machine's D3D11 feature level, and the fact that both renderers are black. I can ship a diagnostic build to that machine if a maintainer tells me exactly what to capture. The workaround build is already installed and working on that machine, so re-capturing anything from the broken build means putting a machine in daily production use back onto it — I would rather spend that disruption once, on a targeted diagnostic build, than on a screenshot.

Code sample

No app code is involved — a default flutter create Windows app reproduces it on affected hardware. The distinguishing factor is the machine, not the app.

Screenshots or Video

Screenshots / Video demonstration Image

About this image: it is the only one I have — a low-quality image the customer sent at the start of the support session, before we knew what we were looking at. I could not reproduce this on my own hardware; it happens only on that customer's machine, which I have no physical access to.

I cannot capture a sharper one either: the workaround (a 3.44.2 build) is already installed and working there, so re-capturing the black window would mean putting a machine in daily production use back onto the broken build. If a maintainer needs something specific from that hardware, I would rather spend that disruption on a targeted diagnostic build than on a screenshot — just tell me what to instrument.

Logs

Our runner writes a startup probe on every launch (it calls D3D11CreateDevice itself to record the achieved feature level, because the adapter name alone is ambiguous — "Intel HD Graphics" is both a 2010 part stuck at 9_3 and a 2015 part at 11_1).

Affected machine (abridged, translated from pt-BR):

GPU 0: Intel(R) HD Graphics | vendor=0x8086 device=0x0042 | VRAM 64 MB
Direct3D: D3D11 hardware OK, feature level 9_3
window created, entering message loop

…followed by normal application logs (session restored, WebSocket connected) with an entirely black window.

A working machine for contrast, same app, same build:

GPU 0: Intel(R) Iris(R) Xe Graphics | vendor=0x8086 device=0x9A49 | VRAM 128 MB
Direct3D: D3D11 hardware OK, feature level 11_1

Flutter Doctor output

Our build host is pinned back to 3.44.2 as the workaround, so this is the doctor output of the working configuration; the failing builds were produced with 3.47.0 on this same host.

[!] Flutter (Channel [user-branch], 3.44.2, on Microsoft Windows [Version 10.0.26200.9168], locale pt-BR)
    ! Flutter version 3.44.2 on channel [user-branch] at C:\flutter\flutter
      (pinned to the 3.44.2 tag deliberately, as the workaround for this bug)
    • Framework revision c9a6c48423 (2026-06-10)
    • Engine revision 77e2e94772
    • Dart version 3.12.2
    • DevTools version 2.57.0
[√] Windows Version (Windows 11 or higher, 25H2, 2009)
[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0-rc1)
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.14.13)
    • Visual Studio Community 2022 version 17.14.36414.22
    • Windows 10 SDK version 10.0.26100.0
[√] Connected device (3 available)
    • Windows (desktop) • windows • windows-x64
[√] Network resources

Activity

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 desktopc: renderingUI glitches reported at the engine/skia or impeller rendering levele: device-specificOnly manifests on certain devicesfound in release: 3.47Found to occur in 3.47platform-windowsBuilding on or for Windows specificallyteam-windowsOwned by the Windows platform teamtriaged-windowsTriaged by the Windows platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions