From dd93de6fb1776398bf586cbd477deade1391c7e4 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Tue, 24 Jun 2025 07:39:37 -0700 Subject: [PATCH] [CP-stable][Windows] Use ANGLE blit extension on GLES 2.0 (#170924) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) ### Issue Link: https://github.com/flutter/flutter/issues/169178 ### Changelog Description: Fix Flutter Windows on devices that only support OpenGL ES 2, like computers with Intel graphics cards. ### Impact Description: Flutter Windows 3.29.0 and higher crashed immediately on startup on computers using Intel HD Graphics graphics cards. ### Workaround: None, other than downgrading your Flutter SDK to 3.27.4 or lower. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: This bug does not reproduce on Flutter's infrastructure. The fix was manually tested on several Windows devices, including affected devices that use Intel HD Graphics graphics cards. --- CHANGELOG.md | 1 + .../renderer/backend/gles/proc_table_gles.cc | 4 + .../renderer/backend/gles/proc_table_gles.h | 3 +- .../platform/windows/compositor_opengl.cc | 41 ++++++--- .../windows/compositor_opengl_unittests.cc | 83 ++++++++++++++++++- 5 files changed, 119 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d5144d5345..dffb009186c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ INTERNAL NOTE ### [3.32.5](https://github.com/flutter/flutter/releases/tag/3.32.5) +- [flutter/170924](https://github.com/flutter/flutter/pull/170924) - Fix Flutter Windows on devices that only support OpenGL ES 2, like computers with Intel graphics cards. - [flutter/170880](https://github.com/flutter/flutter/pull/170880) - Fixes unhandled exception on application shutdown in the debug adapter used by IDEs. ### [3.32.4](https://github.com/flutter/flutter/releases/tag/3.32.4) diff --git a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.cc index e7cf345f75e..399b88c94fc 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.cc @@ -141,6 +141,10 @@ ProcTableGLES::ProcTableGLES( // NOLINT(google-readability-function-size) DiscardFramebufferEXT.Reset(); } + if (!description_->HasExtension("GL_ANGLE_framebuffer_blit")) { + BlitFramebufferANGLE.Reset(); + } + capabilities_ = std::make_shared(*this); is_valid_ = true; 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 87308fb4d32..98871d18cb0 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 @@ -266,7 +266,8 @@ void(glDepthRange)(GLdouble n, GLdouble f); PROC(GetQueryObjectui64vEXT); \ PROC(BeginQueryEXT); \ PROC(EndQueryEXT); \ - PROC(GetQueryObjectuivEXT); + PROC(GetQueryObjectuivEXT); \ + PROC(BlitFramebufferANGLE); enum class DebugResourceType { kTexture, diff --git a/engine/src/flutter/shell/platform/windows/compositor_opengl.cc b/engine/src/flutter/shell/platform/windows/compositor_opengl.cc index 515959085c2..2e2de8a90a8 100644 --- a/engine/src/flutter/shell/platform/windows/compositor_opengl.cc +++ b/engine/src/flutter/shell/platform/windows/compositor_opengl.cc @@ -20,6 +20,20 @@ struct FramebufferBackingStore { uint32_t texture_id; }; +typedef const impeller::GLProc BlitFramebufferProc; + +const BlitFramebufferProc& GetBlitFramebufferProc( + const impeller::ProcTableGLES& gl) { + if (gl.BlitFramebuffer.IsAvailable()) { + return gl.BlitFramebuffer; + } else if (gl.BlitFramebufferANGLE.IsAvailable()) { + return gl.BlitFramebufferANGLE; + } + + // CompositorOpenGL::Initialize verifies that a blit procedure is available. + FML_UNREACHABLE(); +} + } // namespace CompositorOpenGL::CompositorOpenGL(FlutterWindowsEngine* engine, @@ -160,16 +174,17 @@ bool CompositorOpenGL::Present(FlutterWindowsView* view, gl_->BindFramebuffer(GL_READ_FRAMEBUFFER, source_id); gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, kWindowFrameBufferId); - gl_->BlitFramebuffer(0, // srcX0 - 0, // srcY0 - width, // srcX1 - height, // srcY1 - 0, // dstX0 - 0, // dstY0 - width, // dstX1 - height, // dstY1 - GL_COLOR_BUFFER_BIT, // mask - GL_NEAREST // filter + auto blitFramebuffer = GetBlitFramebufferProc(*gl_); + blitFramebuffer(0, // srcX0 + 0, // srcY0 + width, // srcX1 + height, // srcY1 + 0, // dstX0 + 0, // dstY0 + width, // dstX1 + height, // dstY1 + GL_COLOR_BUFFER_BIT, // mask + GL_NEAREST // filter ); if (!surface->SwapBuffers()) { @@ -206,6 +221,12 @@ bool CompositorOpenGL::Initialize() { format_.general_format = GL_RGBA; } + if (!gl_->BlitFramebuffer.IsAvailable() && + !gl_->BlitFramebufferANGLE.IsAvailable()) { + FML_LOG(ERROR) << "Unable to find OpenGL blit framebuffer procedure."; + return false; + } + is_initialized_ = true; return true; } diff --git a/engine/src/flutter/shell/platform/windows/compositor_opengl_unittests.cc b/engine/src/flutter/shell/platform/windows/compositor_opengl_unittests.cc index 37099e24163..e518cd2b4aa 100644 --- a/engine/src/flutter/shell/platform/windows/compositor_opengl_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/compositor_opengl_unittests.cc @@ -27,6 +27,14 @@ namespace { using ::testing::AnyNumber; using ::testing::Return; +void MockGetIntegerv(GLenum name, int* value) { + if (name == GL_NUM_EXTENSIONS) { + *value = 1; + } else { + *value = 0; + } +} + const unsigned char* MockGetString(GLenum name) { switch (name) { case GL_VERSION: @@ -37,8 +45,12 @@ const unsigned char* MockGetString(GLenum name) { } } -void MockGetIntegerv(GLenum name, int* value) { - *value = 0; +const unsigned char* MockGetStringi(GLenum name, int index) { + if (name == GL_EXTENSIONS) { + return reinterpret_cast("GL_ANGLE_framebuffer_blit"); + } else { + return reinterpret_cast(""); + } } GLenum MockGetError() { @@ -52,6 +64,8 @@ const impeller::ProcTableGLES::Resolver kMockResolver = [](const char* name) { if (function_name == "glGetString") { return reinterpret_cast(&MockGetString); + } else if (function_name == "glGetStringi") { + return reinterpret_cast(&MockGetStringi); } else if (function_name == "glGetIntegerv") { return reinterpret_cast(&MockGetIntegerv); } else if (function_name == "glGetError") { @@ -163,6 +177,30 @@ TEST_F(CompositorOpenGLTest, InitializationFailure) { EXPECT_FALSE(compositor.CreateBackingStore(config, &backing_store)); } +TEST_F(CompositorOpenGLTest, InitializationRequiresBlit) { + UseHeadlessEngine(); + + const impeller::ProcTableGLES::Resolver resolver = [](const char* name) { + std::string function_name{name}; + + if (function_name == "glBlitFramebuffer" || + function_name == "glBlitFramebufferANGLE") { + return (void*)nullptr; + } + + return kMockResolver(name); + }; + + auto compositor = + CompositorOpenGL{engine(), resolver, /*enable_impeller=*/false}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + EXPECT_CALL(*render_context(), MakeCurrent).WillOnce(Return(true)); + ASSERT_FALSE(compositor.CreateBackingStore(config, &backing_store)); +} + TEST_F(CompositorOpenGLTest, Present) { UseEngineWithView(); @@ -225,5 +263,46 @@ TEST_F(CompositorOpenGLTest, NoSurfaceIgnored) { ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); } +TEST_F(CompositorOpenGLTest, PresentUsingANGLEBlitExtension) { + UseEngineWithView(); + + bool resolved_ANGLE_blit = false; + const impeller::ProcTableGLES::Resolver resolver = + [&resolved_ANGLE_blit](const char* name) { + std::string function_name{name}; + + if (function_name == "glBlitFramebuffer") { + return (void*)nullptr; + } else if (function_name == "glBlitFramebufferANGLE") { + resolved_ANGLE_blit = true; + return reinterpret_cast(&DoNothing); + } + + return kMockResolver(name); + }; + + auto compositor = + CompositorOpenGL{engine(), resolver, /*enable_impeller=*/false}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + EXPECT_CALL(*render_context(), MakeCurrent).WillOnce(Return(true)); + ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); + + FlutterLayer layer = {}; + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + const FlutterLayer* layer_ptr = &layer; + + EXPECT_CALL(*surface(), IsValid).WillRepeatedly(Return(true)); + EXPECT_CALL(*surface(), MakeCurrent).WillOnce(Return(true)); + EXPECT_CALL(*surface(), SwapBuffers).WillOnce(Return(true)); + EXPECT_TRUE(compositor.Present(view(), &layer_ptr, 1)); + EXPECT_TRUE(resolved_ANGLE_blit); + + ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); +} + } // namespace testing } // namespace flutter