From 1d950a7fe33559e9170aeed115ca514574da57a5 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Thu, 14 May 2026 19:50:23 -0700 Subject: [PATCH 01/10] [Impeller] Retire Y-coord-scale plumbing by flipping GLES at the vertex stage (initial MVP) Implements the design from https://github.com/flutter/flutter/issues/186554: absorb the OpenGL ES render-to-texture Y orientation difference once, in impellerc plus RenderPassGLES, so render-to-texture content is now stored top-down on every backend. * impellerc (compiler.cc): for vertex shaders compiled for any GL target (kOpenGLES / kOpenGLDesktop / kRuntimeStageGLES / kRuntimeStageGLES3), inject a hidden `uniform float _impeller_y_flip;` declaration and a matching `gl_Position.y *= _impeller_y_flip;` epilogue at the end of main(). Sits naturally in CreateGLSLCompiler's existing spirv-cross-emission stage, alongside the already-enabled `sl_options.vertex.fixup_clipspace = true;` knob. * RenderPassGLES (render_pass_gles.cc): determine flip_y per pass from `is_wrapped_fbo` (swapchain -> +1.0, offscreen FBO -> -1.0), write the uniform after every pipeline.BindProgram, invert the front-face winding state in lockstep when flipping, and switch the viewport / scissor Y math to the unflipped form for FBO renders (the existing `target_h - y - h` math is preserved for the swapchain so on-screen rendering is unchanged). * TextureGLES::GetYCoordScale: always returns 1.0 now. With the vertex-stage flip in place, GLES render-to-texture content is stored top-down at the GL framebuffer level. The existing per-site `texture_sampler_y_coord_scale` plumbing across the ~20 entity- contents and filter sites carries the value 1.0 and reduces to no-ops, no source changes needed at the call sites. They get retired in a follow-up. --- .../src/flutter/impeller/compiler/compiler.cc | 75 +++++++++++++++++++ .../impeller/compiler/compiler_unittests.cc | 60 +++++++++++++++ .../renderer/backend/gles/render_pass_gles.cc | 61 +++++++++++---- .../renderer/backend/gles/texture_gles.cc | 11 +-- 4 files changed, 184 insertions(+), 23 deletions(-) diff --git a/engine/src/flutter/impeller/compiler/compiler.cc b/engine/src/flutter/impeller/compiler/compiler.cc index c170fb035328b..ed938fc4a7d53 100644 --- a/engine/src/flutter/impeller/compiler/compiler.cc +++ b/engine/src/flutter/impeller/compiler/compiler.cc @@ -33,6 +33,72 @@ constexpr const char* kEGLImageExternalExtension = "GL_OES_EGL_image_external"; constexpr const char* kEGLImageExternalExtension300 = "GL_OES_EGL_image_external_essl3"; constexpr int kVerboseErrorLineThreshold = 6; + +// HAL-populated per pass: +1.0 swapchain, -1.0 offscreen FBO. See +// https://github.com/flutter/flutter/issues/186554. +constexpr const char* kYFlipUniformName = "_impeller_y_flip"; + +bool IsGLTargetPlatform(TargetPlatform platform) { + return platform == TargetPlatform::kOpenGLES || + platform == TargetPlatform::kOpenGLDesktop || + platform == TargetPlatform::kRuntimeStageGLES || + platform == TargetPlatform::kRuntimeStageGLES3; +} + +// Appends `gl_Position.y *= _impeller_y_flip;` to main() and declares +// the uniform, into a GLSL vertex shader emitted by spirv-cross. +std::string InjectYFlipForGLESVertexShader(std::string source) { + // Scan main()'s body tracking brace depth to find its closing `}`. + const std::string epilogue = + std::string(" gl_Position.y *= ") + kYFlipUniformName + ";\n"; + + const size_t main_pos = source.find("void main"); + if (main_pos == std::string::npos) { + return source; + } + const size_t open_brace = source.find('{', main_pos); + if (open_brace == std::string::npos) { + return source; + } + int depth = 1; + for (size_t i = open_brace + 1; i < source.size(); ++i) { + const char c = source[i]; + if (c == '{') { + ++depth; + } else if (c == '}') { + --depth; + if (depth == 0) { + source.insert(i, epilogue); + break; + } + } + } + + // Insert the uniform after the last `precision` directive, falling + // back to right after `#version`. + const std::string declaration = + std::string("\nuniform float ") + kYFlipUniformName + ";\n"; + size_t inject_at = std::string::npos; + for (size_t pos = source.find("\nprecision "); pos != std::string::npos; + pos = source.find("\nprecision ", pos + 1)) { + const size_t eol = source.find('\n', pos + 1); + if (eol == std::string::npos) { + break; + } + inject_at = eol; + } + if (inject_at == std::string::npos) { + const size_t version_pos = source.find("#version"); + if (version_pos != std::string::npos) { + inject_at = source.find('\n', version_pos); + } + } + if (inject_at == std::string::npos) { + inject_at = 0; + } + source.insert(inject_at, declaration); + return source; +} } // namespace // This value should be <= 7372. UBOs can be larger on some devices but a @@ -451,6 +517,15 @@ Compiler::Compiler(const std::shared_ptr& source_mapping, // for Vulkan. The reflector needs information that is only valid after a // successful compilation call. auto sl_compilation_result_str = sl_compiler.GetCompiler()->compile(); + + // GL vertex shaders get a y-flip epilogue; see + // https://github.com/flutter/flutter/issues/186554. + if (IsGLTargetPlatform(source_options.target_platform) && + source_options.type == SourceType::kVertexShader) { + sl_compilation_result_str = + InjectYFlipForGLESVertexShader(std::move(sl_compilation_result_str)); + } + auto sl_compilation_result = CreateMappingWithString(sl_compilation_result_str); diff --git a/engine/src/flutter/impeller/compiler/compiler_unittests.cc b/engine/src/flutter/impeller/compiler/compiler_unittests.cc index f3aca32ea8f65..fabc4637c21bd 100644 --- a/engine/src/flutter/impeller/compiler/compiler_unittests.cc +++ b/engine/src/flutter/impeller/compiler/compiler_unittests.cc @@ -79,6 +79,66 @@ TEST(CompilerTest, Defines) { EXPECT_NE(compiler_2.GetSPIRVAssembly(), nullptr); } +TEST(CompilerTest, YFlipInjectionForGLESVertexShaders) { + // Compiles `fixture_name` for `platform` and returns the generated SL + // source. See https://github.com/flutter/flutter/issues/186554. + auto compile = [](const char* fixture_name, SourceType type, + TargetPlatform platform) -> std::string { + std::shared_ptr fixture = + flutter::testing::OpenFixtureAsMapping(fixture_name); + FML_CHECK(fixture); + + SourceOptions options(fixture_name, type); + options.source_language = SourceLanguage::kGLSL; + options.target_platform = platform; + options.working_directory = std::make_shared( + flutter::testing::OpenFixturesDirectory()); + options.entry_point_name = "main"; + + Reflector::Options reflector_options; + reflector_options.target_platform = platform; + reflector_options.header_file_name = "y_flip_injection.h"; + reflector_options.shader_name = "shader"; + + Compiler compiler(fixture, options, reflector_options); + if (!compiler.IsValid()) { + return ""; + } + auto sl = compiler.GetSLShaderSource(); + if (!sl || !sl->GetMapping()) { + return ""; + } + return std::string(reinterpret_cast(sl->GetMapping()), + sl->GetSize()); + }; + + // GL vertex shader: gets both the declaration and the epilogue. + const std::string gl_vert = compile( + "sample.vert", SourceType::kVertexShader, TargetPlatform::kOpenGLES); + EXPECT_NE(gl_vert.find("uniform float _impeller_y_flip"), + std::string::npos) + << "GLES vertex shader is missing the y-flip uniform declaration:\n" + << gl_vert; + EXPECT_NE(gl_vert.find("gl_Position.y *= _impeller_y_flip"), + std::string::npos) + << "GLES vertex shader is missing the y-flip epilogue:\n" + << gl_vert; + + // GL fragment shader: not injected. + const std::string gl_frag = compile( + "sample.frag", SourceType::kFragmentShader, TargetPlatform::kOpenGLES); + EXPECT_EQ(gl_frag.find("_impeller_y_flip"), std::string::npos) + << "GLES fragment shader was unexpectedly injected:\n" + << gl_frag; + + // Metal vertex shader: not injected. + const std::string mtl_vert = compile( + "sample.vert", SourceType::kVertexShader, TargetPlatform::kMetalIOS); + EXPECT_EQ(mtl_vert.find("_impeller_y_flip"), std::string::npos) + << "Metal vertex shader was unexpectedly injected:\n" + << mtl_vert; +} + TEST(CompilerTest, ShaderKindMatchingIsSuccessful) { ASSERT_EQ(SourceTypeFromFileName("hello.vert"), SourceType::kVertexShader); ASSERT_EQ(SourceTypeFromFileName("hello.frag"), SourceType::kFragmentShader); 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 f9b501426b0ec..df03b47ff60c1 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 @@ -193,6 +193,7 @@ static void EncodeViewport(const ProcTableGLES& gl, const RenderPassData& pass_data, const std::optional& command_viewport, const ISize& target_size, + bool flip_y, std::optional& current_viewport) { auto new_viewport = command_viewport.value_or(pass_data.viewport); @@ -204,12 +205,16 @@ static void EncodeViewport(const ProcTableGLES& gl, current_viewport = new_viewport; + // FBO passes flip in the vertex shader; swapchain keeps the old + // top-down -> bottom-up viewport conversion. + const auto viewport_y_gl = + flip_y ? new_viewport.rect.GetY() + : target_size.height - new_viewport.rect.GetY() - + new_viewport.rect.GetHeight(); gl.Viewport(new_viewport.rect.GetX(), // x - target_size.height - new_viewport.rect.GetY() - - new_viewport.rect.GetHeight(), // y - new_viewport.rect.GetWidth(), // width - new_viewport.rect.GetHeight() // height - ); + viewport_y_gl, // y + new_viewport.rect.GetWidth(), + new_viewport.rect.GetHeight()); if (pass_data.depth_attachment) { if (gl.DepthRangef.IsAvailable()) { gl.DepthRangef(new_viewport.depth_range.z_near, @@ -336,10 +341,16 @@ static void EncodeViewport(const ProcTableGLES& gl, // is bottom left origin, so we convert the coordinates here. ISize target_size = pass_data.color_attachment->GetSize(); + // 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 float y_flip_value = flip_y ? -1.0f : 1.0f; + std::optional current_viewport; CullMode current_cull_mode = CullMode::kNone; WindingOrder current_winding_order = WindingOrder::kClockwise; - gl.FrontFace(GL_CW); + // Inverted to keep front-facing consistent under the vertex y-flip. + gl.FrontFace(flip_y ? GL_CCW : GL_CW); for (const auto& command : commands) { #ifdef IMPELLER_DEBUG @@ -392,6 +403,7 @@ static void EncodeViewport(const ProcTableGLES& gl, pass_data, // command.viewport, // target_size, // + flip_y, // current_viewport // ); @@ -401,12 +413,14 @@ static void EncodeViewport(const ProcTableGLES& gl, if (command.scissor.has_value()) { const auto& scissor = command.scissor.value(); gl.Enable(GL_SCISSOR_TEST); - gl.Scissor( - scissor.GetX(), // x - target_size.height - scissor.GetY() - scissor.GetHeight(), // y - scissor.GetWidth(), // width - scissor.GetHeight() // height - ); + // Same flip handling as the viewport above. + const auto scissor_y_gl = + flip_y ? scissor.GetY() + : target_size.height - scissor.GetY() - scissor.GetHeight(); + gl.Scissor(scissor.GetX(), // x + scissor_y_gl, // y + scissor.GetWidth(), + scissor.GetHeight()); } //-------------------------------------------------------------------------- @@ -431,17 +445,18 @@ static void EncodeViewport(const ProcTableGLES& gl, } //-------------------------------------------------------------------------- - /// Setup winding order. - /// + /// Setup winding order. The pipeline's winding is inverted when + /// `flip_y` is in effect (the vertex flip reverses the rasterizer's + /// view of winding). WindingOrder pipeline_winding_order = pipeline.GetDescriptor().GetWindingOrder(); if (current_winding_order != pipeline_winding_order) { switch (pipeline.GetDescriptor().GetWindingOrder()) { case WindingOrder::kClockwise: - gl.FrontFace(GL_CW); + gl.FrontFace(flip_y ? GL_CCW : GL_CW); break; case WindingOrder::kCounterClockwise: - gl.FrontFace(GL_CCW); + gl.FrontFace(flip_y ? GL_CW : GL_CCW); break; } current_winding_order = pipeline_winding_order; @@ -471,6 +486,20 @@ static void EncodeViewport(const ProcTableGLES& gl, return false; } + //-------------------------------------------------------------------------- + /// Bind the hidden y-flip uniform if the vertex shader declares it. + { + const auto program_handle = + reactor.GetGLHandle(pipeline.GetProgramHandle()); + if (program_handle.has_value()) { + const GLint y_flip_loc = + gl.GetUniformLocation(*program_handle, "_impeller_y_flip"); + if (y_flip_loc >= 0) { + gl.Uniform1fv(y_flip_loc, 1, &y_flip_value); + } + } + } + //-------------------------------------------------------------------------- /// Bind uniform data. /// 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 f54b05d56204d..01a8a9faa7611 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc @@ -649,13 +649,10 @@ bool TextureGLES::SetAsFramebufferAttachment(GLenum target, // |Texture| Scalar TextureGLES::GetYCoordScale() const { - switch (GetCoordinateSystem()) { - case TextureCoordinateSystem::kUploadFromHost: - return 1.0; - case TextureCoordinateSystem::kRenderToTexture: - return -1.0; - } - FML_UNREACHABLE(); + // GLES render-to-texture content is stored top-down via the + // vertex-stage y-flip; see + // https://github.com/flutter/flutter/issues/186554. + return 1.0; } bool TextureGLES::IsWrapped() const { From 3e8854f3fce5ff7b18bd16e14216d8c41600baac Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 15 May 2026 02:29:37 -0700 Subject: [PATCH 02/10] Retrigger Windows CI after infra cancellation From bf05eaaea7990a72c23d448b304accf380861d30 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 15 May 2026 18:48:33 -0700 Subject: [PATCH 03/10] [Impeller] Address review: cache y-flip uniform, wrap main instead of brace counting Two changes addressing gemini's review and gaaclarke's reply: 1. **Cache `_impeller_y_flip` location at link time** (gemini high-priority). `PipelineGLES::BuildVertexDescriptor` now resolves the uniform once via `glGetUniformLocation` and stashes it in `y_flip_uniform_location_`. `RenderPassGLES` reads it through the new `PipelineGLES::GetYFlipUniformLocation()` getter instead of calling `glGetUniformLocation` for every command in the inner loop. -1 still means "not present" (fragment-only program, no injection, etc.) and skips the write. Net: one location query per pipeline link, zero per draw. 2. **Wrap `main` instead of brace-counting an epilogue** (gemini medium, gaaclarke seconded). `InjectYFlipForGLESVertexShader` now renames the SPIRV-Cross-emitted `void main(` to `void _impeller_user_main(` and appends a fresh `main()` that calls the renamed user entry point and applies `gl_Position.y *= _impeller_y_flip;` afterward. The wrap pattern makes the flip run on every control-flow exit including early `return`s (gemini's first concern), and the rename anchored on `\nvoid main(` in spirv-cross's mechanical, comment-free output is unambiguous (gemini's second concern). A SPIRV-Cross `SPIRFunction::fixup_hooks_out` based approach was investigated but needs subclassing CompilerGLSL to reach protected emit machinery; leaving that as a future cleanup. New test `YFlipInjectionHandlesEarlyReturnsInGLESVertexShader` uses a new `y_flip_early_return.vert` fixture (an `if (...) { ...; return; }` path before the implicit final return) and asserts the wrapper exists, calls the renamed user entry, and is the only remaining `void main(` in the emitted source. --- .../src/flutter/impeller/compiler/compiler.cc | 71 ++++++++++++------- .../impeller/compiler/compiler_unittests.cc | 63 ++++++++++++++-- engine/src/flutter/impeller/fixtures/BUILD.gn | 1 + .../fixtures/y_flip_early_return.vert | 24 +++++++ .../renderer/backend/gles/pipeline_gles.cc | 5 ++ .../renderer/backend/gles/pipeline_gles.h | 10 +++ .../renderer/backend/gles/render_pass_gles.cc | 37 +++++----- 7 files changed, 157 insertions(+), 54 deletions(-) create mode 100644 engine/src/flutter/impeller/fixtures/y_flip_early_return.vert diff --git a/engine/src/flutter/impeller/compiler/compiler.cc b/engine/src/flutter/impeller/compiler/compiler.cc index ed938fc4a7d53..5f746014fb5ab 100644 --- a/engine/src/flutter/impeller/compiler/compiler.cc +++ b/engine/src/flutter/impeller/compiler/compiler.cc @@ -38,6 +38,10 @@ constexpr int kVerboseErrorLineThreshold = 6; // https://github.com/flutter/flutter/issues/186554. constexpr const char* kYFlipUniformName = "_impeller_y_flip"; +// Renamed entry point for the user's original `main` after wrapping. See +// `InjectYFlipForGLESVertexShader` below. +constexpr const char* kUserMainName = "_impeller_user_main"; + bool IsGLTargetPlatform(TargetPlatform platform) { return platform == TargetPlatform::kOpenGLES || platform == TargetPlatform::kOpenGLDesktop || @@ -45,37 +49,50 @@ bool IsGLTargetPlatform(TargetPlatform platform) { platform == TargetPlatform::kRuntimeStageGLES3; } -// Appends `gl_Position.y *= _impeller_y_flip;` to main() and declares -// the uniform, into a GLSL vertex shader emitted by spirv-cross. +// Declares `uniform float _impeller_y_flip;` and wraps the vertex +// shader's entry point so that `gl_Position.y *= _impeller_y_flip;` +// applies to every code path through the shader, including early returns. +// +// Implementation: rename the SPIRV-Cross-emitted `void main(` to +// `void _impeller_user_main(`, then append a new `main()` that calls the +// renamed user entry point and applies the flip. Wrapping the entry +// point this way (instead of inserting an epilogue before the closing +// brace) handles early `return`s automatically: any return inside the +// user's original `main` exits that function, control returns to the +// new wrapper, and the wrapper applies the flip before returning. +// +// SPIRV-Cross's output is mechanical (comment-free, deterministic +// indentation, exactly one `void main(` per shader), so the rename is +// unambiguous. Long-term we could push this into SPIRV-Cross itself via +// `SPIRFunction::fixup_hooks_out` (the same mechanism `flip_vert_y` +// uses) but that requires subclassing CompilerGLSL and reaching into +// protected emit machinery; the wrap-main approach is functionally +// equivalent for our needs. std::string InjectYFlipForGLESVertexShader(std::string source) { - // Scan main()'s body tracking brace depth to find its closing `}`. - const std::string epilogue = - std::string(" gl_Position.y *= ") + kYFlipUniformName + ";\n"; - - const size_t main_pos = source.find("void main"); + // Rename the user's `void main(...)` to `void _impeller_user_main(...)`. + // We anchor on `\nvoid main(` to avoid matching the substring inside an + // unlikely declaration; SPIRV-Cross emits the entry point as a + // top-level function so the leading newline is reliable. + constexpr std::string_view kMainPattern = "\nvoid main("; + const size_t main_pos = source.find(kMainPattern); if (main_pos == std::string::npos) { return source; } - const size_t open_brace = source.find('{', main_pos); - if (open_brace == std::string::npos) { - return source; - } - int depth = 1; - for (size_t i = open_brace + 1; i < source.size(); ++i) { - const char c = source[i]; - if (c == '{') { - ++depth; - } else if (c == '}') { - --depth; - if (depth == 0) { - source.insert(i, epilogue); - break; - } - } - } - - // Insert the uniform after the last `precision` directive, falling - // back to right after `#version`. + const std::string user_main_decl = + std::string("\nvoid ") + kUserMainName + "("; + source.replace(main_pos, kMainPattern.size(), user_main_decl); + + // Append the wrapper at the end of the file. Always finishes with a + // trailing newline so adjacent output stays clean. + std::string wrapper = "\nvoid main() {\n "; + wrapper += kUserMainName; + wrapper += "();\n gl_Position.y *= "; + wrapper += kYFlipUniformName; + wrapper += ";\n}\n"; + source.append(wrapper); + + // Inject the uniform declaration after the last `precision` directive, + // falling back to right after `#version`, falling back to the top. const std::string declaration = std::string("\nuniform float ") + kYFlipUniformName + ";\n"; size_t inject_at = std::string::npos; diff --git a/engine/src/flutter/impeller/compiler/compiler_unittests.cc b/engine/src/flutter/impeller/compiler/compiler_unittests.cc index fabc4637c21bd..2ef1d935a4171 100644 --- a/engine/src/flutter/impeller/compiler/compiler_unittests.cc +++ b/engine/src/flutter/impeller/compiler/compiler_unittests.cc @@ -113,10 +113,9 @@ TEST(CompilerTest, YFlipInjectionForGLESVertexShaders) { }; // GL vertex shader: gets both the declaration and the epilogue. - const std::string gl_vert = compile( - "sample.vert", SourceType::kVertexShader, TargetPlatform::kOpenGLES); - EXPECT_NE(gl_vert.find("uniform float _impeller_y_flip"), - std::string::npos) + const std::string gl_vert = compile("sample.vert", SourceType::kVertexShader, + TargetPlatform::kOpenGLES); + EXPECT_NE(gl_vert.find("uniform float _impeller_y_flip"), std::string::npos) << "GLES vertex shader is missing the y-flip uniform declaration:\n" << gl_vert; EXPECT_NE(gl_vert.find("gl_Position.y *= _impeller_y_flip"), @@ -132,13 +131,65 @@ TEST(CompilerTest, YFlipInjectionForGLESVertexShaders) { << gl_frag; // Metal vertex shader: not injected. - const std::string mtl_vert = compile( - "sample.vert", SourceType::kVertexShader, TargetPlatform::kMetalIOS); + const std::string mtl_vert = compile("sample.vert", SourceType::kVertexShader, + TargetPlatform::kMetalIOS); EXPECT_EQ(mtl_vert.find("_impeller_y_flip"), std::string::npos) << "Metal vertex shader was unexpectedly injected:\n" << mtl_vert; } +TEST(CompilerTest, YFlipInjectionHandlesEarlyReturnsInGLESVertexShader) { + // `y_flip_early_return.vert` has an `if (...) { ...; return; }` before the + // implicit final return at the end of `main`. The wrap-`main` injection + // strategy must move the user's body into a renamed inner function and + // emit a wrapper that applies the y-flip after the call, so both the + // early and the implicit return paths run through the flip. + std::shared_ptr fixture = + flutter::testing::OpenFixtureAsMapping("y_flip_early_return.vert"); + FML_CHECK(fixture); + + SourceOptions options("y_flip_early_return.vert", SourceType::kVertexShader); + options.source_language = SourceLanguage::kGLSL; + options.target_platform = TargetPlatform::kOpenGLES; + options.working_directory = std::make_shared( + flutter::testing::OpenFixturesDirectory()); + options.entry_point_name = "main"; + + Reflector::Options reflector_options; + reflector_options.target_platform = TargetPlatform::kOpenGLES; + reflector_options.header_file_name = "y_flip_early_return.h"; + reflector_options.shader_name = "shader"; + + Compiler compiler(fixture, options, reflector_options); + ASSERT_TRUE(compiler.IsValid()); + auto sl = compiler.GetSLShaderSource(); + ASSERT_TRUE(sl && sl->GetMapping()); + const std::string gl_vert(reinterpret_cast(sl->GetMapping()), + sl->GetSize()); + + // The user's `void main(...)` should have been renamed to + // `void _impeller_user_main(...)`, with a new entry point appended at the + // end of the source. + EXPECT_NE(gl_vert.find("void _impeller_user_main("), std::string::npos) + << "Expected user entry-point rename:\n" + << gl_vert; + EXPECT_NE(gl_vert.find("_impeller_user_main();"), std::string::npos) + << "Expected wrapper to call the renamed user entry point:\n" + << gl_vert; + EXPECT_NE(gl_vert.find("gl_Position.y *= _impeller_y_flip"), + std::string::npos) + << "Expected y-flip in the wrapper:\n" + << gl_vert; + + // Exactly one definition of `void main(` should remain (the wrapper); + // the original was renamed. + const size_t first_main = gl_vert.find("\nvoid main("); + ASSERT_NE(first_main, std::string::npos); + EXPECT_EQ(gl_vert.find("\nvoid main(", first_main + 1), std::string::npos) + << "Expected only the wrapper's `void main(` to remain after rename:\n" + << gl_vert; +} + TEST(CompilerTest, ShaderKindMatchingIsSuccessful) { ASSERT_EQ(SourceTypeFromFileName("hello.vert"), SourceType::kVertexShader); ASSERT_EQ(SourceTypeFromFileName("hello.frag"), SourceType::kFragmentShader); diff --git a/engine/src/flutter/impeller/fixtures/BUILD.gn b/engine/src/flutter/impeller/fixtures/BUILD.gn index d062f86f0b91c..4b581ce9fb6dd 100644 --- a/engine/src/flutter/impeller/fixtures/BUILD.gn +++ b/engine/src/flutter/impeller/fixtures/BUILD.gn @@ -183,6 +183,7 @@ test_fixtures("file_fixtures") { "wtf.otf", "texture_lookup.frag", "mat2_test.frag", + "y_flip_early_return.vert", ] if (host_os == "mac") { fixtures += [ "/System/Library/Fonts/Apple Color Emoji.ttc" ] diff --git a/engine/src/flutter/impeller/fixtures/y_flip_early_return.vert b/engine/src/flutter/impeller/fixtures/y_flip_early_return.vert new file mode 100644 index 0000000000000..b8c480012411c --- /dev/null +++ b/engine/src/flutter/impeller/fixtures/y_flip_early_return.vert @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Vertex shader with multiple `return` paths. Used by +// `YFlipInjectionHandlesEarlyReturnsInGLESVertexShader` in +// `compiler_unittests.cc` to confirm the y-flip wrapper applies on every +// control-flow exit, not only the implicit one at the end of `main`. See +// https://github.com/flutter/flutter/issues/186554. + +uniform UniformBufferObject { + float discard_flag; +} +ubo; + +in vec2 inPosition; + +void main() { + if (ubo.discard_flag > 0.5) { + gl_Position = vec4(0.0); + return; + } + gl_Position = vec4(inPosition, 0.0, 1.0); +} diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.cc index e173ce167be85..d2fdd29f82090 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.cc @@ -53,6 +53,11 @@ bool PipelineGLES::BuildVertexDescriptor(const ProcTableGLES& gl, if (!vtx_desc->ReadUniformsBindings(gl, program)) { return false; } + // Resolve the injected y-flip uniform once at link time. If the vertex + // shader doesn't declare it (fragment-only program, non-vertex pipeline, + // or future opt-out), `glGetUniformLocation` returns -1 and the render + // pass skips the write. + y_flip_uniform_location_ = gl.GetUniformLocation(program, "_impeller_y_flip"); buffer_bindings_ = std::move(vtx_desc); return true; } diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.h index ddbebe9ca656b..97240070ee13e 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.h @@ -39,6 +39,15 @@ class PipelineGLES final [[nodiscard]] bool BuildVertexDescriptor(const ProcTableGLES& gl, GLuint program); + // GL location of the `_impeller_y_flip` uniform injected into vertex + // shaders by `impellerc` (see + // https://github.com/flutter/flutter/issues/186554). Resolved once at link + // time so the hot render-pass command loop can do a single + // `gl.Uniform1fv` instead of a per-command `glGetUniformLocation`. -1 if + // the shader does not declare the uniform (e.g. a fragment-only pipeline + // or a pipeline built before the injection landed). + GLint GetYFlipUniformLocation() const { return y_flip_uniform_location_; } + private: friend PipelineLibraryGLES; friend class testing::RenderPassGLESViewportTest; @@ -46,6 +55,7 @@ class PipelineGLES final std::shared_ptr reactor_; std::shared_ptr handle_; std::unique_ptr buffer_bindings_; + GLint y_flip_uniform_location_ = -1; bool is_valid_ = false; // |Pipeline| 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 df03b47ff60c1..800eb0c275669 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 @@ -207,14 +207,13 @@ static void EncodeViewport(const ProcTableGLES& gl, // FBO passes flip in the vertex shader; swapchain keeps the old // top-down -> bottom-up viewport conversion. - const auto viewport_y_gl = - flip_y ? new_viewport.rect.GetY() - : target_size.height - new_viewport.rect.GetY() - - new_viewport.rect.GetHeight(); + const auto viewport_y_gl = flip_y ? new_viewport.rect.GetY() + : target_size.height - + new_viewport.rect.GetY() - + new_viewport.rect.GetHeight(); gl.Viewport(new_viewport.rect.GetX(), // x viewport_y_gl, // y - new_viewport.rect.GetWidth(), - new_viewport.rect.GetHeight()); + new_viewport.rect.GetWidth(), new_viewport.rect.GetHeight()); if (pass_data.depth_attachment) { if (gl.DepthRangef.IsAvailable()) { gl.DepthRangef(new_viewport.depth_range.z_near, @@ -417,10 +416,9 @@ static void EncodeViewport(const ProcTableGLES& gl, const auto scissor_y_gl = flip_y ? scissor.GetY() : target_size.height - scissor.GetY() - scissor.GetHeight(); - gl.Scissor(scissor.GetX(), // x - scissor_y_gl, // y - scissor.GetWidth(), - scissor.GetHeight()); + gl.Scissor(scissor.GetX(), // x + scissor_y_gl, // y + scissor.GetWidth(), scissor.GetHeight()); } //-------------------------------------------------------------------------- @@ -487,17 +485,14 @@ static void EncodeViewport(const ProcTableGLES& gl, } //-------------------------------------------------------------------------- - /// Bind the hidden y-flip uniform if the vertex shader declares it. - { - const auto program_handle = - reactor.GetGLHandle(pipeline.GetProgramHandle()); - if (program_handle.has_value()) { - const GLint y_flip_loc = - gl.GetUniformLocation(*program_handle, "_impeller_y_flip"); - if (y_flip_loc >= 0) { - gl.Uniform1fv(y_flip_loc, 1, &y_flip_value); - } - } + /// Bind the hidden y-flip uniform if the vertex shader declares it. The + /// location is resolved once at pipeline link time (see + /// `PipelineGLES::GetYFlipUniformLocation`), so the inner command loop + /// does a single `Uniform1fv` instead of a per-draw + /// `glGetUniformLocation`. + const GLint y_flip_loc = pipeline.GetYFlipUniformLocation(); + if (y_flip_loc >= 0) { + gl.Uniform1fv(y_flip_loc, 1, &y_flip_value); } //-------------------------------------------------------------------------- From a8cbd77f34c0f5e53a763f74e46e5535d04f8598 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 15 May 2026 19:46:04 -0700 Subject: [PATCH 04/10] [Impeller] Trim verbose comments on the y-flip review fix --- .../src/flutter/impeller/compiler/compiler.cc | 42 +++++-------------- .../impeller/compiler/compiler_unittests.cc | 17 ++------ .../fixtures/y_flip_early_return.vert | 7 +--- .../renderer/backend/gles/pipeline_gles.cc | 5 +-- .../renderer/backend/gles/pipeline_gles.h | 9 +--- .../renderer/backend/gles/render_pass_gles.cc | 6 +-- 6 files changed, 19 insertions(+), 67 deletions(-) diff --git a/engine/src/flutter/impeller/compiler/compiler.cc b/engine/src/flutter/impeller/compiler/compiler.cc index 5f746014fb5ab..4a8abceffc35d 100644 --- a/engine/src/flutter/impeller/compiler/compiler.cc +++ b/engine/src/flutter/impeller/compiler/compiler.cc @@ -34,12 +34,9 @@ constexpr const char* kEGLImageExternalExtension300 = "GL_OES_EGL_image_external_essl3"; constexpr int kVerboseErrorLineThreshold = 6; -// HAL-populated per pass: +1.0 swapchain, -1.0 offscreen FBO. See -// https://github.com/flutter/flutter/issues/186554. +// Set per pass by RenderPassGLES: +1.0 swapchain, -1.0 offscreen FBO. See +// flutter/flutter#186554. constexpr const char* kYFlipUniformName = "_impeller_y_flip"; - -// Renamed entry point for the user's original `main` after wrapping. See -// `InjectYFlipForGLESVertexShader` below. constexpr const char* kUserMainName = "_impeller_user_main"; bool IsGLTargetPlatform(TargetPlatform platform) { @@ -49,30 +46,13 @@ bool IsGLTargetPlatform(TargetPlatform platform) { platform == TargetPlatform::kRuntimeStageGLES3; } -// Declares `uniform float _impeller_y_flip;` and wraps the vertex -// shader's entry point so that `gl_Position.y *= _impeller_y_flip;` -// applies to every code path through the shader, including early returns. -// -// Implementation: rename the SPIRV-Cross-emitted `void main(` to -// `void _impeller_user_main(`, then append a new `main()` that calls the -// renamed user entry point and applies the flip. Wrapping the entry -// point this way (instead of inserting an epilogue before the closing -// brace) handles early `return`s automatically: any return inside the -// user's original `main` exits that function, control returns to the -// new wrapper, and the wrapper applies the flip before returning. -// -// SPIRV-Cross's output is mechanical (comment-free, deterministic -// indentation, exactly one `void main(` per shader), so the rename is -// unambiguous. Long-term we could push this into SPIRV-Cross itself via -// `SPIRFunction::fixup_hooks_out` (the same mechanism `flip_vert_y` -// uses) but that requires subclassing CompilerGLSL and reaching into -// protected emit machinery; the wrap-main approach is functionally -// equivalent for our needs. +// Wraps the SPIRV-Cross-emitted entry point so `gl_Position.y *= +// _impeller_y_flip;` runs on every exit, including early returns. Renames +// `void main(` to `void _impeller_user_main(` and appends a wrapper that +// calls it then applies the flip. std::string InjectYFlipForGLESVertexShader(std::string source) { - // Rename the user's `void main(...)` to `void _impeller_user_main(...)`. - // We anchor on `\nvoid main(` to avoid matching the substring inside an - // unlikely declaration; SPIRV-Cross emits the entry point as a - // top-level function so the leading newline is reliable. + // Anchor on leading newline; spirv-cross emits the entry point at file + // scope, so the match is unambiguous in its comment-free output. constexpr std::string_view kMainPattern = "\nvoid main("; const size_t main_pos = source.find(kMainPattern); if (main_pos == std::string::npos) { @@ -82,8 +62,6 @@ std::string InjectYFlipForGLESVertexShader(std::string source) { std::string("\nvoid ") + kUserMainName + "("; source.replace(main_pos, kMainPattern.size(), user_main_decl); - // Append the wrapper at the end of the file. Always finishes with a - // trailing newline so adjacent output stays clean. std::string wrapper = "\nvoid main() {\n "; wrapper += kUserMainName; wrapper += "();\n gl_Position.y *= "; @@ -91,8 +69,8 @@ std::string InjectYFlipForGLESVertexShader(std::string source) { wrapper += ";\n}\n"; source.append(wrapper); - // Inject the uniform declaration after the last `precision` directive, - // falling back to right after `#version`, falling back to the top. + // Declare the uniform after the last `precision` directive, falling + // back to right after `#version`, falling back to the top. const std::string declaration = std::string("\nuniform float ") + kYFlipUniformName + ";\n"; size_t inject_at = std::string::npos; diff --git a/engine/src/flutter/impeller/compiler/compiler_unittests.cc b/engine/src/flutter/impeller/compiler/compiler_unittests.cc index 2ef1d935a4171..b884fce9100b2 100644 --- a/engine/src/flutter/impeller/compiler/compiler_unittests.cc +++ b/engine/src/flutter/impeller/compiler/compiler_unittests.cc @@ -139,11 +139,8 @@ TEST(CompilerTest, YFlipInjectionForGLESVertexShaders) { } TEST(CompilerTest, YFlipInjectionHandlesEarlyReturnsInGLESVertexShader) { - // `y_flip_early_return.vert` has an `if (...) { ...; return; }` before the - // implicit final return at the end of `main`. The wrap-`main` injection - // strategy must move the user's body into a renamed inner function and - // emit a wrapper that applies the y-flip after the call, so both the - // early and the implicit return paths run through the flip. + // `y_flip_early_return.vert` has an early `return` before main's implicit + // exit; the wrap-main injection must flip on both paths. std::shared_ptr fixture = flutter::testing::OpenFixtureAsMapping("y_flip_early_return.vert"); FML_CHECK(fixture); @@ -167,26 +164,18 @@ TEST(CompilerTest, YFlipInjectionHandlesEarlyReturnsInGLESVertexShader) { const std::string gl_vert(reinterpret_cast(sl->GetMapping()), sl->GetSize()); - // The user's `void main(...)` should have been renamed to - // `void _impeller_user_main(...)`, with a new entry point appended at the - // end of the source. EXPECT_NE(gl_vert.find("void _impeller_user_main("), std::string::npos) - << "Expected user entry-point rename:\n" << gl_vert; EXPECT_NE(gl_vert.find("_impeller_user_main();"), std::string::npos) - << "Expected wrapper to call the renamed user entry point:\n" << gl_vert; EXPECT_NE(gl_vert.find("gl_Position.y *= _impeller_y_flip"), std::string::npos) - << "Expected y-flip in the wrapper:\n" << gl_vert; - // Exactly one definition of `void main(` should remain (the wrapper); - // the original was renamed. + // Only the wrapper's `void main(` should remain after the rename. const size_t first_main = gl_vert.find("\nvoid main("); ASSERT_NE(first_main, std::string::npos); EXPECT_EQ(gl_vert.find("\nvoid main(", first_main + 1), std::string::npos) - << "Expected only the wrapper's `void main(` to remain after rename:\n" << gl_vert; } diff --git a/engine/src/flutter/impeller/fixtures/y_flip_early_return.vert b/engine/src/flutter/impeller/fixtures/y_flip_early_return.vert index b8c480012411c..690648f07f519 100644 --- a/engine/src/flutter/impeller/fixtures/y_flip_early_return.vert +++ b/engine/src/flutter/impeller/fixtures/y_flip_early_return.vert @@ -2,11 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Vertex shader with multiple `return` paths. Used by -// `YFlipInjectionHandlesEarlyReturnsInGLESVertexShader` in -// `compiler_unittests.cc` to confirm the y-flip wrapper applies on every -// control-flow exit, not only the implicit one at the end of `main`. See -// https://github.com/flutter/flutter/issues/186554. +// Vertex shader with an early return; exercises the y-flip wrap-main +// path. See flutter/flutter#186554. uniform UniformBufferObject { float discard_flag; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.cc index d2fdd29f82090..6ac83ecbed7c1 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.cc @@ -53,10 +53,7 @@ bool PipelineGLES::BuildVertexDescriptor(const ProcTableGLES& gl, if (!vtx_desc->ReadUniformsBindings(gl, program)) { return false; } - // Resolve the injected y-flip uniform once at link time. If the vertex - // shader doesn't declare it (fragment-only program, non-vertex pipeline, - // or future opt-out), `glGetUniformLocation` returns -1 and the render - // pass skips the write. + // Cache the y-flip uniform; -1 if not declared. y_flip_uniform_location_ = gl.GetUniformLocation(program, "_impeller_y_flip"); buffer_bindings_ = std::move(vtx_desc); return true; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.h index 97240070ee13e..101a1cb768164 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_gles.h @@ -39,13 +39,8 @@ class PipelineGLES final [[nodiscard]] bool BuildVertexDescriptor(const ProcTableGLES& gl, GLuint program); - // GL location of the `_impeller_y_flip` uniform injected into vertex - // shaders by `impellerc` (see - // https://github.com/flutter/flutter/issues/186554). Resolved once at link - // time so the hot render-pass command loop can do a single - // `gl.Uniform1fv` instead of a per-command `glGetUniformLocation`. -1 if - // the shader does not declare the uniform (e.g. a fragment-only pipeline - // or a pipeline built before the injection landed). + // GL location of the `_impeller_y_flip` uniform (flutter/flutter#186554), + // cached at link time. -1 if the shader doesn't declare it. GLint GetYFlipUniformLocation() const { return y_flip_uniform_location_; } private: 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 800eb0c275669..32380fe2a6edf 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 @@ -485,11 +485,7 @@ static void EncodeViewport(const ProcTableGLES& gl, } //-------------------------------------------------------------------------- - /// Bind the hidden y-flip uniform if the vertex shader declares it. The - /// location is resolved once at pipeline link time (see - /// `PipelineGLES::GetYFlipUniformLocation`), so the inner command loop - /// does a single `Uniform1fv` instead of a per-draw - /// `glGetUniformLocation`. + /// Bind the y-flip uniform if the vertex shader declares it. const GLint y_flip_loc = pipeline.GetYFlipUniformLocation(); if (y_flip_loc >= 0) { gl.Uniform1fv(y_flip_loc, 1, &y_flip_value); From ef7fbc5fbbf5ee0e6771ba157ec62704fee4ac3b Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 15 May 2026 21:09:26 -0700 Subject: [PATCH 05/10] [Impeller] Stop manually flipping GLES readback rows `BlitCopyTextureToBufferCommandGLES::Encode` used to call `FlipImage` on the `glReadPixels` output for `TextureCoordinateSystem::kRenderToTexture` textures, compensating for GL's bottom-up FBO storage so that downstream consumers saw top-down rows. With the vertex-stage y-flip from flutter/ flutter#186554 in place, render-to-texture content is already drawn top-down at the GL FBO level, so the row order coming back from `glReadPixels` is already what the host expects. The leftover flip double-inverted every readback (`asImage().toByteData()`, `Picture. toImage`, screenshots) and was the source of the upside-down GLES goldens on this PR. Dropping the `FlipImage` call retires the last live consumer of `TextureCoordinateSystem`; the enum itself, the `Get/SetCoordinateSystem` accessors, and the now-noop `SetCoordinateSystem(kUploadFromHost)` calls in production and tests can be retired in the y-coord-scale plumbing cleanup follow-up. --- .../backend/gles/blit_command_gles.cc | 53 +++---------------- 1 file changed, 8 insertions(+), 45 deletions(-) diff --git a/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc index 56b50a7a4f3a7..a3fac9edf0f44 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc @@ -17,29 +17,6 @@ namespace impeller { -namespace { -static void FlipImage(uint8_t* buffer, - size_t width, - size_t height, - size_t stride) { - if (buffer == nullptr || stride == 0) { - return; - } - - const auto byte_width = width * stride; - - for (size_t top = 0; top < height; top++) { - size_t bottom = height - top - 1; - if (top >= bottom) { - break; - } - auto* top_row = buffer + byte_width * top; - auto* bottom_row = buffer + byte_width * bottom; - std::swap_ranges(top_row, top_row + byte_width, bottom_row); - } -} -} // namespace - BlitEncodeGLES::~BlitEncodeGLES() = default; static void DeleteFBO(const ProcTableGLES& gl, GLuint fbo, GLenum type) { @@ -288,8 +265,6 @@ bool BlitCopyTextureToBufferCommandGLES::Encode( return false; } - TextureCoordinateSystem coord_system = source->GetCoordinateSystem(); - GLuint read_fbo = GL_NONE; fml::ScopedCleanupClosure delete_fbos( [&gl, &read_fbo]() { DeleteFBO(gl, read_fbo, GL_FRAMEBUFFER); }); @@ -303,27 +278,15 @@ bool BlitCopyTextureToBufferCommandGLES::Encode( } DeviceBufferGLES::Cast(*destination) - .UpdateBufferData( - [&gl, // - this, // - format = gles_format->external_format, // - type = gles_format->type, // - coord_system, // - bytes_per_pixel = BytesPerPixelForPixelFormat(source_format) // + .UpdateBufferData([&gl, // + this, // + format = gles_format->external_format, // + type = gles_format->type // ](uint8_t* data, size_t length) { - gl.ReadPixels(source_region.GetX(), source_region.GetY(), - source_region.GetWidth(), source_region.GetHeight(), - format, type, data + destination_offset); - switch (coord_system) { - case TextureCoordinateSystem::kUploadFromHost: - break; - case TextureCoordinateSystem::kRenderToTexture: - // The texture is upside down, and must be inverted when copying - // byte data out. - FlipImage(data + destination_offset, source_region.GetWidth(), - source_region.GetHeight(), bytes_per_pixel); - } - }); + gl.ReadPixels(source_region.GetX(), source_region.GetY(), + source_region.GetWidth(), source_region.GetHeight(), + format, type, data + destination_offset); + }); return true; }; From b38870367a60271e6a3bb00eb8fb1760b6887321 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 15 May 2026 22:18:47 -0700 Subject: [PATCH 06/10] [Impeller] Update RenderPassGLES viewport tests for the unflipped offscreen-FBO y math `ViewportCachedAcrossCommands` and `CommandsWithoutViewportGetRenderPassViewport` asserted the old `target_h - y - h` viewport conversion (`Viewport(0, 50, 50, 50)` for a `SetViewport(0, 0, 50, 50)` against a 100x100 target). With the y-flip pulled into the vertex stage and the offscreen-FBO viewport math switched to the unflipped form, the expected call is now `Viewport(0, 0, 50, 50)`. The caching/fallback semantics these tests cover are unchanged; only the GL coord values flip. --- .../renderer/backend/gles/render_pass_gles_unittests.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2a0ccf087683c..32207052db31f 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 @@ -311,7 +311,7 @@ TEST_F(RenderPassGLESViewportTest, ViewportCachedAcrossCommands) { // first override. We set a catch-all to 0 to ensure no other calls occur. EXPECT_CALL(mock_gl_impl_ref, Viewport(_, _, _, _)).Times(0); EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 0, 100, 100)).Times(1); - EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 50, 50, 50)).Times(1); + EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 0, 50, 50)).Times(1); EXPECT_TRUE(render_pass->EncodeCommands()); EXPECT_TRUE(reactor->React()); @@ -344,7 +344,7 @@ TEST_F(RenderPassGLESViewportTest, EXPECT_CALL(mock_gl_impl_ref, Viewport(_, _, _, _)).Times(0); EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 0, 100, 100)).Times(2); - EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 50, 50, 50)).Times(1); + EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 0, 50, 50)).Times(1); EXPECT_TRUE(render_pass->EncodeCommands()); EXPECT_TRUE(reactor->React()); From a8b45a70a2d05a267b70d9059512f585aec4a270 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Sat, 16 May 2026 19:14:42 -0700 Subject: [PATCH 07/10] [Impeller] Update malioc baseline for the y-flip wrap-main vertex epilogue The y-flip injection from flutter/flutter#186554 adds one multiply (`gl_Position.y *= _impeller_y_flip;`) and one `uniform float` declaration to every GLES vertex shader. The Mali offline compiler reports a uniform, small cost increase across all entity GLES vertex shaders: * Mali-G78 (`Position` variant): +0.015625 cycles in longest/shortest/total_path_cycles, +1-2 uniform_registers_used. * Mali-T880 (`Main` variant): +0.33 cycles in path_cycles, +1 uniform_registers_used. Patched golden manually from the malioc_diff CI log (the regen script is Linux-only and we don't have the arm_tools CIPD package). Touches 26 (shader x core) combinations across ~14 distinct vertex shaders. --- engine/src/flutter/impeller/tools/malioc.json | 262 +++++++++--------- 1 file changed, 131 insertions(+), 131 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index 60ba99c740049..2595f829ac54e 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -1909,8 +1909,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -1928,8 +1928,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -1939,8 +1939,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -1949,7 +1949,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 22, + "uniform_registers_used": 24, "work_registers_used": 32 }, "Varying": { @@ -2302,8 +2302,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -2321,8 +2321,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -2332,8 +2332,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -2342,7 +2342,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 20, + "uniform_registers_used": 22, "work_registers_used": 32 }, "Varying": { @@ -2411,7 +2411,7 @@ "load_store" ], "longest_path_cycles": [ - 2.640000104904175, + 2.9700000286102295, 4.0, 0.0 ], @@ -2424,7 +2424,7 @@ "load_store" ], "shortest_path_cycles": [ - 2.640000104904175, + 2.9700000286102295, 4.0, 0.0 ], @@ -2432,13 +2432,13 @@ "load_store" ], "total_cycles": [ - 2.6666667461395264, + 3.0, 4.0, 0.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -2577,8 +2577,8 @@ "load_store" ], "longest_path_cycles": [ - 0.109375, - 0.109375, + 0.125, + 0.125, 0.0, 0.0, 2.0, @@ -2596,8 +2596,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.109375, - 0.109375, + 0.125, + 0.125, 0.0, 0.0, 2.0, @@ -2607,8 +2607,8 @@ "load_store" ], "total_cycles": [ - 0.109375, - 0.109375, + 0.125, + 0.125, 0.0, 0.0, 2.0, @@ -2617,7 +2617,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 18, + "uniform_registers_used": 20, "work_registers_used": 32 } } @@ -3856,8 +3856,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -3875,8 +3875,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -3886,8 +3886,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -3896,7 +3896,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 20, + "uniform_registers_used": 22, "work_registers_used": 32 }, "Varying": { @@ -3992,7 +3992,7 @@ ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -4013,8 +4013,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4032,8 +4032,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4043,8 +4043,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4170,8 +4170,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4189,8 +4189,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4200,8 +4200,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4566,8 +4566,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4585,8 +4585,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4596,8 +4596,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4606,7 +4606,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 20, + "uniform_registers_used": 22, "work_registers_used": 32 }, "Varying": { @@ -4702,7 +4702,7 @@ ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -4723,8 +4723,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4742,8 +4742,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4753,8 +4753,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -4832,7 +4832,7 @@ "load_store" ], "longest_path_cycles": [ - 3.299999952316284, + 3.630000114440918, 4.0, 0.0 ], @@ -4845,7 +4845,7 @@ "load_store" ], "shortest_path_cycles": [ - 3.299999952316284, + 3.630000114440918, 4.0, 0.0 ], @@ -4853,13 +4853,13 @@ "load_store" ], "total_cycles": [ - 3.3333332538604736, + 3.6666669845581055, 4.0, 0.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 8, + "uniform_registers_used": 9, "work_registers_used": 2 } } @@ -4995,8 +4995,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -5014,8 +5014,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -5025,8 +5025,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -5035,7 +5035,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 20, + "uniform_registers_used": 22, "work_registers_used": 32 }, "Varying": { @@ -5104,7 +5104,7 @@ "load_store" ], "longest_path_cycles": [ - 2.640000104904175, + 2.9700000286102295, 12.0, 0.0 ], @@ -5117,7 +5117,7 @@ "load_store" ], "shortest_path_cycles": [ - 2.640000104904175, + 2.9700000286102295, 12.0, 0.0 ], @@ -5125,13 +5125,13 @@ "load_store" ], "total_cycles": [ - 2.6666667461395264, + 3.0, 12.0, 0.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -5737,8 +5737,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -5756,8 +5756,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -5767,8 +5767,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6244,8 +6244,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6263,8 +6263,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6274,8 +6274,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6284,7 +6284,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 20, + "uniform_registers_used": 22, "work_registers_used": 32 }, "Varying": { @@ -6353,7 +6353,7 @@ "load_store" ], "longest_path_cycles": [ - 2.640000104904175, + 2.9700000286102295, 4.0, 0.0 ], @@ -6366,7 +6366,7 @@ "load_store" ], "shortest_path_cycles": [ - 2.640000104904175, + 2.9700000286102295, 4.0, 0.0 ], @@ -6374,13 +6374,13 @@ "load_store" ], "total_cycles": [ - 2.6666667461395264, + 3.0, 4.0, 0.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -6519,8 +6519,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6538,8 +6538,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6549,8 +6549,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6559,7 +6559,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 20, + "uniform_registers_used": 22, "work_registers_used": 32 }, "Varying": { @@ -6628,7 +6628,7 @@ "load_store" ], "longest_path_cycles": [ - 2.640000104904175, + 2.9700000286102295, 4.0, 0.0 ], @@ -6641,7 +6641,7 @@ "load_store" ], "shortest_path_cycles": [ - 2.640000104904175, + 2.9700000286102295, 4.0, 0.0 ], @@ -6649,13 +6649,13 @@ "load_store" ], "total_cycles": [ - 2.6666667461395264, + 3.0, 4.0, 0.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -6794,8 +6794,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6813,8 +6813,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6824,8 +6824,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -6834,7 +6834,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 20, + "uniform_registers_used": 22, "work_registers_used": 32 }, "Varying": { @@ -6930,7 +6930,7 @@ ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -7069,8 +7069,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -7088,8 +7088,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -7099,8 +7099,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -7109,7 +7109,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 20, + "uniform_registers_used": 22, "work_registers_used": 32 } } @@ -7154,7 +7154,7 @@ ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -8004,8 +8004,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -8023,8 +8023,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -8034,8 +8034,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -8285,8 +8285,8 @@ "load_store" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -8304,8 +8304,8 @@ "load_store" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -8315,8 +8315,8 @@ "load_store" ], "total_cycles": [ - 0.140625, - 0.140625, + 0.15625, + 0.15625, 0.0, 0.0, 2.0, @@ -12492,4 +12492,4 @@ } } } -} \ No newline at end of file +} From 0d75b013531ccd92c16b9ed85f63247cc8c1382c Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Sat, 16 May 2026 22:55:43 -0700 Subject: [PATCH 08/10] malioc --- engine/src/flutter/impeller/tools/malioc.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index 2595f829ac54e..80447cdb4484a 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -4853,14 +4853,14 @@ "load_store" ], "total_cycles": [ - 3.6666669845581055, + 3.6666667461395264, 4.0, 0.0 ] }, "thread_occupancy": 100, "uniform_registers_used": 9, - "work_registers_used": 2 + "work_registers_used": 3 } } } @@ -5874,7 +5874,7 @@ }, "thread_occupancy": 100, "uniform_registers_used": 6, - "work_registers_used": 2 + "work_registers_used": 3 } } } From 60dab1e13cd2f44bb3affafcae0de786a1e4354d Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Tue, 19 May 2026 00:04:15 -0700 Subject: [PATCH 09/10] [Impeller] Drop manual GLES y-flip from runtime effect fixture shaders The vertex-stage y-flip now stores GLES render-to-texture content top-down, so the per-shader IMPELLER_TARGET_OPENGLES flips double-flip and produce upside-down output. --- .../src/flutter/impeller/fixtures/runtime_stage_border.frag | 6 +----- .../impeller/fixtures/runtime_stage_filter_circle.frag | 6 +----- .../impeller/fixtures/runtime_stage_filter_warp.frag | 3 --- engine/src/flutter/impeller/fixtures/texture.frag | 6 +----- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/engine/src/flutter/impeller/fixtures/runtime_stage_border.frag b/engine/src/flutter/impeller/fixtures/runtime_stage_border.frag index 2d2cdc1676a1f..f1cbef4dbc696 100644 --- a/engine/src/flutter/impeller/fixtures/runtime_stage_border.frag +++ b/engine/src/flutter/impeller/fixtures/runtime_stage_border.frag @@ -13,11 +13,7 @@ void main() { vec2 fragCoord = FlutterFragCoord().xy; vec2 screenUV = vec2(fragCoord.x / uSize.x, fragCoord.y / uSize.y); - vec2 correctedScreenUV = screenUV; -#ifdef IMPELLER_TARGET_OPENGLES - correctedScreenUV.y = 1.0 - screenUV.y; -#endif - vec4 texColor = texture(uTexture, correctedScreenUV); + vec4 texColor = texture(uTexture, screenUV); // Check if we're within 20px of any edge float borderWidth = 20.0; diff --git a/engine/src/flutter/impeller/fixtures/runtime_stage_filter_circle.frag b/engine/src/flutter/impeller/fixtures/runtime_stage_filter_circle.frag index 423e77f9fcd07..ac498ab08043b 100644 --- a/engine/src/flutter/impeller/fixtures/runtime_stage_filter_circle.frag +++ b/engine/src/flutter/impeller/fixtures/runtime_stage_filter_circle.frag @@ -14,15 +14,11 @@ float radius = 30.0; void main() { vec2 uv = FlutterFragCoord().xy / u_size; - vec2 fixed_uv = uv; -#ifdef IMPELLER_TARGET_OPENGLES - fixed_uv.y = 1.0 - fixed_uv.y; -#endif vec2 norm_origin = u_origin / u_size; float norm_radius = radius / max(u_size.x, u_size.y); if (distance(uv, norm_origin) < norm_radius) { frag_color = vec4(1.0, 0.0, 0.0, 1.0); } else { - frag_color = texture(u_texture, fixed_uv); + frag_color = texture(u_texture, uv); } } diff --git a/engine/src/flutter/impeller/fixtures/runtime_stage_filter_warp.frag b/engine/src/flutter/impeller/fixtures/runtime_stage_filter_warp.frag index 7432dea8cb707..e5cc8266bedc1 100644 --- a/engine/src/flutter/impeller/fixtures/runtime_stage_filter_warp.frag +++ b/engine/src/flutter/impeller/fixtures/runtime_stage_filter_warp.frag @@ -12,8 +12,5 @@ out vec4 frag_color; void main() { vec2 uv = FlutterFragCoord().xy / u_size; uv = uv + vec2(0.0, 0.1 * sin(uv.x * 3.14 * 5.0)); -#ifdef IMPELLER_TARGET_OPENGLES - uv.y = 1.0 - uv.y; -#endif frag_color = texture(u_texture, uv); } diff --git a/engine/src/flutter/impeller/fixtures/texture.frag b/engine/src/flutter/impeller/fixtures/texture.frag index 37719781802e5..1e95b0f8f8ade 100644 --- a/engine/src/flutter/impeller/fixtures/texture.frag +++ b/engine/src/flutter/impeller/fixtures/texture.frag @@ -9,9 +9,5 @@ out vec4 frag_color; uniform sampler2D texture_contents; void main() { - vec2 tex_coords = interpolated_texture_coordinates; -#ifdef IMPELLER_TARGET_OPENGLES - tex_coords.y = 1.0 - tex_coords.y; -#endif - frag_color = texture(texture_contents, tex_coords); + frag_color = texture(texture_contents, interpolated_texture_coordinates); } From b5cf21f90aece859cd4f33812b2b75538bbde9b4 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Tue, 19 May 2026 17:59:56 -0700 Subject: [PATCH 10/10] [Impeller] Drop manual GLES y-flip from the ui FragmentShader test fixture There were two texture.frag fixtures; the previous fix only touched the impeller-side one. This is the one FragmentProgram.fromAsset loads in fragment_shader_test, which is why the fragment_shader_texture_with_quality_* OpenGLES goldens kept flipping. --- .../lib/ui/fixtures/shaders/general_shaders/texture.frag | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/texture.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/texture.frag index bd8ff339017db..af63f21f58c05 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/texture.frag +++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/texture.frag @@ -13,9 +13,5 @@ uniform sampler2D u_texture; out vec4 frag_color; void main() { - vec2 tex_coords = FlutterFragCoord().xy / u_size; -#ifdef IMPELLER_TARGET_OPENGLES - tex_coords.y = 1.0 - tex_coords.y; -#endif - frag_color = texture(u_texture, tex_coords); + frag_color = texture(u_texture, FlutterFragCoord().xy / u_size); }