You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When looking at the frame capture for #191207 it looks like there may be superfluous draws happening when compositing to the swapchain. We should double check this to see if we can simplify it.
An audit of swapchain composition on Windows was performed based on the RenderDoc frame captures from #191207#191207 and the
implementation in compositor_opengl.cc, manager.cc, and Impeller's canvas.cc.
──────
1. Frame Capture Breakdown (RenderDoc Trace)
At the end of each frame (and in particular for frames with backdrop filters), RenderDoc records the following sequential operations:
Event ID
Operation
Duration
Resource Involved
Description
EID 1063
Draw(4)
~3.30 ms
render_target_ (4x MSAA backing store)
Impeller's Canvas::BlitToOnscreen ("Root pass blit") drawing a full-screen textured quad.
ANGLE D3D11 resolving the 4x MSAA backing store into an internal 1x intermediate texture.
EID 1079
Draw(4)
~1.22 ms
2D Texture 9623 → ANGLE_BackBufferTexture
ANGLE pixel shader copy (ANGLE_Blit11PS / ANGLE_Blit11VertexBuffer) to flip Y into the swapchain backbuffer.
EID 1080
Present()
—
DXGI Swapchain
Presenting the backbuffer to DWM.
Across these three operations alone, ~5.65 ms of GPU time is consumed per frame (more than 68% of an 8.33 ms frame budget at 120 Hz, and over 34% at 60 Hz). Even on frames without backdrop blur
(where EID 1063 does not occur), EID 1065 and EID 1079 waste ~2.35 ms on every single frame.
──────
2. Root Causes Identified
A. EID 1065 (ResolveSubresource, ~1.13 ms) — Backing Store MSAA
The Allocation: In compositor_opengl.cc:87-108, the embedder allocates a 4x MSAA color renderbuffer (color_renderbuffer_id) via glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, ...) when
supports_offscreen_msaa_ is true (added in PR [windows]: Uses offscreen MSAA when implicit msaa isn't available. #190256 to provide MSAA for path rendering).
The Presentation: In compositor_opengl.cc:234-248, glBlitFramebuffer is called from the backing store FBO to kWindowFrameBufferId (0).
The Resolve: Because the source FBO has a 4-sample renderbuffer, ANGLE's D3D11 backend in Renderer11.cpp:3630-3648 invokes resolveMultisampledTexture(). This calls
ID3D11DeviceContext::ResolveSubresource, resolving the 4x buffer into ANGLE's internal 1x texture mCachedResolveTexture (2D Texture 9623).
The Disconnect: Impeller in embedder.cc:1163-1192 only checks SupportsImplicitResolvingMSAA() (false on ANGLE), so it wraps this backing store as a 1-sample texture (sample_count = kCount1,
store_action = kStore). Impeller does not even realize this FBO is multisampled. Furthermore, Impeller's offscreen passes (EntityPassTarget) already allocate their own 4x MSAA render targets
and resolve them explicitly.
B. EID 1079 (Draw(4), ~1.22 ms) — Inverted-Y Shader Blit
Why ANGLE Couldn't Use D3D11 DMA Copy: ANGLE's Renderer11.cpp:3775-3839 has a fast path that uses ID3D11DeviceContext::CopySubresourceRegion if !reversalRequired.
The Inversion: Flutter sets EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE in manager.cc:80. In ANGLE:
• For GL_FRAMEBUFFER_DEFAULT (the window surface / swapchain backbuffer), UsePresentPathFast is true.
• For offscreen FBOs, UsePresentPathFast is false.
• In Framebuffer11.cpp:348-361, ANGLE inverts the destination rectangle height (actualDestArea.height = -destArea.height).
The Fallback: Because the destination Y axis is inverted relative to the source, reversalRequired is true. Direct3D 11 CopySubresourceRegion does not support axis flipping, forcing ANGLE
into the fallback path: Renderer11.cpp:3871-3875, which binds ANGLE_Blit11VS/ANGLE_Blit11PS and executes a full-screen quad draw.
The Irony: EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE was designed to avoid a copy when rendering directly to the swapchain. When rendering to an offscreen FBO and blitting to the swapchain,
it forces ANGLE to use a pixel shader draw call instead of a DMA copy because of the orientation mismatch.
C. EID 1063 (BlitToOnscreen, ~3.30 ms) — Root Pass Blit
Impeller renders to an offscreen pass and calls canvas.cc:2659-2707.
canvas.cc:2651-2657 returns false on GLES (it explicitly requires Context::BackendType::kMetal, originally disabled in commit 3e219d1 because of crashes with
GL_EXT_multisampled_render_to_texture on mobile drivers).
As a result, Impeller executes a full-screen textured quad render pass ("Root pass blit", TextureContents, BlendMode::kSrc) into render_target_.
Because render_target_ is a 4x MSAA renderbuffer, drawing this quad incurs 4x multisample rasterization over the entire screen, taking ~3.30 ms.
──────
3. Recommended Remediation Strategies
Recommendation 1: Direct Rendering to Swapchain via Surface Backing Store (Saves ~2.35 ms/frame)
Eliminates EID 1065 (ResolveSubresource) and EID 1079 (Draw(4)).
• Mechanism:
1. Implement kFlutterOpenGLTargetTypeSurface for Impeller in embedder.cc:1478 (resolving #151670#151670).
2. Have compositor_opengl.cc:47 return a surface backing store wrapping the view's window_surface.h.
3. Impeller wraps FBO 0 via surface_gles.cc:14.
4. With EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE, ANGLE inverts the viewport so rendering targets the DXGI swapchain backbuffer directly.
5. In compositor_opengl.cc:183, view->surface()->SwapBuffers() simply flips the DXGI swapchain.
• Benefits:
• Zero offscreen backing store FBO allocations.
• Zero glBlitFramebuffer calls.
• Zero resolves and zero end-of-frame blits.
Recommendation 2: Remove 4x MSAA from the Offscreen Backing Store (Saves ~1.13 ms/frame + 75% VRAM)
If an offscreen backing store is retained for compatibility or multi-layer compositing:
• In compositor_opengl.cc:87-131, revert the backing store to a standard 1-sample texture (as in the else branch).
• Impact on Antialiasing:
• Shapes (rectangles, rounded rects, circles, ovals, superellipses) use Impeller's analytic UberSDF shader, which was fixed on Windows in PR #190260 and does not use MSAA.
• Offscreen entity passes (EntityPassTarget) already allocate their own 4x MSAA render targets and resolve them.
• Eliminates ResolveSubresource (1.13 ms saved).
• Reduces Canvas::BlitToOnscreen quad draw time by rendering into a 1x texture instead of a 4x MSAA renderbuffer.
Recommendation 3: Enable Fast Texture Blits for Impeller on GLES 3.0+ (Saves ~2–3 ms on backdrop filter frames)
Optimizes EID 1063 (BlitToOnscreen).
• In canvas.cc:2651, allow GLES when SupportsTextureToTextureBlits() is true and implicit MSAA is not active (!SupportsImplicitResolvingMSAA()).
• On Windows / ANGLE (GLES 3.0+), this allows BlitToOnscreen to use BlitPass (glBlitFramebuffer) instead of an entire RenderPass quad draw, significantly reducing the 3.3 ms overhead during
backdrop filter rendering.
This seems like a fair evaluation of the situation:
Recommendation 1 seems doable, but is somewhat involved.
Recommendation 2 seems like something we probably don't want to do since there are cases that still do rely on MSAA and being draw directly to the screen.
Recommendation 3 seems like an easy win we should implement.
I researched trying to get rid of the last draw call. In directx I think we could resolve the MSAA render buffer to the swapchain texture directly. Or resolve it to a 1x texture and do a blit after having removed the flipped y coordinates.
The opengles instructions don't match up to that though. From opengles's perspective we have a MSAA renderbuffer and that can't be blit to a texture. I think the only hope to eliminate that renderpass would be a patch to ANGLE.
One option to allow us to blit would be to switch the backing store to a 1x texture and use transient MSAA renderbuffers to resolve to it when we encounter things like DrawPath. I'm not a fan of that though since it leaks rendering details a level higher into the display_list interpreter.
I was able to prototype the optimization. It required an ANGLE patch to implement an extension to tell ANGLE that the framebuffer is upside down. This already exists for other backends, but wasn't implemented for dx11. A partial ANGLE patch is in the ANGLE bug report.
Here's the change we needed from the flutter side. We basically keep track if our renderpass is going to the swapchain, if so render it flipped so that DX11 can resolve MSAA directly to the swapchain.
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h
index a093159c25e..f602ada0d0e 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h+++ b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h@@ -287,6 +287,7 @@ void(glDepthRange)(GLdouble n, GLdouble f);
PROC(DebugMessageCallbackKHR); \
PROC(DiscardFramebufferEXT); \
PROC(FramebufferTexture2DMultisampleEXT); \
+ PROC(FramebufferParameteriMESA); \
PROC(PushDebugGroupKHR); \
PROC(PopDebugGroupKHR); \
PROC(ObjectLabelKHR); \
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles.cc
index 9efab5e56d4..d6f85165d1a 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles.cc+++ b/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles.cc@@ -358,7 +358,7 @@ static void EncodeViewport(const ProcTableGLES& gl,
// Offscreen FBO passes flip in the vertex shader (the swapchain is
// left alone); see https://github.com/flutter/flutter/issues/186554.
- const bool flip_y = !is_wrapped_fbo;+ const bool flip_y = !color_gles.IsSwapchain();
const float y_flip_value = flip_y ? -1.0f : 1.0f;
std::optional<Viewport> current_viewport;
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles_unittests.cc b/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles_unittests.cc
index 6a7ece8c4b6..90e99a597e8 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles_unittests.cc+++ b/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles_unittests.cc@@ -545,5 +545,83 @@ TEST_F(RenderPassGLESCommandTest, ZeroInstanceCountIssuesNoDraw) {
EXPECT_TRUE(reactor->React());
}
+// Verifies that wrapped offscreen FBOs flip in the vertex shader (flip_y=true).+TEST_F(RenderPassGLESCommandTest, WrappedOffscreenFBOFlipsVertexY) {+ auto ctx = CreateRenderPassGLESContext();+ testing::NiceMock<MockGLESImpl>& mock_gl_impl_ref = ctx.mock_gl_impl_ref;+ std::shared_ptr<PipelineGLES>& pipeline = ctx.pipeline;+ std::shared_ptr<ReactorGLES>& reactor = ctx.reactor;++ // Offscreen FBO (non-zero FBO, is_swapchain = false).+ TextureDescriptor tex_desc;+ tex_desc.size = {100, 100};+ tex_desc.format = PixelFormat::kR8G8B8A8UNormInt;+ auto offscreen_texture =+ TextureGLES::WrapFBO(reactor, tex_desc, 1u, /*is_swapchain=*/false);++ RenderTarget target;+ ColorAttachment color0;+ color0.texture = offscreen_texture;+ color0.store_action = StoreAction::kDontCare;+ color0.load_action = LoadAction::kClear;+ target.SetColorAttachment(color0, 0);++ auto command_buffer =+ std::static_pointer_cast<Context>(ctx.context)->CreateCommandBuffer();+ auto render_pass = command_buffer->CreateRenderPass(target);++ render_pass->SetPipeline(PipelineRef(pipeline));+ render_pass->SetElementCount(1);+ render_pass->SetIndexBuffer({}, IndexType::kNone);+ render_pass->SetViewport(+ Viewport{Rect::MakeXYWH(0, 0, 50, 50), DepthRange{0.0f, 1.0f}});+ EXPECT_TRUE(render_pass->Draw().ok());++ // flip_y = true: viewport Y is 0.+ EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 0, 50, 50)).Times(1);++ EXPECT_TRUE(render_pass->EncodeCommands());+ EXPECT_TRUE(reactor->React());+}++// Verifies that wrapped swapchain FBOs do not flip in the vertex shader+// (flip_y=false).+TEST_F(RenderPassGLESCommandTest, WrappedSwapchainDoesNotFlipVertexY) {+ auto ctx = CreateRenderPassGLESContext();+ testing::NiceMock<MockGLESImpl>& mock_gl_impl_ref = ctx.mock_gl_impl_ref;+ std::shared_ptr<PipelineGLES>& pipeline = ctx.pipeline;+ std::shared_ptr<ReactorGLES>& reactor = ctx.reactor;++ // Swapchain FBO (FBO 0, is_swapchain = true).+ TextureDescriptor tex_desc;+ tex_desc.size = {100, 100};+ tex_desc.format = PixelFormat::kR8G8B8A8UNormInt;+ auto swapchain_texture = TextureGLES::WrapFBO(reactor, tex_desc, 0u);++ RenderTarget target;+ ColorAttachment color0;+ color0.texture = swapchain_texture;+ color0.store_action = StoreAction::kDontCare;+ color0.load_action = LoadAction::kClear;+ target.SetColorAttachment(color0, 0);++ auto command_buffer =+ std::static_pointer_cast<Context>(ctx.context)->CreateCommandBuffer();+ auto render_pass = command_buffer->CreateRenderPass(target);++ render_pass->SetPipeline(PipelineRef(pipeline));+ render_pass->SetElementCount(1);+ render_pass->SetIndexBuffer({}, IndexType::kNone);+ render_pass->SetViewport(+ Viewport{Rect::MakeXYWH(0, 0, 50, 50), DepthRange{0.0f, 1.0f}});+ EXPECT_TRUE(render_pass->Draw().ok());++ // flip_y = false: viewport Y is 100 - 0 - 50 = 50.+ EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 50, 50, 50)).Times(1);++ EXPECT_TRUE(render_pass->EncodeCommands());+ EXPECT_TRUE(reactor->React());+}+
} // namespace testing
} // namespace impeller
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/surface_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/surface_gles.cc
index 4f0866cec8c..a45b7a94b4d 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/surface_gles.cc+++ b/engine/src/flutter/impeller/renderer/backend/gles/surface_gles.cc@@ -34,8 +34,8 @@ std::unique_ptr<Surface> SurfaceGLES::WrapFBO(
color0_tex.storage_mode = StorageMode::kDevicePrivate;
ColorAttachment color0;
- color0.texture =- TextureGLES::WrapFBO(gl_context.GetReactor(), color0_tex, fbo);+ color0.texture = TextureGLES::WrapFBO(gl_context.GetReactor(), color0_tex,+ fbo, /*is_swapchain=*/true);
color0.clear_color = Color::DarkSlateGray();
color0.load_action = LoadAction::kClear;
color0.store_action = StoreAction::kStore;
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc
index a19756936f4..e21da9de8ff 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc+++ b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc@@ -95,9 +95,10 @@ HandleType ToHandleType(TextureGLES::Type type) {
std::shared_ptr<TextureGLES> TextureGLES::WrapFBO(
std::shared_ptr<ReactorGLES> reactor,
TextureDescriptor desc,
- GLuint fbo) {- auto texture = std::shared_ptr<TextureGLES>(- new TextureGLES(std::move(reactor), desc, false, fbo, std::nullopt));+ GLuint fbo,+ bool is_swapchain) {+ auto texture = std::shared_ptr<TextureGLES>(new TextureGLES(+ std::move(reactor), desc, false, fbo, std::nullopt, is_swapchain));
if (!texture->IsValid()) {
return nullptr;
}
@@ -127,7 +128,8 @@ std::shared_ptr<TextureGLES> TextureGLES::WrapTexture(
std::shared_ptr<TextureGLES> TextureGLES::CreatePlaceholder(
std::shared_ptr<ReactorGLES> reactor,
TextureDescriptor desc) {
- return TextureGLES::WrapFBO(std::move(reactor), desc, 0u);+ return TextureGLES::WrapFBO(std::move(reactor), desc, 0u,+ /*is_swapchain=*/true);
}
TextureGLES::TextureGLES(std::shared_ptr<ReactorGLES> reactor,
@@ -137,14 +139,15 @@ TextureGLES::TextureGLES(std::shared_ptr<ReactorGLES> reactor,
desc, //
threadsafe, //
std::nullopt, //
- std::nullopt //- ) {}+ std::nullopt, //+ /*is_swapchain=*/false) {}
TextureGLES::TextureGLES(std::shared_ptr<ReactorGLES> reactor,
TextureDescriptor desc,
bool threadsafe,
std::optional<GLuint> fbo,
- std::optional<HandleGLES> external_handle)+ std::optional<HandleGLES> external_handle,+ bool is_swapchain)
: Texture(desc),
reactor_(std::move(reactor)),
type_(GetTextureTypeFromDescriptor(
@@ -158,7 +161,8 @@ TextureGLES::TextureGLES(std::shared_ptr<ReactorGLES> reactor,
: UniqueHandleGLES::MakeUntracked(reactor_,
ToHandleType(type_)))),
is_wrapped_(fbo.has_value() || external_handle.has_value()),
- wrapped_fbo_(fbo) {+ wrapped_fbo_(fbo),+ is_swapchain_(is_swapchain || (fbo.has_value() && *fbo == 0)) {
// Ensure the texture descriptor itself is valid.
if (!GetTextureDescriptor().IsValid()) {
return;
@@ -860,6 +864,10 @@ bool TextureGLES::IsWrapped() const {
return is_wrapped_;
}
+bool TextureGLES::IsSwapchain() const {+ return is_swapchain_;+}+
std::optional<GLuint> TextureGLES::GetFBO() const {
return wrapped_fbo_;
}
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.h
index 8db5ad86821..9eb07090b81 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.h+++ b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.h@@ -34,9 +34,10 @@ class TextureGLES final : public Texture,
/// This is useful for creating a render target for the default
/// window managed framebuffer.
///
- /// @param[in] reactor The reactor- /// @param[in] desc The description- /// @param[in] fbo The fbo+ /// @param[in] reactor The reactor+ /// @param[in] desc The description+ /// @param[in] fbo The fbo+ /// @param[in] is_swapchain Whether the FBO represents an onscreen swapchain
///
/// @return If a texture representation of the framebuffer could be
/// created.
@@ -44,7 +45,8 @@ class TextureGLES final : public Texture,
static std::shared_ptr<TextureGLES> WrapFBO(
std::shared_ptr<ReactorGLES> reactor,
TextureDescriptor desc,
- GLuint fbo);+ GLuint fbo,+ bool is_swapchain = false);
//----------------------------------------------------------------------------
/// @brief Create a texture by wrapping an external OpenGL texture
@@ -104,6 +106,8 @@ class TextureGLES final : public Texture,
bool IsWrapped() const;
+ bool IsSwapchain() const;+
/// @brief Reset the internal texture state so that the reactor will not free
/// the associated handle.
void Leak();
@@ -198,6 +202,7 @@ class TextureGLES final : public Texture,
std::array<std::bitset<kMaxTrackedMipLevels>, 6> slice_mip_initialized_ = {};
const bool is_wrapped_;
const std::optional<GLuint> wrapped_fbo_;
+ const bool is_swapchain_;
UniqueHandleGLES cached_fbo_;
uint32_t cached_fbo_mip_level_ = 0;
uint32_t cached_fbo_slice_ = 0;
@@ -207,7 +212,8 @@ class TextureGLES final : public Texture,
TextureDescriptor desc,
bool threadsafe,
std::optional<GLuint> fbo,
- std::optional<HandleGLES> external_handle);+ std::optional<HandleGLES> external_handle,+ bool is_swapchain = false);
// |Texture|
void SetLabel(std::string_view label) override;
diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc
index 29d75fbf2d8..6c2924553dc 100644
--- a/engine/src/flutter/shell/platform/embedder/embedder.cc+++ b/engine/src/flutter/shell/platform/embedder/embedder.cc@@ -1181,7 +1181,8 @@ MakeRenderTargetFromBackingStoreImpeller(
impeller::ColorAttachment color0;
color0.texture = impeller::TextureGLES::WrapFBO(
- gl_context.GetReactor(), color0_tex, framebuffer->name);+ gl_context.GetReactor(), color0_tex, framebuffer->name,+ /*is_swapchain=*/framebuffer->name == 0);
color0.clear_color = impeller::Color::DarkSlateGray();
color0.load_action = impeller::LoadAction::kClear;
if (implicit_msaa) {
diff --git a/engine/src/flutter/shell/platform/windows/compositor_opengl.cc b/engine/src/flutter/shell/platform/windows/compositor_opengl.cc
index d1b1f5bc71a..5764315bb3b 100644
--- a/engine/src/flutter/shell/platform/windows/compositor_opengl.cc+++ b/engine/src/flutter/shell/platform/windows/compositor_opengl.cc@@ -56,6 +56,13 @@ bool CompositorOpenGL::CreateBackingStore(
gl_->GenFramebuffers(1, &store->framebuffer_id);
gl_->BindFramebuffer(GL_FRAMEBUFFER, store->framebuffer_id);
+ // Impeller renders into offscreen framebuffers top-down, flipping Y in the vertex stage. Declare+ // that orientation so the driver does not insert a compensating vertical flip when this backing+ // store is blitted to the swapchain, which would otherwise force a full-screen shader pass.+ if (enable_impeller_ && gl_->FramebufferParameteriMESA.IsAvailable()) {+ gl_->FramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE);+ }+
if (enable_impeller_) {
if (supports_implicit_msaa_) {
gl_->GenTextures(1, &store->texture_id);
@gaaclarke Thank you for your work on this, and for the patch on Angle. It is very much appreciated.
Looking forward to #185556 and to #193162 which hopefully will help too.
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 -v and a minimal reproduction of the issue.
Activity
gaaclarke commentedon Sep 9, 2026
Audit from Gemini:
This seems like a fair evaluation of the situation:
gaaclarke commentedon Sep 10, 2026
I researched trying to get rid of the last draw call. In directx I think we could resolve the MSAA render buffer to the swapchain texture directly. Or resolve it to a 1x texture and do a blit after having removed the flipped y coordinates.
The opengles instructions don't match up to that though. From opengles's perspective we have a MSAA renderbuffer and that can't be blit to a texture. I think the only hope to eliminate that renderpass would be a patch to ANGLE.
One option to allow us to blit would be to switch the backing store to a 1x texture and use transient MSAA renderbuffers to resolve to it when we encounter things like DrawPath. I'm not a fan of that though since it leaks rendering details a level higher into the display_list interpreter.
gaaclarke commentedon Sep 10, 2026
I filed an issue against ANGLE to try to eliminate the extra full screen that happens at the end of the frame. https://g-issues.angleproject.org/issues/559744846
gaaclarke commentedon Sep 11, 2026
I was able to prototype the optimization. It required an ANGLE patch to implement an extension to tell ANGLE that the framebuffer is upside down. This already exists for other backends, but wasn't implemented for dx11. A partial ANGLE patch is in the ANGLE bug report.
Here's the change we needed from the flutter side. We basically keep track if our renderpass is going to the swapchain, if so render it flipped so that DX11 can resolve MSAA directly to the swapchain.
Starts flipping content on windows to avoid full screen renders (flut…
Razorex001 commentedon Oct 5, 2026
@gaaclarke Thank you for your work on this, and for the patch on Angle. It is very much appreciated.
Looking forward to #185556 and to #193162 which hopefully will help too.
github-actions commentedon Oct 19, 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.Starts flipping content on windows to avoid full screen renders (flut…