Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions engine/src/flutter/impeller/compiler/compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,67 @@ constexpr const char* kEGLImageExternalExtension = "GL_OES_EGL_image_external";
constexpr const char* kEGLImageExternalExtension300 =
"GL_OES_EGL_image_external_essl3";
constexpr int kVerboseErrorLineThreshold = 6;

// Set per pass by RenderPassGLES: +1.0 swapchain, -1.0 offscreen FBO. See
// flutter/flutter#186554.
constexpr const char* kYFlipUniformName = "_impeller_y_flip";
constexpr const char* kUserMainName = "_impeller_user_main";

bool IsGLTargetPlatform(TargetPlatform platform) {
return platform == TargetPlatform::kOpenGLES ||
platform == TargetPlatform::kOpenGLDesktop ||
platform == TargetPlatform::kRuntimeStageGLES ||
platform == TargetPlatform::kRuntimeStageGLES3;
}

// 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) {
// 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(";
Comment thread
bdero marked this conversation as resolved.
const size_t main_pos = source.find(kMainPattern);
if (main_pos == std::string::npos) {
return source;
}
const std::string user_main_decl =
std::string("\nvoid ") + kUserMainName + "(";
source.replace(main_pos, kMainPattern.size(), user_main_decl);

std::string wrapper = "\nvoid main() {\n ";
wrapper += kUserMainName;
wrapper += "();\n gl_Position.y *= ";
wrapper += kYFlipUniformName;
wrapper += ";\n}\n";
source.append(wrapper);

// 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;
for (size_t pos = source.find("\nprecision "); pos != std::string::npos;
Comment thread
bdero marked this conversation as resolved.
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
Expand Down Expand Up @@ -499,6 +560,15 @@ Compiler::Compiler(const std::shared_ptr<const fml::Mapping>& 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);

Expand Down
100 changes: 100 additions & 0 deletions engine/src/flutter/impeller/compiler/compiler_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,106 @@ 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<fml::Mapping> 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<fml::UniqueFD>(
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<const char*>(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, YFlipInjectionHandlesEarlyReturnsInGLESVertexShader) {
// `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<fml::Mapping> 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<fml::UniqueFD>(
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<const char*>(sl->GetMapping()),
sl->GetSize());

EXPECT_NE(gl_vert.find("void _impeller_user_main("), std::string::npos)
<< gl_vert;
EXPECT_NE(gl_vert.find("_impeller_user_main();"), std::string::npos)
<< gl_vert;
EXPECT_NE(gl_vert.find("gl_Position.y *= _impeller_y_flip"),
std::string::npos)
<< gl_vert;

// 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)
<< gl_vert;
}

TEST(CompilerTest, ShaderKindMatchingIsSuccessful) {
ASSERT_EQ(SourceTypeFromFileName("hello.vert"), SourceType::kVertexShader);
ASSERT_EQ(SourceTypeFromFileName("hello.frag"), SourceType::kFragmentShader);
Expand Down
1 change: 1 addition & 0 deletions engine/src/flutter/impeller/fixtures/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
6 changes: 1 addition & 5 deletions engine/src/flutter/impeller/fixtures/texture.frag
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
21 changes: 21 additions & 0 deletions engine/src/flutter/impeller/fixtures/y_flip_early_return.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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 an early return; exercises the y-flip wrap-main
// path. See flutter/flutter#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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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); });
Expand All @@ -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;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ bool PipelineGLES::BuildVertexDescriptor(const ProcTableGLES& gl,
if (!vtx_desc->ReadUniformsBindings(gl, program)) {
return false;
}
// 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,18 @@ class PipelineGLES final
[[nodiscard]] bool BuildVertexDescriptor(const ProcTableGLES& gl,
GLuint program);

// 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:
friend PipelineLibraryGLES;
friend class testing::RenderPassGLESViewportTest;

std::shared_ptr<ReactorGLES> reactor_;
std::shared_ptr<UniqueHandleGLES> handle_;
std::unique_ptr<BufferBindingsGLES> buffer_bindings_;
GLint y_flip_uniform_location_ = -1;
bool is_valid_ = false;

// |Pipeline|
Expand Down
Loading
Loading