Skip to content

About

Minimal Windows reproduction for flutter/flutter#190774 - snapshotting a GPU-surface external texture crashes under Impeller

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

Repository files navigation

Minimal reproduction — flutter/flutter#190774

Snapshotting a route transition while a GPU-surface external texture (kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle) is on screen kills the process under Impeller on Windows, with 0xC0000005 on a null read.

Reproduce it

flutter pub get
flutter run -d windows

The app registers a texture, pushes a route about half a second later, and dies on that first transition. flutter run prints Lost connection to device. No environment variables, no scripts, nothing else required.

To see the faulting stack instead of a silent exit, set one variable first:

$env:FLUTTER_REPRO_CRASH_TRACE = '1'
flutter run -d windows

That enables a small vectored exception handler in the runner (windows/runner/native_crash_reporter.cpp) which prints module+offset frames to stderr. It is off by default and is not part of the bug.

Expected: routes push and pop for 30 seconds, the app prints clean timeout reached; exiting normally, exits 0. Actual: access violation on the first route push, exit 0xC0000005.

This is not a teardown artifact, and the reason is structural rather than inferred from log timing: route push/pop performs no texture retirement at all. Unregistration exists only in the native plugin's destructor, so the texture and its producer are still live and registered throughout the transition that crashes.

Environment reproduced on

Item Value
Flutter 3.47.0-0.1.pre (beta)
Framework revision bd1e75d918605c91b411e8789fb911e6c9a84534
Engine revision bbd15867c003dc66e678cb3c218649fa8bf914f2
flutter_windows.dll SHA-256 7902667F7BE09E606AEC1A8679AD53EC02B1E25CA580F525C114FEE8AD697635
OS Windows 11 Pro build 26200 x64
GPU NVIDIA RTX 3080, driver 595.95 (32.0.15.9595)
Toolchain VS 2022 17.14.36, Windows SDK 10.0.26100.0
Backend Using the Impeller rendering backend (OpenGLESSDF)

Module offsets are only comparable within a single engine revision. Also reproduces on 3.47.0-0.3.pre (engine effb186f31…, same offsets) and on the current beta 3.47.0-0.4.pre (engine f88005a259…, whole chain shifted by exactly 0x400).

What it contains

The original case went through a third-party Vulkan renderer. That is replaced with plain D3D11 in the runner (windows/runner/external_texture_repro.cpp) — the smallest thing that still hands Flutter a DXGI shared-handle GPU surface. It preserves the contract that mattered:

  • allocates on Flutter's own DXGI adapter (GetGraphicsAdapter)
  • DXGI_FORMAT_B8G8R8A8_UNORM
  • D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE
  • IDXGIResource1::CreateSharedHandle
  • GpuSurfaceTexture(kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle)
  • descriptor, texture variant and shared handle retained for the texture's life
  • MarkTextureFrameAvailable from a worker thread at ~60 Hz

On the Dart side a Texture widget sits under a ZoomPageTransitionsBuilder with allowSnapshotting: true, and a loop pushes and pops a route every ~1.3 seconds. Dependencies are flutter, flutter_test, flutter_lints only; the texture is a 512×512 surface cleared to a cycling solid colour, with no scene or asset loading.

Synchronization, stated explicitly: the producer uses SHARED | SHARED_NTHANDLE without a keyed mutex, calling Flush after each clear. That is disclosed deliberately rather than left to be inferred. It is not believed to be the cause — the stack matches the null-context path already identified on this issue — but it is the kind of thing a reviewer should be able to see and judge.

Texture retirement uses the asynchronous UnregisterTexture(texture_id, callback) overload and retains the texture variant, descriptor, D3D resources and shared handle through that callback, so deprecated synchronous teardown is not a confound.

Stack

code          : 0xc0000005
faulting addr : flutter_windows.dll+0x2c51f7
operation     : READ at 0x0
  ...
  flutter_windows.dll+0x2c51f7
  flutter_windows.dll+0xeb4d1
  flutter_windows.dll+0xeaf0c
  flutter_windows.dll+0xeadbb
  ...
  flutter_windows.dll+0x5450fd

Below those frames the debugger shows a deeply recursive pattern alternating between two call sites, consistent with a layer-tree walk.

flutter_windows.dll+0x5450fd is reported upstream on this issue as LayerTree::Flatten at this engine revision. That attribution comes from the existing symbolized analysis, not from anything in this repository — the retained debugger log has no usable engine symbols and cannot confirm it.

Caveats: .ecxr returns Unable to get exception context, HRESULT 0x8000FFFF, so frames come from the break-in context at the first-chance AV. There are no public PDBs for flutter_windows.dll, so the debugger resolves every frame to the nearest exported symbol plus a huge offset; those names are meaningless and only module+offset is quoted. To capture a dump yourself, use .dump /mrR — /mp includes the process environment block.

Optional: the full control matrix

Everything above needs only flutter run. run_repro.ps1 exists to run the controls reproducibly and record evidence; it is tooling, not part of the bug.

.\run_repro.ps1 -DurationSeconds 30 -Clean
Snapshotting Continuous updates/marks Impeller Result Exit code
on on on crash, signature matched 0xC0000005
off on on control passed, 22 cycles 0
on off on control passed, 22 cycles 0
on on off control passed, 22 cycles 0

Each run used a 30-second configured timeout; the crashing run ends earlier. Each control completed 22 push/pop cycles, so they exercised the same path rather than failing to start, and the Impeller-off log contains zero Using the Impeller rendering backend lines. Logs are retained under logs\. Add -AllowAnyEngine to test a different SDK, which classifies on fault module and fault type rather than exact offsets.

Stated precisely: in one 30-second run per configuration, the crash was not observed when snapshotting, continuous updates/marks, or Impeller was disabled. That is weaker than "all three are required" — each dimension would need independent isolation and repeats first.

Two caveats about the -MarkFrames:$false control:

  • It is not "no frame marks". Create() always renders one frame and marks it once at registration; the flag only disables the subsequent ~60 Hz worker.
  • It changes two things at once — ongoing texture writes and ongoing frame notifications — so it cannot distinguish new pixel content from the cache invalidation MarkTextureFrameAvailable triggers.

One trap worth knowing if you write your own controls: flutter run does not propagate FLUTTER_ENGINE_SWITCHES to the application process. An "Impeller disabled" run launched through flutter run still starts Impeller and still crashes, which reads as "not Impeller-specific." My first Impeller-off control was invalid for exactly that reason. run_repro.ps1 builds and launches the .exe directly to avoid it, and fails if -ImpellerOff is requested but Impeller starts anyway. flutter run also reported exit code 0 after the app had died of an access violation, so classify from the log, not the CLI.

About

Minimal Windows reproduction for flutter/flutter#190774 - snapshotting a GPU-surface external texture crashes under Impeller

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages