Skip to content

[Impeller] Vulkan synchronization validation reports three hazards in BlitPassVK and RenderPassBuilderVK #194987

Description

@jpohhhh

Steps to reproduce

  1. Run a Flutter app with Impeller on Vulkan, with VK_LAYER_KHRONOS_validation enabled and synchronization validation on (VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, VK_VALIDATION_VALIDATE_SYNC=1).
  2. Start the app.

Found on a Raspberry Pi 5 / CM5 (V3D 7.1, Mesa 26.2.1, validation layer 1.4.309) running Impeller Vulkan through a custom embedder. All three messages appeared within about 30 seconds of every launch.

Expected results

No synchronization hazards reported.

Actual results

Three hazards logged at startup.

  1. BlitPassVK::OnCopyTextureToBufferCommand: the barrier that moves the source image to TransferSrc names shader reads in the vertex and fragment stages as its destination. The next access is vkCmdCopyImageToBuffer, a transfer read.
  2. BlitPassVK::OnGenerateMipmapCommand: the barrier that moves all mip levels to TransferDst allows only transfer reads. The next accesses are vkCmdBlitImage writes into levels 1..N.
  3. RenderPassBuilderVK::Build: the incoming VK_SUBPASS_EXTERNAL dependency covers only color attachment output and fragment shader stages. A depth/stencil attachment's clear runs in the fragment test stages, so it isn't ordered after the attachment's layout transition.

Code sample

The hazards come from the engine's barrier setup, not from app code. In our app all three appeared at every launch. The readback and mipmap hazards need a texture readback and a mipmapped texture respectively, which our app performs at startup. (I'm not sure why, frankly, it's not explicit and I'm not familiar enough with graphics to know what those are)

Screenshots or Video

Logs

Logs
Validation Error: [ SYNC-HAZARD-READ-AFTER-WRITE ] vkCmdCopyImageToBuffer(): READ_AFTER_WRITE hazard detected. vkCmdCopyImageToBuffer reads VkImage 0x…, which was previously written during an image layout transition initiated by vkCmdPipelineBarrier. The current synchronization allows VK_ACCESS_2_SHADER_READ_BIT accesses at VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT|VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, but to prevent this hazard, it must allow VK_ACCESS_2_TRANSFER_READ_BIT accesses at VK_PIPELINE_STAGE_2_COPY_BIT.

Validation Error: [ SYNC-HAZARD-WRITE-AFTER-WRITE ] vkCmdBlitImage(): WRITE_AFTER_WRITE hazard detected. vkCmdBlitImage writes to VkImage 0x…, which was previously written during an image layout transition initiated by vkCmdPipelineBarrier. The current synchronization allows VK_ACCESS_2_TRANSFER_READ_BIT accesses at VK_PIPELINE_STAGE_2_COPY_BIT|VK_PIPELINE_STAGE_2_RESOLVE_BIT|VK_PIPELINE_STAGE_2_BLIT_BIT, but to prevent this hazard, it must allow VK_ACCESS_2_TRANSFER_WRITE_BIT accesses at VK_PIPELINE_STAGE_2_BLIT_BIT.

Validation Error: [ SYNC-HAZARD-WRITE-AFTER-WRITE ] vkCmdBeginRenderPass(): WRITE_AFTER_WRITE hazard detected: attachment loadOp access is not synchronized with the attachment layout transition. vkCmdBeginRenderPass clears the depth aspect of attachment 2 in subpass 0 (VkImageView 0x…, loadOp VK_ATTACHMENT_LOAD_OP_CLEAR), which was previously written during an image layout transition initiated by the same command. The current synchronization allows VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT accesses at VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, but to prevent this hazard, it must allow VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT accesses at VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT.

Flutter Doctor output

Doctor output

This is the development machine. The hazards were found on a Raspberry Pi 5 / CM5 running Linux arm64, where the engine (built from this same 3.47.1 revision, 5d53178869) runs under a custom embedder (flutter-pi), so flutter doctor there wouldn't show the device either. The code involved is unchanged on master.

[✓] Flutter (Channel stable, 3.47.1, on macOS 15.7.1 24G231 darwin-arm64, locale en-US) [243ms]
    • Flutter version 3.47.1 on channel stable at /Users/jpo/dev/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 6655482ec0 (9 weeks ago), 2026-08-19 10:07:23 -0700
    • Engine revision 5d53178869
    • Dart version 3.13.1
    • DevTools version 2.60.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, enable-record-use, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration, enable-macos-arm64-only

[!] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [1,636ms]
    • Android SDK at /Users/jpo/Library/Android/sdk
    • Emulator version 35.5.10.0 (build_id 13402964) (CL:N/A)
    • Platform android-36, build-tools 36.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
      This JDK is specified in your Flutter configuration.
      To change the current JDK, run: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13368085-b895.109)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

[✓] Xcode - develop for iOS and macOS (Xcode 26.0.1) [1,659ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 17A400
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [7ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (3 available) [10.2s]
    • iPhone (wireless) (mobile) • <redacted> • ios            • iOS 26.6.2 23G90
    • macOS (desktop)                    • macos                     • darwin-arm64   • macOS 15.7.1 24G231 darwin-arm64
    • Chrome (web)                       • chrome                    • web-javascript • Google Chrome 155.0.8059.85

[✓] Network resources [300ms]
    • All expected network resources are available.

! Doctor found issues in 1 category.

Activity

  1. suryaKommana2662 commented on Oct 22, 2026

    @suryaKommana2662
    Contributor

    @jpohhhh Thanks for the detailed report and synchronization validation logs. I couldn't verify the issue in my Chromebook Linux container, as the required Vulkan validation environment is not currently available. Routing this to the team-engine for further investigation.

  2. added
    engineflutter/engine related. See also e: labels.
    e: impellerImpeller rendering backend issues and features requests
    and removed
    in triagePresently being triaged by the triage team
    on Oct 22, 2026
  3. added
    P1High-priority issues at the top of the work list
    on Oct 26, 2026
  4. gaaclarke commented on Oct 26, 2026

    @gaaclarke
    Member

    We should test this out to see if it's legitimate or a false positive. Ideally things like this would get flagged automatically on CI.

  5. andywolff commented on Oct 27, 2026

    @andywolff
    Contributor

    Synchronization validation is disabled by default, so SYNC-HAZARD-* warnings are never generated. We could enable it in CI by setting VK_VALIDATION_VALIDATE_SYNC=1 or VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT, but I gather that the reason it's disabled by default is that it adds a lot of CPU cost (AI suggests it could be a 5x-10x slowdown). In @jpohhhh 's PRs they add individual unit tests which validate behavior on mocks, which will catch regressions in those specific areas. But it won't catch undiscovered sync hazards. Perhaps we should set up a new CI shard with sync validation enabled which runs a set of tests we select which exercise the areas of engine code most likely to contain sync hazards

  6. andywolff commented on Oct 28, 2026

    @andywolff
    Contributor

    Filed #195502 with my recommendation on how to cover this sort of thing

  7. piotrbobinski-droid commented on Nov 4, 2026

    @piotrbobinski-droid

    This hazard is visible on real hardware, not just under sync validation: hazard 1 (OnCopyTextureToBufferCommand) corrupts pixels on a Mali GPU.

    Device: Samsung Galaxy Tab Active5 (Exynos 1380, Mali-G68 r38p1), Android 16, Impeller Vulkan, Flutter master 7b0a985e54 (3.50.0-1.0.pre-209).

    What we see: an app renders text glyphs with Picture.toImage() and reads them back right away with Image.toByteData(format: ImageByteFormat.rawRgba). A meaningful share of those first reads come back wrong, in three shapes:

    • all zeros;
    • stale: every pixel comes from an earlier, same-size toImage render;
    • torn: a mix of regions from several earlier renders, or zero regions next to old ones.

    To tell them apart, we stamped a per-render nonce into the green and blue channels of every pixel (through a colour-matrix saveLayer) and checked the whole read-back:

    Run First reads Bad Kinds Second read of the same ui.Image, ~1 frame later
    A 36 ≥ 7 (≥ 19%)* 4 stale, 3 torn (one partly zero) all 7 correct
    B 100 23 (23%) not broken down all 23 correct

    * Run A used an earlier version of the stamp, whose consecutive nonces were too close to tell a read of the immediately preceding render apart. Its count is a lower bound. Run B's stamp detects reads of any of the previous 83 renders.

    The image itself is fine: sampling it on the GPU (flutter_gpu Texture.fromImage) never showed the problem over hundreds of renders. A second read a frame later always matched. (Caveat: Texture.fromImage currently runs vkDeviceWaitIdle while it wraps the image, through the throwaway AiksContext in GetTextureFromImage. That observation shows the render completes correctly, not that sampling is synchronized on its own.) Both fit the barrier described above: copyImageToBuffer runs in the transfer stage, but the barrier before it only orders shader reads (dst_stage vertex|fragment, dst_access shader read). So the copy can run before the render's writes and the layout transition land.

    On Adreno 810 (also Vulkan) and Adreno 512 (OpenGL ES) the same code never read bad data.

    Our workaround until #194991 lands: validate every read-back against the nonce, and re-read after a frame when it doesn't match.

  8. jpohhhh commented on Nov 5, 2026

    @jpohhhh
    ContributorAuthor

    @piotrbobinski-droid The readback fix landed in #194991 (merged Nov 5), so toImage → toByteData on Vulkan should be fixed on master now.

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 liste: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.team-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions