[Impeller] Add Vulkan rendering backend for Linux and Windows desktops#183382
[Impeller] Add Vulkan rendering backend for Linux and Windows desktops#183382sero583 wants to merge 1 commit into
Conversation
Add Vulkan rendering support for Flutter desktop applications on Linux
and Windows. This uses Impeller's existing KHR swapchain codepath (the
same path used by Android) so the engine manages the swapchain
internally, avoiding duplicated swapchain state in platform code.
Includes comprehensive Vulkan backend stability fixes discovered during
sustained stress testing on AMD RDNA 2 hardware (Radeon RX 6750 XT).
Linux Platform Integration:
- fl_vulkan_manager.{cc,h}: Vulkan instance, device, queue, and surface
management for GTK. Supports X11 and Wayland via GDK with Wayland
subsurface positioning for header-bar-aware rendering.
- fl_engine.cc: FLUTTER_LINUX_RENDERER=vulkan env var support with
graceful fallback to OpenGL. Auto-enables Impeller when Vulkan is
selected (no Skia+Vulkan desktop path exists).
- fl_view.cc: Vulkan mode setup, first-frame workaround, subsurface
geometry updates. No compositor in Vulkan mode -- Impeller manages
the swapchain directly via KHR swapchain.
- vulkan_native_surface_linux.{cc,h}: VulkanNativeSurface for X11
(VK_KHR_xlib_surface) and Wayland (VK_KHR_wayland_surface).
Windows Platform Integration:
- vulkan_manager.{cc,h}: VkInstance/VkDevice/VkSurfaceKHR lifecycle for
Win32 (VK_KHR_win32_surface). Discrete GPU preference, validation
layer support gated behind FLUTTER_VULKAN_VALIDATION=1.
- flutter_windows_engine.cc: Vulkan renderer selection, KHR swapchain
config, --impeller-backend=vulkan flag injection. EGL fallback when
Vulkan surface creation fails.
- flutter_window.cc: WM_ACTIVATE handler with InvalidateRect for
swapchain recovery after occlusion.
Embedder Integration:
- embedder.h: New FlutterVulkanSurfaceHandle typedef and surface field
in FlutterVulkanRendererConfig (backward-compatible via struct_size).
- embedder_surface_vulkan_impeller.{cc,h}: KHR swapchain mode when
VkSurfaceKHR is provided. Conditional VK_EXT_debug_utils detection.
Initial surface extent query with Wayland fallback.
- embedder_engine.{cc,h}: SetSurfaceSizeUpdater posts resize to raster
thread on SetViewportMetrics.
Vulkan Rendering Stability:
- blit_pass_vk.cc: Fix six barriers -- pre-copy barriers use correct
eTransfer/eTransferWrite stages, post-copy barriers flush transfer
write caches, mipmap barriers match actual current layout. Fixes
corrupted atlas glyphs and SYNC-HAZARD-WRITE-AFTER-WRITE on AMD RDNA.
- render_pass_builder_vk.cc: Conditionally include depth/stencil stages
and access flags in subpass dependencies when depth attachment present.
Remove eByRegion from external dependencies (meaningless per Vulkan
spec section 7.1 for VK_SUBPASS_EXTERNAL).
- khr_swapchain_impl_vk.cc: Re-signal synchronizer fence on failed
acquireNextImageKHR. Prevents raster thread deadlock with
kMaxFramesInFlight=2. Short-circuit if device is marked lost.
- khr_swapchain_vk.cc: Zero-extent surface guard for minimized windows.
- gpu_surface_vulkan_impeller.{cc,h}: Frame throttling fences
(kMaxFramesInFlight=2) with timeout-based skip. Present barrier.
Resize fence drain before destroying cached image views.
Memory Management:
- allocator_vk.cc: Staging buffer pool cap (maxBlockCount=256, 1 GB).
Working set trim on Windows via EmptyWorkingSet() when RSS > 1 GB and
> 4x VMA usage (300-frame cooldown). Reclaims Resizable BAR pages.
- render_target_cache.cc/h: Hard cap of 256 entries with stable
partition eviction (used-this-frame first, unused FIFO).
Resource Lifecycle Safety:
- command_pool_vk.{cc,h}: AbandonForDriverCrash() releases handles
without Vulkan API calls. Buffer release before resetCommandPool.
Recycled pool eviction oldest-first, cap 8. IPLR_GUARDED_BY
annotation for unused_command_buffers_. context_ stored by value.
- command_queue_vk.{cc,h}: On vkQueueSubmit failure: abandon tracked
objects, mark device lost, log heap budgets, release fence. Chunked
submission in batches of 64.
- context_vk.{cc,h}: Atomic device_lost_ flag with IsDeviceLost()/
MarkDeviceLost(). CreateCommandBuffer() short-circuits when lost.
Chunked flush (64 per batch).
- tracked_objects_vk.{cc,h}: AbandonForDriverCrash() releases command
buffer handle, abandons pool, releases descriptor pools.
- descriptor_pool_vk.{cc,h}: AbandonForDriverCrash(). vkResetDescriptor-
Pool on reclaim (previously stayed at capacity causing
VK_ERROR_OUT_OF_POOL_MEMORY on every reuse).
Vulkan Infrastructure:
- vulkan_proc_table.{cc,h}: DestroySurfaceKHR, CreateWin32SurfaceKHR,
CreateXlibSurfaceKHR, CreateWaylandSurfaceKHR procs. Swapchain procs
shared across Android/Linux/Windows.
- vulkan_interface.h: Platform surface extension defines for Win32,
X11, Wayland.
- capabilities_vk.cc: Enumerate instance layers in embedder mode.
- workarounds_vk.{cc,h}: Mesa dzn sub-region copy workaround.
- driver_info_vk.{cc,h}: GetDriverID() for driver-specific detection.
dart:ui API:
- RenderingBackend enum (opengl, vulkan, software, metal, canvaskit,
skwasm) and PlatformDispatcher.renderingBackend getter on both native
and web. Engine sets the value via Dart_SetField during init.
- Framework-level defaultRenderingBackend getter in foundation library.
Build and Tooling:
- linux/BUILD.gn: Vulkan sources and dependencies.
- windows/BUILD.gn: Vulkan manager sources and test.
- vulkan/BUILD.gn: Linux native surface sources.
- runtime/dart_vm.cc: Platform::Initialize() signature fix.
Validated on AMD Radeon RX 6750 XT (RDNA 2, 12 GB, Resizable BAR,
Windows 10 Pro) and Mesa dzn (D3D12 translation via WSL2). All 9
benchmark scenes (https://github.com/sero583/flutter-benchmark) complete
without crash, deadlock, or validation error under
VK_LAYER_KHRONOS_validation.
Fixes: flutter#181711
There was a problem hiding this comment.
Code Review
This pull request introduces Vulkan rendering support for Flutter on Linux and Windows desktops by integrating Impeller's existing Vulkan backend. The changes are extensive, spanning the embedder API, platform-specific Vulkan managers, and numerous stability and performance enhancements in the Impeller backend itself. Key improvements include robust resource management through frame throttling and pool capping, workarounds for specific driver issues like Mesa's dzn, and a new framework-level API to expose the active rendering backend. The implementation appears thorough and well-executed. My review includes a couple of suggestions for improving robustness and code clarity.
| // budget_props.heapBudget[i] == 0 means the extension data wasn't | ||
| // filled (driver too old or extension not exposed). In that case we | ||
| // fall back to just logging the static heap size. | ||
| bool has_budget = budget_props.heapBudget[0] != 0; |
There was a problem hiding this comment.
Relying on heapBudget[0] != 0 to detect if the VK_EXT_memory_budget extension is active seems a bit fragile. While a heap budget of zero is unlikely for a usable heap, it's not strictly impossible for a driver to report that for the first heap. A more robust approach would be to directly check if the extension was enabled when the context was created.
You can get the capabilities from the context and check for the extension's presence, which would be a more direct and reliable signal.
| bool has_budget = budget_props.heapBudget[0] != 0; | |
| bool has_budget = context->GetCapabilities()->HasExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); |
| #ifndef FLUTTER_RELEASE | ||
| // Honor explicit backend request in debug/profile builds. | ||
| // Vulkan is gated to debug/profile until the implementation stabilizes. | ||
| // TODO(sero583): Enable Vulkan in release builds once validated. | ||
| bool vulkan_requested = false; | ||
| for (const auto& sw : switches) { | ||
| if (sw == "--impeller-backend=vulkan") { | ||
| vulkan_requested = true; | ||
| break; | ||
| } | ||
| } | ||
| #else | ||
| bool vulkan_requested = false; | ||
| #endif // FLUTTER_RELEASE | ||
|
|
There was a problem hiding this comment.
This preprocessor block can be simplified for better readability and to avoid redundancy. The vulkan_requested variable can be initialized to false once, and the #else block, which just duplicates this initialization, can be removed entirely.
| #ifndef FLUTTER_RELEASE | |
| // Honor explicit backend request in debug/profile builds. | |
| // Vulkan is gated to debug/profile until the implementation stabilizes. | |
| // TODO(sero583): Enable Vulkan in release builds once validated. | |
| bool vulkan_requested = false; | |
| for (const auto& sw : switches) { | |
| if (sw == "--impeller-backend=vulkan") { | |
| vulkan_requested = true; | |
| break; | |
| } | |
| } | |
| #else | |
| bool vulkan_requested = false; | |
| #endif // FLUTTER_RELEASE | |
| bool vulkan_requested = false; | |
| #ifndef FLUTTER_RELEASE | |
| // Honor explicit backend request in debug/profile builds. | |
| // Vulkan is gated to debug/profile until the implementation stabilizes. | |
| // TODO(sero583): Enable Vulkan in release builds once validated. | |
| for (const auto& sw : switches) { | |
| if (sw == "--impeller-backend=vulkan") { | |
| vulkan_requested = true; | |
| break; | |
| } | |
| } | |
| #endif // FLUTTER_RELEASE |
| /// * [RenderingBackend], the enum of possible rendering backends. | ||
| RenderingBackend get renderingBackend { | ||
| return RenderingBackend._fromIndex(_renderingBackend); | ||
| } |
There was a problem hiding this comment.
Can we punt on exposing the rendering backend? AFAICT this is not necessary for a minimum viable product. We will likely want a separate conversation on whether we should add this or not.
| FlutterDesktopRendererSoftware = 1, | ||
| // Vulkan rendering via Impeller. | ||
| FlutterDesktopRendererVulkan = 2, | ||
| } FlutterDesktopRendererType; |
There was a problem hiding this comment.
Instead of adding a public API, let's use flags for now similar to how Flutter Windows lets you opt-in to the Impeller preview:
flutter/packages/flutter_tools/lib/src/desktop_device.dart
Lines 297 to 303 in dc7ed64
| // Gets the rendering backend used by the engine. | ||
| // Valid after the engine has been created. | ||
| FLUTTER_EXPORT FlutterDesktopRendererType | ||
| FlutterDesktopEngineGetRenderingBackend(FlutterDesktopEngineRef engine); |
| #ifndef FLUTTER_RELEASE | ||
| // Honor explicit backend request in debug/profile builds. | ||
| // Vulkan is gated to debug/profile until the implementation stabilizes. | ||
| // TODO(sero583): Enable Vulkan in release builds once validated. |
There was a problem hiding this comment.
Let's do the same thing as Impeller above. You can't set these flags in the apps you deploy using flutter build windows.
|
@sero583 Thanks for opening this pull request! Because this is such a significant feature, I want to make sure it gets the right eyes on it. To help with that, could you please:
Just to set realistic expectations, landing a change of this magnitude is a significant undertaking. We're very grateful for the effort, but please be prepared for a rigorous review cycle. |
|
|
||
| // |WindowBindingHandler| | ||
| virtual void SetFlutterCursor(HCURSOR cursor) override; | ||
|
|
| // The current cursor set by the Flutter framework. Defaults to the standard | ||
| // arrow cursor. Restored in WM_SETCURSOR to prevent stale cursors from | ||
| // persisting when the mouse re-enters the client area from non-client areas. | ||
| HCURSOR current_cursor_ = LoadCursor(nullptr, IDC_ARROW); |
There was a problem hiding this comment.
Why is LoadCursor part of this PR?
|
This has a very vibe-coded feel to it. How much of this was written by hand? Also while vulkan support makes sense on Linux, I'm not entirely sure about windows. |
|
Also I don't think we want to go with the swapchain approach? This definitely needs more discussion, but in general swapchain makes it difficult/impossible to have resize synchronization and compositor integration. I also noticed the wayland subsurface is desync on linux, definitely a problem for resizing. If we ever have platform view on windows swapchain will be a problem. Ideally we want to render to a dxgi surface. |
|
This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
@loic-sharma Thanks for the guidance. I'lll
Converting this to a draft for now since it needs further discussion and refinement. @gaaclarke Since you own Impeller - would the Vulkan backend stability fixes (blit pass barrier corrections, command pool lifecycle safety, device-lost recovery, descriptor pool reset) be welcome as a standalone PR? These were discovered through sustained stress testing on AMD RDNA 2 and are independent of the desktop platform integration. |
|
@knopp Thanks for the review. LoadCursor: Not Vulkan-specific - this is a general Windows fix for stale resize cursors. Will split it into its own PR. Swapchain approach: Agreed on the limitations. The KHR swapchain path was chosen deliberately to reuse Impeller's battle-tested Android codepath (
Vulkan on Windows: I'd push back slightly on dropping it entirely. The Windows-on-ARM ecosystem is growing - Qualcomm Adreno and ARM Mali drivers on these devices ship Vulkan natively, and for some workloads Vulkan outperforms the D3D12 translation path. A hybrid approach (DXGI primary + Vulkan fallback) could serve both ecosystems without significant maintenance cost. Wayland desync: Acknowledged, synchronized mode is the correct approach. Re "vibe-coded" - this is several weeks of hands-on development with |
@sero583 Please see the design Doc guidance. TLDR: A google doc that uses our template is preferred, stakeholders can leave feedback on the content. It's fine for a PR description to be short if it links to the design doc, but feel free to make the PR description detailed as well. |
Actually no. Not the swapchain, not if we want to have platform views on windows at some point. I meant into a DXGI resource that we can present (or obtain through dcomp surface). You keep mentioning d3d12 translation layers? What are those? I was under the impression that snapdragon has native d3d12 drivers. |
|
@knopp You're right on both counts. On the DXGI point - the design doc targets VkImage → DXGI resource → DComp, not swapchain. On "translation layers" - poor wording on my part; I was thinking of Mesa dzn (Vulkan-on-D3D12 in WSL2) and carelessly applied the term to Snapdragon, which has native drivers. Both corrections are reflected in the design doc: #183495 |
|
Design doc and tracking issue are up: #183495 as already previously mentioned in my comment to knopp. @loic-sharma @gaaclarke - feedback welcome. Holding off on PR changes until the architecture is agreed on. Update: PR got merged, design doc is now up at https://flutter.dev/go/impeller-backend-desktop |
_Description of what this PR is changing or adding, and why: Adds a redirect for a design document. _Issues fixed by this PR (if any):_ _PRs or commits this PR depends on (if any):_ flutter/flutter#183382 ## Presubmit checklist - [x] If you are unwilling, or unable, to sign the CLA, even for a _tiny_, one-word PR, please file an issue instead of a PR. - [x] If this PR is not meant to land until a future stable release, mark it as draft with an explanation. - [x] This PR follows the [Google Developer Documentation Style Guidelines](https://developers.google.com/style)—for example, it doesn't use _i.e._ or _e.g._, and it avoids _I_ and _we_ (first-person pronouns). - [x] This PR uses [semantic line breaks](https://github.com/dart-lang/site-shared/blob/main/doc/writing-for-dart-and-flutter-websites.md#semantic-line-breaks) of 80 characters or fewer. --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
|
Hey @loic-sharma and @jtmcdole, thanks for the feedback! I've updated the design doc and left detailed replies to your comments. Specifically, I created a new tab called 'Addressing ANGLE concerns' to break down the translation layer costs, and replied to the Vulkan/OpenGL fallback thread. Let me know what you think when you have a chance to review. |
|
Hey @josetr, thanks for the feedback! The visual difference you're noticing stems from the fact that the current OpenGL path uses Skia as its renderer, whereas this PR introduces Impeller with Vulkan support. The two engines do render slightly differently - this is an inherent tradeoff for Impeller's improved performance and the elimination of shader compilation jank. Regarding the status of this PR: I'm currently awaiting feedback from the team on the proposed architecture via Google Docs, as the current PoC is still a bit rough around the edges. Work is on hold until we reach a consensus, though I believe the proposal is already heading in the right direction. It may just need a few adjustments here and there. I'm looking forward to finalizing this and getting it merged. As for your OpenGL issue on Linux Mint 22.3 - could you share the output of |
|
Is Impeller supposed to render buttons like that one that poorly everywhere else it's being used right now? Feels like it may be related to this PR. As for the OpenGL issue, it seems to be located at Since I'm on Linux Mint - X11, Wayland users won't experience this problem since A workaround seems to be to use As an experiment, what worked for me was forking Flutter & using the GLX + X11 APIs directly. Only then I get consistent 60fps. |
|
@josetr Thanks for the screenshots and all the detail about your setup. Just to clarify scope: this PR wires the existing Impeller Vulkan backend into the Linux/Windows embedders and hardens that backend for stability. It doesn't add a new “desktop-only” rendering path, so the visual differences you're seeing between the left (Skia/OpenGL) and right (Impeller/Vulkan) captures aren't caused by the desktop wiring itself – they come from Impeller vs Skia using different rendering pipelines. Some differences between Skia and Impeller are expected, but from your images it does look like the Impeller output is a quality regression rather than just "slightly different". That's something that should be tracked and tuned in the core Impeller/Vulkan backend (and compared with Android Impeller behavior), not left as-is. If you're okay with it, I'd suggest opening a separate Impeller issue that links to your comment here and your GLX branch. That way the rendering team can look at the specific button case you've highlighted while this PR stays focused on getting the desktop Vulkan wiring and stability improvements landed. |
|
quick follow-up from my side: the design doc at https://flutter.dev/go/impeller-backend-desktop has been updated based on your earlier feedback (including the "Addressing ANGLE concerns" tab and the Vulkan vs OpenGL fallback discussion), and all open comments there have replies. Before I start splitting this into the recommended sequence (Impeller/engine -> Windows -> Linux) and reworking the PoC to match the doc, I'd really appreciate a quick read on whether the proposed architecture looks acceptable in principle, or if there are any fundamental changes you would like to see first. I'd like to keep moving on this, but I want to make sure the direction is aligned with the team's expectations. Once I have a green light on the high-level direction, I'll proceed with the split and send focused PRs for review. |
|
This effort is beneficial for enhancing video playback performance on the Linux desktop. During my testing, employing mpv to directly play videos consumed less than 5% of the CPU. However, when utilizing mpv as a backend to render videos within Flutter, CPU usage soared to 22%. The majority of this time is spent on a single task: transferring images from the GPU to the CPU and then back to the GPU. This redundant process is excessively time-consuming. |
|
@dongfengweixiao Thanks, that means a lot - I really appreciate the feedback, and I'm glad to hear this work could help improve Linux video playback performance. At the moment, I’m mostly waiting on feedback to the design doc, since that will likely determine the broader direction for this work. If things remain quiet for a while, I’ll probably start breaking out some of the smaller, self-contained enhancements into separate PRs when I have the time. |


Description
Add Vulkan/Impeller rendering support for Flutter on Linux and Windows desktops.
The Flutter embedder API already supports Vulkan, and Impeller already has a mature Vulkan backend powering Android. This PR extends that support to desktop platforms by wiring the embedder's Vulkan surface into Impeller's existing KHR swapchain path, reusing the same rendering pipeline that powers Android — no new compositor or rendering pipeline was invented.
Why this matters
Desktop Flutter currently renders through OpenGL (Linux) and ANGLE/OpenGL ES (Windows). Vulkan provides:
Key design decisions
Reuse Impeller's KHR swapchain path rather than building a new compositor. The embedder creates a
VkSurfaceKHRfrom the platform window and passes it to Impeller'sSurfaceContextVK, which manages swapchain lifecycle identically to Android. This means every rendering optimization and bugfix in the Android Vulkan path automatically applies to desktop.Frame throttling via in-flight submission tracking in
CommandQueueVKprevents unbounded resource accumulation. Without this, sustained submission rates (e.g., during benchmark stress tests) exhaust host memory. The throttle uses a condition variable with a 5-second timeout — generous enough that no legitimate workload hits it, strict enough to prevent runaway allocation.VMA pool capping at 256 blocks × 4 MB = 1 GB in
AllocatorVK. Without this cap, VMA's pool grows without bound when high-water-mark buffer demand spikes, and pool-internal fragmentation prevents block reuse even after individual allocations are freed.Windows Resizable BAR working set trimming in
AllocatorVK. On GPUs with Resizable BAR (AMD RDNA, Intel Arc), the driver maps all VRAM into the process address space, inflating the working set to 2–4 GB even when actual CPU-side allocation is ~200 MB. PeriodicEmptyWorkingSet()calls (gated by cooldown, RSS threshold, and RSS-to-VMA ratio) let the OS reclaim pages the GPU driver mapped but that aren't actively used by the CPU.Mesa dzn workarounds for WSL2's Direct3D 12 translation layer. Mesa's Vulkan-on-D3D12 driver reports
minImageTransferGranularityof (0,0,0) and fails sub-region buffer-to-image copies. The workaround stages through a full-size temporary texture whenskip_sub_region_buffer_to_image_copyis set.Framebuffer fetch compatibility — drivers that do not support subpass self-dependencies (e.g., Mesa dzn) can have input attachment references and self-dependencies conditionally omitted via
SetFramebufferFetchEnabled(false)inRenderPassBuilderVK.Opt-in activation — Vulkan is not the default. On Linux, set
FLUTTER_LINUX_RENDERER=vulkan; on Windows, set the renderer type toFlutterDesktopRendererVulkanbefore engine startup. OpenGL/ANGLE remain the defaults for full backward compatibility.Related Issues
Related PRs
DisposeThreadLocalCachedResources()call ingpu_surface_vulkan_impeller.ccaddresses the same issue for the non-delegate (embedder-managed image) path. I am glad this was caught and fixed upstream as well, as I was focused on this PR and had no time to submit a similar fix.Changes
1. Vulkan proc table & surface abstractions
vulkan/procs/vulkan_proc_table.{cc,h}CreateWin32SurfaceKHR,CreateXlibSurfaceKHR,CreateWaylandSurfaceKHRfunction pointers and instance-level resolutionvulkan/procs/vulkan_interface.hvulkan/vulkan_native_surface_linux.{cc,h}VulkanNativeSurfaceLinuxsupporting both X11 and Wayland via runtime detectionvulkan/vulkan_swapchain.hFML_OS_LINUXvulkan/BUILD.gnWhy: The existing Vulkan proc table only resolved Android surface functions. Desktop platforms need Win32, X11, and Wayland surface creation. The Linux native surface class auto-detects the windowing system at construction time.
2. Embedder Vulkan surface integration
shell/platform/embedder/embedder_surface_vulkan_impeller.{cc,h}VkSurfaceKHR. When provided, creates aSurfaceContextVKand uses Impeller's KHR swapchain path; otherwise falls back to the existing embedder-managed image pathshell/platform/embedder/embedder.{cc,h}FlutterVulkanSurfaceHandletypedef andsurfacefield onFlutterVulkanRendererConfig. When non-null, the engine manages the swapchain internally (KHR path); when null, the legacy embedder-managed image callbacks are usedshell/platform/embedder/embedder_engine.{cc,h}SetSurfaceSizeUpdater()for platforms to handle swapchain resize on viewport changesshell/platform/embedder/embedder_surface.{cc,h}UpdateSurfaceSize()virtual method for subclasses that manage their own swapchainWhy: The embedder already had a Vulkan path, but it used embedder-managed images (the host allocates VkImages and hands them to the engine). For desktop, it's more efficient and correct to let Impeller manage its own swapchain via
SurfaceContextVK, which handles image acquisition, present timing, and swapchain recreation on resize. Thesurfacefield onFlutterVulkanRendererConfiglets each platform pass in aVkSurfaceKHRcreated from the native window handle (HWND, X11 Window, wl_surface). When present, the embedder skips the callback-based image path and delegates swapchain management entirely to Impeller.3. GPU surface improvements
shell/gpu/gpu_surface_vulkan_impeller.{cc,h}DisposeThreadLocalCachedResources()andDidAcquireSurfaceFrame(), and swapchain transient management to prevent resource leaks in the non-KHR (embedder-managed image) pathWhy: Without frame throttling, the GPU surface submits work faster than the GPU can complete it, causing
VK_ERROR_OUT_OF_HOST_MEMORY. The fence-based throttle (WaitForFence+ResetFence) ensures each frame completes before the next begins. Per-frame resource recycling (command pools viaReclaim(), descriptor pools via pool recycler) prevents per-frame resource accumulation.4. Impeller Vulkan backend hardening
render_pass_builder_vk.{cc,h}SetFramebufferFetchEnabled()to conditionally omit input attachment references and self-dependencies. Refactored subpass dependencies from hardcoded array indices to a counted builder patternblit_pass_vk.{cc,h}minImageTransferGranularity. Addedallocator_member for staging texture allocationworkarounds_vk.{cc,h}skip_sub_region_buffer_to_image_copyworkaround flag, detected for Mesa dzn driver viais_mesa_dznhelpercontext_vk.{cc,h}VK_KHR_swapchainvalidation to log info instead of returning null when the extension is unavailable (it is optional for embedder-managed rendering)driver_info_vk.{cc,h}VendorVK::kMesaandVK_DRIVER_ID_MESA_DOZENdriver ID checkcommand_pool_vk.{cc,h}context_from reference-to-weak_ptr to value copy for safe use after constructor scope. AddedIPLR_GUARDED_BY(pool_mutex_)annotation onunused_command_buffers_command_queue_vk.{cc,h}InFlightStatestruct, condition variable, andkMaxInFlightSubmissionscapcommand_buffer_vk.ccpipeline_vk.ccrender_pass_vk.ccsupports_framebuffer_fetch_flag to render pass creationdescriptor_pool_vk.{cc,h}maxSetsis computed as the sum of per-type pool sizes (804 total). Fixed doc comment referring to wrong pool typetracked_objects_vk.{cc,h}allocator_vk.cccapabilities_vk.ccVK_KHR_surfaceandVK_KHR_swapchainto required extensions for desktoprender_target_cache.{cc,h}Why each change was necessary:
render_pass_builder_vk: Mesa dzn doesn't support subpass self-dependencies, causingvkCreateRenderPassto fail. The refactored builder lets the context disable framebuffer fetch at runtime. Additionally,VK_DEPENDENCY_BY_REGION_BITwas removed from the incoming and outgoing external dependencies (VK_SUBPASS_EXTERNAL) where it is meaningless per Vulkan spec section 7.1 — the flag is only defined for self-dependencies.blit_pass_vk: Mesa dzn reportsminImageTransferGranularity = {0,0,0}and corrupts textures on sub-region copies. The staging workaround copies the entire buffer to a temporary texture, then blits only the needed region.command_queue_vk: Without throttling, the host queues submissions faster than the GPU drains them. On a benchmark running at 1000+ FPS (debug, minimal scene), this exhausts host memory within minutes.descriptor_pool_vk: PoolmaxSetsis now the sum of per-type pool sizes (804 total: 256 texture + 512 buffer + 32 storage + 4 subpass). Pool reset viaReclaim()inDescriptorPoolRecyclerVKprevents pool exhaustion across frames.allocator_vk: VMA's memory pool grows monotonically. The 256-block cap prevents unbounded growth. The Windows EmptyWorkingSet trimming addresses a cosmetic but alarming RSS inflation caused by Resizable BAR.5. Linux platform integration
shell/platform/linux/fl_vulkan_manager.{cc,h}shell/platform/linux/fl_engine.ccshell/platform/linux/fl_engine_private.hshell/platform/linux/fl_view.ccshell/platform/linux/public/flutter_linux/fl_engine.hFlRendererTypeenum (OpenGL, Software, Vulkan) and publicfl_engine_get_rendering_backend()APIshell/platform/linux/BUILD.gnWhy: Linux needed a platform-specific Vulkan manager because GDK provides the native window handles (X11
Windowor Waylandwl_surface). The manager handles:VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)g_atomic_int_set/g_atomic_int_getVK_IMAGE_USAGE_INPUT_ATTACHMENT_BITconditionally added based oncaps.supportedUsageFlags(avoids swapchain creation failure on software/translation-layer drivers)g_once_init_enter/g_once_init_leave6. Windows platform integration
shell/platform/windows/vulkan_manager.{cc,h}FLUTTER_VULKAN_VALIDATION=1shell/platform/windows/flutter_windows_engine.{cc,h}GetVulkanRendererConfig()passesVkSurfaceKHRviaReleaseSurface(), Vulkan device info, and no-op callbacks for KHR swapchain mode. Display refresh rate detection for Vulkan vsyncshell/platform/windows/flutter_window.{cc,h}GetWindowHandle()to expose HWND for Vulkan surface creationshell/platform/windows/flutter_windows_view.{cc,h}shell/platform/windows/flutter_windows.ccFlutterDesktopEngineGetRenderingBackend()public API implementationshell/platform/windows/public/flutter_windows.hFlutterDesktopEngineGetRenderingBackend()public API andFlutterDesktopRendererTypeenum (OpenGL, Software, Vulkan)shell/platform/windows/window_binding_handler.h/testing/mock_window_binding_handler.hGetWindowHandle()to window binding interface and mockshell/platform/windows/BUILD.gnvulkan-1.libdependencyWhy: Windows integration follows the same pattern as Linux — a platform-specific Vulkan manager creates the instance, device, and surface, then passes the
VkSurfaceKHRto the embedder. TheFlutterDesktopRendererTypeenum (set viaFlutterDesktopEngineProperties) allows apps to opt in to Vulkan before engine startup.7. RenderingBackend API (framework)
lib/ui/platform_dispatcher.dartRenderingBackendenum (opengl, vulkan, software, metal, canvaskit, skwasm) andPlatformDispatcher.renderingBackendgetterlib/ui/dart_ui.cclib/ui/natives.dart_renderingBackendvariable set by the engine at startuplib/web_ui/lib/platform_dispatcher.dartRenderingBackendenum (mirrors native) and abstract getterlib/web_ui/lib/src/engine/platform_dispatcher.dartcanvaskitfor CanvasKitRenderer,skwasmfor SkwasmRendererpackages/flutter/lib/src/foundation/rendering_backend.dartdefaultRenderingBackendconvenience getterpackages/flutter/lib/foundation.dartrendering_backend.dartWhy: Currently, there is no public API for apps or tooling to query which rendering backend is active. The
RenderingBackendenum covers all six backends (opengl, vulkan, software, metal, canvaskit, skwasm). The engine sets the value at startup via a native call, and the framework exposes it throughPlatformDispatcher.renderingBackend. This enables:8. Flutter Tools
packages/flutter_tools/lib/src/desktop_device.dart--impeller-backend=vulkanto request the Vulkan backend9. Miscellaneous
display_manager.ccDartConverter<unsigned long long>assertion overflowTesting
Automated tests
fl_vulkan_manager_test.ccvulkan_native_surface_linux_test.ccfl_engine_test.ccvulkan_manager_unittests.ccplatform_dispatcher_test.dart(native)RenderingBackendenum values,renderingBackendgetterplatform_dispatcher_test.dart(web)rendering_backend_test.dartdefaultRenderingBackendgetterdesktop_device_test.dartManual stress testing
All manual testing used flutter-benchmark — a 9-scene GPU stress-test suite covering particles, nested widgets, bezier curves, image composition, text rendering, transforms, shader masks, deep opacity trees, and non-uniform text scaling.
Android note: The benchmark app runs on Android with this engine build. Most scenes complete without issue, but extreme stress tests (10,000-particle scenes, rapid text rendering with large glyph atlases) can trigger OOM or driver timeouts on mobile SoCs. This is not a regression — the same behavior occurs on the upstream engine without any of our changes. Desktop Vulkan is the focus of this PR; Android Vulkan was already supported and is unchanged by this PR.
Key stability metrics (Windows, Impeller Vulkan, Release build):
VK_ERROR_OUT_OF_HOST_MEMORYafter multiple sessions of continuous benchmark runsMore verifications welcome! My hardware is limited to AMD RDNA 2 + Samsung Exynos. Testing on NVIDIA, Intel, and other mobile SoCs would be valuable.
Build verification
flutter/shell/platform/windows:flutter_windowsflutter/shell/platform/windows:flutter_windowsflutter/shell/platform/linux:flutter_linux_gtkflutter/shell/platform/linux:flutter_linux_gtkDiscussion Points
RSS working-set trim cooldown assumes 60 fps
The
kTrimCooldownFrames = 300constant inallocator_vk.ccyields a ~5 s cooldown at 60 fps but only ~1.25 s at 240 Hz. SinceEmptyWorkingSetmerely moves pages to the standby list (they fault back in on next access) and the trigger conditions are already conservative (RSS > 1 GB, RSS > 4x VMA), the practical impact is negligible at any refresh rate. A time-based cooldown viastd::chronowould be more precise but adds per-frame clock overhead for no measurable benefit. Feedback welcome on whether a frame-count heuristic is acceptable here.Runtime Vulkan crash recovery as a longer-term alternative?
The current approach (static blocklist via
IsKnownBadDriver()) is reactive: we discover a bad GPU/driver, add it to the list, ship a new Flutter release, and users still suffer until they update. This raises a question worth discussing:Could Flutter detect Vulkan failures at runtime and automatically fall back to GLES?
Today, if a Vulkan driver returns
VK_ERROR_DEVICE_LOSTor hangs during rendering, there is no recovery path — the app freezes or crashes. The rendering backend is locked in at startup. In principle, the engine could:VK_ERROR_DEVICE_LOSTor repeated command submission failures during rendering.SharedPreferencesor a file in the app's cache directory).Benefits:
Challenges:
ContextVK::Create()(as seen on Mali-G52), the crash detection never fires — the process is killed by Android's ANR watchdog. A persistent flag set before Vulkan init ("attempting Vulkan...") and cleared on success could handle this case, but adds complexity.This PR takes the pragmatic immediate fix (static block), but the runtime detection approach could be a valuable longer-term investment for the Impeller team, especially as the number of Android GPU/driver/SoC combinations continues to grow.
Engine reliability under extreme GPU stress
During stress testing with flutter-benchmark, I observed that the engine can crash on mobile SoCs (Samsung Xclipse 940) under extreme workloads — 10,000 simultaneous particles, rapid large-atlas text reflows, and concurrent heavy shader effects. The crashes are OOM kills or GPU driver timeouts, not Vulkan validation errors. Crucially, this is not a regression from this PR — the same crashes reproduce on the upstream engine with identical test scenes. No real-world app would sustain these workloads, but the observation suggests the engine could benefit from more graceful degradation under resource pressure (e.g., frame dropping, allocation throttling, or LOD reduction). This would be a separate effort outside the scope of this PR.
Notes for reviewers
Activation & backward compatibility
FLUTTER_LINUX_RENDERER=vulkan. Windows: setFlutterDesktopRendererVulkanrenderer type before engine startup.RenderingBackendenum, Vulkan managers, embedder callbacks, and workaround flags are all new — no existing APIs are modified or removed.Thread safety
FlVulkanManagerusesg_atomic_int_set/g_atomic_int_getfor itsshutting_downflag (accessed from both UI and raster threads).CommandQueueVK::InFlightStateusesstd::mutex+std::condition_variablefor in-flight submission tracking.VulkanManageris created on the platform thread and accessed from the raster thread; all shared state is set before engine start.Driver compatibility
VK_IMAGE_USAGE_INPUT_ATTACHMENT_BITconditionally added based oncaps.supportedUsageFlags— avoids swapchain creation failures on software/translation-layer drivers.VK_DRIVER_ID_MESA_DOZENviaGetDriverID()(Vulkan 1.2+).Memory management
kMaxInFlightSubmissions) prevents host memory exhaustion under sustained high-FPS workloads.EmptyWorkingSet()trims inflated RSS (cooldown: 300 frames, threshold: RSS > 1 GB + VMA < 256 MB + RSS/VMA > 4×). This is cosmetic — the pages are GPU-mapped VRAM, not leaked memory.Areas that may benefit from extra scrutiny
FlutterVulkanSurfaceHandle surfacefield onFlutterVulkanRendererConfig,SetSurfaceSizeUpdater(), andUpdateSurfaceSize()are public API additions.VK_DRIVER_ID_MESA_DOZEN; other translation-layer drivers may need similar treatment in the future.RenderingBackendenum — new framework-level API. The backing index values (0–5) are set by the engine at startup and should remain stable.Pre-existing formatting deviations in adjacent files
During
clang-formatvalidation of this PR, I observed that the following upstream files in the repository contain pre-existing formatting deviations from the project's.clang-formatstyle. These files are not modified by this PR and are listed here for transparency:common/graphics/persistent_cache.cclib/ui/plugins/callback_cache.ccshell/common/engine.ccshell/common/shell.ccshell/platform/windows/platform_handler.ccAll files modified by this PR have been formatted with
clang-format --style=file.Pre-launch Checklist
///).