diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files index a8e366a4750..e592ad19d55 100644 --- a/engine/src/flutter/ci/licenses_golden/excluded_files +++ b/engine/src/flutter/ci/licenses_golden/excluded_files @@ -214,6 +214,7 @@ ../../../flutter/impeller/tessellator/tessellator_unittests.cc ../../../flutter/impeller/toolkit/android/README.md ../../../flutter/impeller/toolkit/android/toolkit_android_unittests.cc +../../../flutter/impeller/toolkit/glvk/README.md ../../../flutter/impeller/tools/malioc_cores.py ../../../flutter/impeller/tools/malioc_diff.py ../../../flutter/impeller/tools/metal_library.py diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index c3966f5aed8..9e9257e96dc 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -42612,6 +42612,10 @@ ORIGIN: ../../../flutter/impeller/toolkit/egl/surface.cc + ../../../flutter/LICE ORIGIN: ../../../flutter/impeller/toolkit/egl/surface.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/gles/gles.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/gles/texture.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/glvk/proc_table.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/glvk/proc_table.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/glvk/trampoline.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/glvk/trampoline.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/typographer/backends/skia/text_frame_skia.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/typographer/backends/skia/text_frame_skia.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/typographer/backends/skia/typeface_skia.cc + ../../../flutter/LICENSE @@ -43502,6 +43506,8 @@ ORIGIN: ../../../flutter/shell/platform/android/surface_texture_external_texture ORIGIN: ../../../flutter/shell/platform/android/surface_texture_external_texture_gl_impeller.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/surface_texture_external_texture_gl_skia.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/surface_texture_external_texture_gl_skia.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/vsync_waiter_android.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/vsync_waiter_android.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/accessibility_bridge.cc + ../../../flutter/LICENSE @@ -45491,6 +45497,10 @@ FILE: ../../../flutter/impeller/toolkit/egl/surface.h FILE: ../../../flutter/impeller/toolkit/gles/gles.h FILE: ../../../flutter/impeller/toolkit/gles/texture.cc FILE: ../../../flutter/impeller/toolkit/gles/texture.h +FILE: ../../../flutter/impeller/toolkit/glvk/proc_table.cc +FILE: ../../../flutter/impeller/toolkit/glvk/proc_table.h +FILE: ../../../flutter/impeller/toolkit/glvk/trampoline.cc +FILE: ../../../flutter/impeller/toolkit/glvk/trampoline.h FILE: ../../../flutter/impeller/tools/malioc.json FILE: ../../../flutter/impeller/typographer/backends/skia/text_frame_skia.cc FILE: ../../../flutter/impeller/typographer/backends/skia/text_frame_skia.h @@ -46395,6 +46405,8 @@ FILE: ../../../flutter/shell/platform/android/surface_texture_external_texture_g FILE: ../../../flutter/shell/platform/android/surface_texture_external_texture_gl_impeller.h FILE: ../../../flutter/shell/platform/android/surface_texture_external_texture_gl_skia.cc FILE: ../../../flutter/shell/platform/android/surface_texture_external_texture_gl_skia.h +FILE: ../../../flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc +FILE: ../../../flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.h FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.cc FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.h FILE: ../../../flutter/shell/platform/common/accessibility_bridge.cc diff --git a/engine/src/flutter/impeller/toolkit/egl/context.cc b/engine/src/flutter/impeller/toolkit/egl/context.cc index c2d0ecd759a..715516a8f86 100644 --- a/engine/src/flutter/impeller/toolkit/egl/context.cc +++ b/engine/src/flutter/impeller/toolkit/egl/context.cc @@ -99,5 +99,9 @@ void Context::DispatchLifecyleEvent(LifecycleEvent event) const { } } +bool Context::IsCurrent() const { + return ::eglGetCurrentContext() == context_; +} + } // namespace egl } // namespace impeller diff --git a/engine/src/flutter/impeller/toolkit/egl/context.h b/engine/src/flutter/impeller/toolkit/egl/context.h index d6854ee7d33..74bc8195d4d 100644 --- a/engine/src/flutter/impeller/toolkit/egl/context.h +++ b/engine/src/flutter/impeller/toolkit/egl/context.h @@ -99,6 +99,12 @@ class Context { /// bool RemoveLifecycleListener(UniqueID id); + //---------------------------------------------------------------------------- + /// @return True if the context is current and attached to any surface, + /// False otherwise. + /// + bool IsCurrent() const; + private: friend class Display; diff --git a/engine/src/flutter/impeller/toolkit/egl/display.cc b/engine/src/flutter/impeller/toolkit/egl/display.cc index fed9b1787d8..45d4a84e1e7 100644 --- a/engine/src/flutter/impeller/toolkit/egl/display.cc +++ b/engine/src/flutter/impeller/toolkit/egl/display.cc @@ -206,5 +206,9 @@ std::unique_ptr Display::CreatePixelBufferSurface(const Config& config, return std::unique_ptr(new Surface(display_, surface)); } +const EGLDisplay& Display::GetHandle() const { + return display_; +} + } // namespace egl } // namespace impeller diff --git a/engine/src/flutter/impeller/toolkit/egl/display.h b/engine/src/flutter/impeller/toolkit/egl/display.h index af77559a485..715de1d5663 100644 --- a/engine/src/flutter/impeller/toolkit/egl/display.h +++ b/engine/src/flutter/impeller/toolkit/egl/display.h @@ -96,6 +96,8 @@ class Display { virtual std::unique_ptr CreatePixelBufferSurface(const Config& config, size_t width, size_t height); + const EGLDisplay& GetHandle() const; + private: EGLDisplay display_ = EGL_NO_DISPLAY; diff --git a/engine/src/flutter/impeller/toolkit/glvk/BUILD.gn b/engine/src/flutter/impeller/toolkit/glvk/BUILD.gn new file mode 100644 index 00000000000..061b52bf070 --- /dev/null +++ b/engine/src/flutter/impeller/toolkit/glvk/BUILD.gn @@ -0,0 +1,23 @@ +# 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. + +import("../../tools/impeller.gni") + +impeller_component("glvk") { + sources = [ + "proc_table.cc", + "proc_table.h", + "trampoline.cc", + "trampoline.h", + ] + + public_deps = [ + "../../renderer", + "../../renderer/backend/gles", + "../../renderer/backend/vulkan", + "../../toolkit/egl", + "../../toolkit/gles", + "//flutter/fml", + ] +} diff --git a/engine/src/flutter/impeller/toolkit/glvk/README.md b/engine/src/flutter/impeller/toolkit/glvk/README.md new file mode 100644 index 00000000000..c7dce6a5f3b --- /dev/null +++ b/engine/src/flutter/impeller/toolkit/glvk/README.md @@ -0,0 +1,3 @@ +# GLVK Toolkit + +A toolkit for inter-operating between OpenGL and Vulkan. diff --git a/engine/src/flutter/impeller/toolkit/glvk/proc_table.cc b/engine/src/flutter/impeller/toolkit/glvk/proc_table.cc new file mode 100644 index 00000000000..d7d6c825208 --- /dev/null +++ b/engine/src/flutter/impeller/toolkit/glvk/proc_table.cc @@ -0,0 +1,45 @@ +// 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. + +#include "impeller/toolkit/glvk/proc_table.h" + +#include "impeller/base/validation.h" + +namespace impeller::glvk { + +ProcTable::ProcTable(const Resolver& resolver) { + if (!resolver) { + return; + } + + auto error_fn = reinterpret_cast(resolver("glGetError")); + if (!error_fn) { + VALIDATION_LOG << "Could not resolve " << "glGetError"; + return; + } + +#define GLVK_PROC(proc_ivar) \ + if (auto fn_ptr = resolver(proc_ivar.name)) { \ + proc_ivar.function = \ + reinterpret_cast(fn_ptr); \ + proc_ivar.error_fn = error_fn; \ + } else { \ + VALIDATION_LOG << "Could not resolve " << proc_ivar.name; \ + return; \ + } + + FOR_EACH_GLVK_PROC(GLVK_PROC); + +#undef GLVK_PROC + + is_valid_ = true; +} + +ProcTable::~ProcTable() = default; + +bool ProcTable::IsValid() const { + return is_valid_; +} + +} // namespace impeller::glvk diff --git a/engine/src/flutter/impeller/toolkit/glvk/proc_table.h b/engine/src/flutter/impeller/toolkit/glvk/proc_table.h new file mode 100644 index 00000000000..9a2b8035c1a --- /dev/null +++ b/engine/src/flutter/impeller/toolkit/glvk/proc_table.h @@ -0,0 +1,102 @@ +// 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. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_GLVK_PROC_TABLE_H_ +#define FLUTTER_IMPELLER_TOOLKIT_GLVK_PROC_TABLE_H_ + +#include "impeller/renderer/backend/gles/proc_table_gles.h" +#include "impeller/toolkit/gles/gles.h" + +namespace impeller::glvk { + +#define FOR_EACH_GLVK_PROC(PROC) \ + PROC(ActiveTexture) \ + PROC(AttachShader) \ + PROC(BindAttribLocation) \ + PROC(BindBuffer) \ + PROC(BindFramebuffer) \ + PROC(BindTexture) \ + PROC(BufferData) \ + PROC(CheckFramebufferStatus) \ + PROC(Clear) \ + PROC(ClearColor) \ + PROC(ColorMask) \ + PROC(CompileShader) \ + PROC(CreateProgram) \ + PROC(CreateShader) \ + PROC(DeleteBuffers) \ + PROC(DeleteFramebuffers) \ + PROC(DeleteProgram) \ + PROC(DeleteShader) \ + PROC(DeleteTextures) \ + PROC(Disable) \ + PROC(DrawArrays) \ + PROC(EGLImageTargetTexture2DOES) \ + PROC(Enable) \ + PROC(EnableVertexAttribArray) \ + PROC(Flush) \ + PROC(FramebufferTexture2D) \ + PROC(GenBuffers) \ + PROC(GenFramebuffers) \ + PROC(GenTextures) \ + PROC(GetProgramiv) \ + PROC(GetShaderiv) \ + PROC(GetUniformLocation) \ + PROC(LinkProgram) \ + PROC(ShaderSource) \ + PROC(TexParameteri) \ + PROC(Uniform1i) \ + PROC(UniformMatrix4fv) \ + PROC(UseProgram) \ + PROC(VertexAttribPointer) \ + PROC(Viewport) + +//------------------------------------------------------------------------------ +/// @brief A proc. table consisting of methods that are useful when +/// interoperating between OpenGL and Vulkan. This is different from +/// the OpenGL proc. table since it may contain more interop +/// extension related methods. +/// +class ProcTable { + public: + using Resolver = std::function; + + //---------------------------------------------------------------------------- + /// @brief Create a proc table using a resolver to resolve OpenGL + /// methods. + /// + /// @param[in] resolver The resolver + /// + explicit ProcTable(const Resolver& resolver); + + ~ProcTable(); + + ProcTable(const ProcTable&) = delete; + + ProcTable& operator=(const ProcTable&) = delete; + + //---------------------------------------------------------------------------- + /// @brief Determines if a proc. table is suitable for interop purposes. + /// The absence of optional extension methods that have fallbacks + /// don't result in an invalid proc. table. But an invalid proc + /// table must always be discarded as there can be no error + /// recovery. + /// + /// @return True if valid, False otherwise. + /// + bool IsValid() const; + +#define GLVK_PROC(name) GLProc name = {"gl" #name, nullptr}; + + FOR_EACH_GLVK_PROC(GLVK_PROC); + +#undef GLVK_PROC + + private: + bool is_valid_ = false; +}; + +} // namespace impeller::glvk + +#endif // FLUTTER_IMPELLER_TOOLKIT_GLVK_PROC_TABLE_H_ diff --git a/engine/src/flutter/impeller/toolkit/glvk/trampoline.cc b/engine/src/flutter/impeller/toolkit/glvk/trampoline.cc new file mode 100644 index 00000000000..83ac20a14bb --- /dev/null +++ b/engine/src/flutter/impeller/toolkit/glvk/trampoline.cc @@ -0,0 +1,332 @@ +// 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. + +#include "impeller/toolkit/glvk/trampoline.h" + +#include + +#include "flutter/fml/closure.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/timing.h" +#include "impeller/base/validation.h" +#include "impeller/geometry/point.h" +#include "impeller/renderer/backend/gles/formats_gles.h" +#include "impeller/toolkit/android/proc_table.h" +#include "impeller/toolkit/egl/image.h" + +namespace impeller::glvk { + +static GLuint kAttributeIndexPosition = 0u; +static GLuint kAttributeIndexTexCoord = 1u; + +static constexpr const char* kVertShader = R"IMPELLER_SHADER(#version 100 + +precision mediump float; + +attribute vec2 aPosition; +attribute vec2 aTexCoord; + +varying vec2 vTexCoord; + +void main() { + gl_Position = vec4(aPosition, 0.0, 1.0); + vTexCoord = aTexCoord; + +} +)IMPELLER_SHADER"; + +static constexpr const char* kFragShader = R"IMPELLER_SHADER(#version 100 + +#extension GL_OES_EGL_image_external : require + +precision mediump float; + +uniform samplerExternalOES uTexture; +uniform mat4 uUVTransformation; + +varying vec2 vTexCoord; + +void main() { + vec2 texture_coords = (uUVTransformation * vec4(vTexCoord, 0, 1)).xy; + gl_FragColor = texture2D(uTexture, texture_coords); +} + +)IMPELLER_SHADER"; + +Trampoline::Trampoline() { + auto egl_display = std::make_unique(); + if (!egl_display->IsValid()) { + VALIDATION_LOG + << "Could not create EGL display for external texture interop."; + return; + } + + egl::ConfigDescriptor egl_config_desc; + egl_config_desc.api = egl::API::kOpenGLES2; + egl_config_desc.samples = egl::Samples::kOne; + egl_config_desc.color_format = egl::ColorFormat::kRGBA8888; + egl_config_desc.stencil_bits = egl::StencilBits::kZero; + egl_config_desc.depth_bits = egl::DepthBits::kZero; + egl_config_desc.surface_type = egl::SurfaceType::kPBuffer; + auto egl_config = egl_display->ChooseConfig(egl_config_desc); + if (!egl_config) { + VALIDATION_LOG + << "Could not choose EGL config for external texture interop."; + return; + } + + auto egl_surface = egl_display->CreatePixelBufferSurface(*egl_config, 1u, 1u); + auto egl_context = egl_display->CreateContext(*egl_config, nullptr); + + if (!egl_surface || !egl_context) { + VALIDATION_LOG << "Could not create EGL surface and/or context for " + "external texture interop."; + return; + } + + // Make the context current so the proc addresses can be resolved. + if (!egl_context->MakeCurrent(*egl_surface)) { + VALIDATION_LOG << "Could not make the context current."; + return; + } + + auto gl = std::make_unique(egl::CreateProcAddressResolver()); + + if (!gl->IsValid()) { + egl_context->ClearCurrent(); + VALIDATION_LOG << "Could not setup trampoline proc table."; + return; + } + + // Generate program object. + auto vert_shader = gl->CreateShader(GL_VERTEX_SHADER); + auto frag_shader = gl->CreateShader(GL_FRAGMENT_SHADER); + + GLint vert_shader_size = strlen(kVertShader); + GLint frag_shader_size = strlen(kFragShader); + + gl->ShaderSource(vert_shader, 1u, &kVertShader, &vert_shader_size); + gl->ShaderSource(frag_shader, 1u, &kFragShader, &frag_shader_size); + + gl->CompileShader(vert_shader); + gl->CompileShader(frag_shader); + + GLint vert_status = GL_FALSE; + GLint frag_status = GL_FALSE; + + gl->GetShaderiv(vert_shader, GL_COMPILE_STATUS, &vert_status); + gl->GetShaderiv(frag_shader, GL_COMPILE_STATUS, &frag_status); + + FML_CHECK(vert_status == GL_TRUE); + FML_CHECK(frag_status == GL_TRUE); + + program_ = gl->CreateProgram(); + gl->AttachShader(program_, vert_shader); + gl->AttachShader(program_, frag_shader); + + gl->BindAttribLocation(program_, kAttributeIndexPosition, "aPosition"); + gl->BindAttribLocation(program_, kAttributeIndexTexCoord, "aTexCoord"); + + gl->LinkProgram(program_); + + GLint link_status = GL_FALSE; + gl->GetProgramiv(program_, GL_LINK_STATUS, &link_status); + FML_CHECK(link_status == GL_TRUE); + + texture_uniform_location_ = gl->GetUniformLocation(program_, "uTexture"); + uv_transformation_location_ = + gl->GetUniformLocation(program_, "uUVTransformation"); + + gl->DeleteShader(vert_shader); + gl->DeleteShader(frag_shader); + + egl_context->ClearCurrent(); + + gl_ = std::move(gl); + egl_display_ = std::move(egl_display); + egl_context_ = std::move(egl_context); + egl_surface_ = std::move(egl_surface); + is_valid_ = true; +} + +Trampoline::~Trampoline() { + if (!is_valid_) { + return; + } + auto context = MakeCurrentContext(); + gl_->DeleteProgram(program_); +} + +bool Trampoline::IsValid() const { + return is_valid_; +} + +static UniqueEGLImageKHR CreateEGLImageFromAHBTexture( + const EGLDisplay& display, + const AHBTextureSourceVK& to_texture) { + if (!android::GetProcTable().eglGetNativeClientBufferANDROID.IsAvailable()) { + VALIDATION_LOG << "Could not get native client buffer."; + return {}; + } + + EGLClientBuffer client_buffer = + android::GetProcTable().eglGetNativeClientBufferANDROID( + to_texture.GetBackingStore()->GetHandle()); + + if (!client_buffer) { + VALIDATION_LOG + << "Could not get client buffer from Android hardware buffer."; + return {}; + } + + auto image = ::eglCreateImageKHR(display, // + EGL_NO_CONTEXT, // + EGL_NATIVE_BUFFER_ANDROID, // + client_buffer, // + nullptr // + ); + if (image == NULL) { + VALIDATION_LOG << "Could not create EGL Image."; + return {}; + } + + return UniqueEGLImageKHR(EGLImageKHRWithDisplay{image, display}); +} + +bool Trampoline::BlitTextureOpenGLToVulkan( + const GLTextureInfo& src_texture, + const AHBTextureSourceVK& dst_texture) const { + TRACE_EVENT0("impeller", __FUNCTION__); + if (!is_valid_) { + return false; + } + + FML_DCHECK(egl_context_->IsCurrent()); + + auto dst_egl_image = + CreateEGLImageFromAHBTexture(egl_display_->GetHandle(), dst_texture); + if (!dst_egl_image.is_valid()) { + VALIDATION_LOG << "Could not create EGL image from AHB texture."; + return false; + } + + const auto& gl = *gl_; + + GLuint dst_gl_texture = GL_NONE; + gl.GenTextures(1u, &dst_gl_texture); + gl.BindTexture(GL_TEXTURE_2D, dst_gl_texture); + gl.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, dst_egl_image.get().image); + + GLuint offscreen_fbo = GL_NONE; + gl.GenFramebuffers(1u, &offscreen_fbo); + gl.BindFramebuffer(GL_FRAMEBUFFER, offscreen_fbo); + gl.FramebufferTexture2D(GL_FRAMEBUFFER, // + GL_COLOR_ATTACHMENT0, // + GL_TEXTURE_2D, // + dst_gl_texture, // + 0 // + ); + + FML_CHECK(gl.CheckFramebufferStatus(GL_FRAMEBUFFER) == + GL_FRAMEBUFFER_COMPLETE); + + gl.Disable(GL_BLEND); + gl.Disable(GL_SCISSOR_TEST); + gl.Disable(GL_DITHER); + gl.Disable(GL_CULL_FACE); + gl.ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + gl.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl.Clear(GL_COLOR_BUFFER_BIT); + + const auto& fb_size = dst_texture.GetTextureDescriptor().size; + gl.Viewport(0, 0, fb_size.width, fb_size.height); + + gl.UseProgram(program_); + + struct VertexData { + Point position; + Point tex_coord; + }; + + // The vertex coordinates assume OpenGL NDC because that's the API we are + // using to draw the quad. But the texture will be sampled in Vulkan so the + // texture coordinate system assumes Vulkan convention. + // + // See the following help link for an overview of the different coordinate + // systems: + // https://github.com/flutter/engine/blob/5810b3fc791f4bb82b9a454014310990eddc1181/impeller/docs/coordinate_system.md + static constexpr const VertexData kVertData[] = { + {{-1, -1}, {0, 1}}, // bottom left + {{-1, +1}, {0, 0}}, // top left + {{+1, +1}, {1, 0}}, // top right + {{+1, -1}, {1, 1}}, // bottom right + }; + + // This is tedious but we assume no vertex array objects (VAO) are available + // because of ES 2 versioning constraints. + GLuint vertex_buffer = GL_NONE; + gl.GenBuffers(1u, &vertex_buffer); + gl.BindBuffer(GL_ARRAY_BUFFER, vertex_buffer); + gl.BufferData(GL_ARRAY_BUFFER, sizeof(kVertData), kVertData, GL_STATIC_DRAW); + gl.EnableVertexAttribArray(kAttributeIndexPosition); + gl.EnableVertexAttribArray(kAttributeIndexTexCoord); + gl.VertexAttribPointer(kAttributeIndexPosition, 2, GL_FLOAT, GL_FALSE, + sizeof(VertexData), + (void*)offsetof(VertexData, position)); + gl.VertexAttribPointer(kAttributeIndexTexCoord, 2, GL_FLOAT, GL_FALSE, + sizeof(VertexData), + (void*)offsetof(VertexData, tex_coord)); + + gl.ActiveTexture(GL_TEXTURE0); + gl.BindTexture(src_texture.target, src_texture.texture); + gl.TexParameteri(src_texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.TexParameteri(src_texture.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl.TexParameteri(src_texture.target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl.TexParameteri(src_texture.target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl.Uniform1i(texture_uniform_location_, 0u); + + auto gl_uv_transformation = src_texture.uv_transformation; + + gl.UniformMatrix4fv(uv_transformation_location_, 1u, GL_FALSE, + reinterpret_cast(&gl_uv_transformation)); + + gl.DrawArrays(GL_TRIANGLE_FAN, 0, 4); + + gl.UseProgram(GL_NONE); + + gl.Flush(); + + gl.DeleteFramebuffers(1u, &offscreen_fbo); + gl.DeleteTextures(1u, &dst_gl_texture); + gl.DeleteBuffers(1u, &vertex_buffer); + + // Theoretically, this does nothing because the surface is a 1x1 pbuffer + // surface. But frame capture tools use this to denote a frame boundary in + // OpenGL. So add this as a debugging aid anyway. + eglSwapBuffers(egl_display_->GetHandle(), egl_surface_->GetHandle()); + + return true; +} + +AutoTrampolineContext Trampoline::MakeCurrentContext() const { + FML_DCHECK(is_valid_); + return AutoTrampolineContext{*this}; +} + +AutoTrampolineContext::AutoTrampolineContext(const Trampoline& trampoline) + : context_(trampoline.egl_context_.get()), + surface_(trampoline.egl_surface_.get()) { + if (!context_->IsCurrent() && !context_->MakeCurrent(*surface_)) { + VALIDATION_LOG << "Could not make context current."; + } +}; + +AutoTrampolineContext::~AutoTrampolineContext() { + if (!context_->ClearCurrent()) { + VALIDATION_LOG << "Could not clear current context."; + } +} + +} // namespace impeller::glvk diff --git a/engine/src/flutter/impeller/toolkit/glvk/trampoline.h b/engine/src/flutter/impeller/toolkit/glvk/trampoline.h new file mode 100644 index 00000000000..4bb078807bc --- /dev/null +++ b/engine/src/flutter/impeller/toolkit/glvk/trampoline.h @@ -0,0 +1,153 @@ +// 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. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_GLVK_TRAMPOLINE_H_ +#define FLUTTER_IMPELLER_TOOLKIT_GLVK_TRAMPOLINE_H_ + +#include "impeller/base/thread_safety.h" +#include "impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h" +#include "impeller/toolkit/egl/context.h" +#include "impeller/toolkit/egl/display.h" +#include "impeller/toolkit/egl/surface.h" +#include "impeller/toolkit/glvk/proc_table.h" + +namespace impeller::glvk { + +class AutoTrampolineContext; + +//------------------------------------------------------------------------------ +/// @brief An object used to interoperate between OpenGL and Vulkan. +/// +/// While these are not super expensive to create, they do manage an +/// internal EGL context as well as some OpenGL state. For this +/// reason, it is recommended that callers cache these for the +/// duration of the lifecycle of main rendering context. +/// +class Trampoline { + public: + //---------------------------------------------------------------------------- + /// @brief Constructs a new trampoline. It is recommended that these + /// objects be cached and reused for all conversion operations. + /// + /// EGL contexts on already bound to the callers thread may become + /// unbound after a call to this method. + /// + Trampoline(); + + //---------------------------------------------------------------------------- + /// @brief Destroys the trampoline. There are no threading restrictions. + /// EGL contexts on already bound to the callers thread may become + /// unbound after a call to this method. + /// + ~Trampoline(); + + Trampoline(const Trampoline&) = delete; + + Trampoline& operator=(const Trampoline&) = delete; + + //---------------------------------------------------------------------------- + /// @brief Determines if this is a valid trampoline. There is no error + /// recovery mechanism if a trampoline cannot be constructed and + /// an invalid trampoline must be immediately discarded. + /// + /// @return True if valid, False otherwise. + /// + bool IsValid() const; + + //---------------------------------------------------------------------------- + /// @brief Describes an OpenGL texture along with information on how to + /// sample from it. + /// + struct GLTextureInfo { + //-------------------------------------------------------------------------- + /// The OpenGL texture handle. + /// + GLuint texture = 0; + //-------------------------------------------------------------------------- + /// The OpenGL texture target enum. For instance, GL_TEXTURE_2D or + /// GL_TEXTURE_EXTERNAL_OES. + /// + GLenum target = 0; + //-------------------------------------------------------------------------- + /// A transformation applied to the texture coordinates in the form of (u, + /// v, 0, 1) when sampling from the texture. + /// + Matrix uv_transformation; + }; + + //---------------------------------------------------------------------------- + /// @brief Perform a blit operation from the source OpenGL texture to a + /// target Vulkan texture. + /// + /// It is the callers responsibility to ensure that the EGL + /// context associated with the trampoline is already current + /// before making this call. + /// + /// It is also the responsibility of the caller to ensure that the + /// destination texture is the color-attachment-optimal layout. + /// Failure to ensure this will lead to validation error. + /// + /// @see `MakeCurrentContext` + /// + /// @param[in] src_texture The source OpenGL texture. + /// @param[in] dst_texture The destination Vulkan texture. + /// + /// @return True if the blit was successful, False otherwise. + /// + bool BlitTextureOpenGLToVulkan(const GLTextureInfo& src_texture, + const AHBTextureSourceVK& dst_texture) const; + + //---------------------------------------------------------------------------- + /// @brief Make the EGL context associated with this trampoline current + /// on the calling thread. + /// + /// @return The automatic trampoline context. The collection of this + /// context clears the threads EGL binding. + /// + [[nodiscard]] AutoTrampolineContext MakeCurrentContext() const; + + private: + friend class AutoTrampolineContext; + + std::unique_ptr egl_display_; + std::unique_ptr egl_context_; + std::unique_ptr egl_surface_; + std::unique_ptr gl_; + GLuint program_ = GL_NONE; + GLint texture_uniform_location_ = 0; + GLint uv_transformation_location_ = 0; + bool is_valid_ = false; +}; + +//------------------------------------------------------------------------------ +/// @brief An RAII object that makes the trampolines EGL context current +/// when constructed and clears the EGL binding on destruction. +/// +class AutoTrampolineContext final { + public: + //---------------------------------------------------------------------------- + /// @brief Constructs a new instance and makes the trampolines EGL + /// context current on the calling thread. + /// + /// @param[in] trampoline The trampoline. + /// + explicit AutoTrampolineContext(const Trampoline& trampoline); + + //---------------------------------------------------------------------------- + /// @brief Destroys the object and clears the previous EGL binding. + /// + ~AutoTrampolineContext(); + + AutoTrampolineContext(const AutoTrampolineContext&) = delete; + + AutoTrampolineContext& operator=(const AutoTrampolineContext&) = delete; + + private: + const egl::Context* context_ = nullptr; + const egl::Surface* surface_ = nullptr; +}; + +} // namespace impeller::glvk + +#endif // FLUTTER_IMPELLER_TOOLKIT_GLVK_TRAMPOLINE_H_ diff --git a/engine/src/flutter/shell/platform/android/BUILD.gn b/engine/src/flutter/shell/platform/android/BUILD.gn index d00643c3673..433af900f8c 100644 --- a/engine/src/flutter/shell/platform/android/BUILD.gn +++ b/engine/src/flutter/shell/platform/android/BUILD.gn @@ -133,6 +133,8 @@ source_set("flutter_shell_native_src") { "surface_texture_external_texture_gl_impeller.h", "surface_texture_external_texture_gl_skia.cc", "surface_texture_external_texture_gl_skia.h", + "surface_texture_external_texture_vk_impeller.cc", + "surface_texture_external_texture_vk_impeller.h", "vsync_waiter_android.cc", "vsync_waiter_android.h", ] @@ -152,6 +154,7 @@ source_set("flutter_shell_native_src") { "//flutter/impeller/toolkit/android", "//flutter/impeller/toolkit/egl", "//flutter/impeller/toolkit/gles", + "//flutter/impeller/toolkit/glvk", "//flutter/lib/ui", "//flutter/runtime", "//flutter/runtime:libdart", diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.cc b/engine/src/flutter/shell/platform/android/platform_view_android.cc index 704f69978c4..926c99928b6 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android.cc @@ -22,6 +22,7 @@ #include "flutter/shell/platform/android/image_external_texture_gl_skia.h" #include "flutter/shell/platform/android/surface_texture_external_texture_gl_impeller.h" #include "flutter/shell/platform/android/surface_texture_external_texture_gl_skia.h" +#include "flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.h" #include "fml/logging.h" #if IMPELLER_ENABLE_VULKAN // b/258506856 for why this is behind an if #include "flutter/shell/platform/android/android_surface_vk_impeller.h" @@ -279,23 +280,36 @@ void PlatformViewAndroid::RegisterExternalTexture( // Impeller GLES. RegisterTexture(std::make_shared( std::static_pointer_cast( - android_context_->GetImpellerContext()), - texture_id, surface_texture, jni_facade_)); + android_context_->GetImpellerContext()), // + texture_id, // + surface_texture, // + jni_facade_ // + )); break; case AndroidRenderingAPI::kSkiaOpenGLES: // Legacy GL. RegisterTexture(std::make_shared( - texture_id, surface_texture, jni_facade_)); + texture_id, // + surface_texture, // + jni_facade_ // + )); break; case AndroidRenderingAPI::kSoftware: FML_LOG(INFO) << "Software rendering does not support external textures."; break; case AndroidRenderingAPI::kImpellerVulkan: - FML_LOG(ERROR) << "Impeller requires migrating plugins that create and " - "register surface textures to the new surface producer " - "API. See " - "https://docs.flutter.dev/release/breaking-changes/" - "android-surface-plugins"; + FML_LOG(IMPORTANT) + << "Flutter recommends migrating plugins that create and " + "register surface textures to the new surface producer " + "API. See https://docs.flutter.dev/release/breaking-changes/" + "android-surface-plugins"; + RegisterTexture(std::make_shared( + std::static_pointer_cast( + android_context_->GetImpellerContext()), // + texture_id, // + surface_texture, // + jni_facade_ // + )); } } diff --git a/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc b/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc index 56dece61d64..64fea9837bb 100644 --- a/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc +++ b/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc @@ -54,32 +54,57 @@ void SurfaceTextureExternalTexture::Paint(PaintContext& context, } FML_CHECK(state_ == AttachmentState::kAttached); - if (dl_image_) { - DlAutoCanvasRestore autoRestore(context.canvas, true); - - // The incoming texture is vertically flipped, so we flip it - // back. OpenGL's coordinate system has Positive Y equivalent to up, while - // Skia's coordinate system has Negative Y equvalent to up. - context.canvas->Translate(bounds.x(), bounds.y() + bounds.height()); - context.canvas->Scale(bounds.width(), -bounds.height()); - - if (!transform_.isIdentity()) { - DlImageColorSource source(dl_image_, DlTileMode::kClamp, - DlTileMode::kClamp, sampling, &transform_); - - DlPaint paintWithShader; - if (context.paint) { - paintWithShader = *context.paint; - } - paintWithShader.setColorSource(&source); - context.canvas->DrawRect(SkRect::MakeWH(1, 1), paintWithShader); - } else { - context.canvas->DrawImage(dl_image_, {0, 0}, sampling, context.paint); - } - } else { + if (!dl_image_) { FML_LOG(WARNING) << "No DlImage available for SurfaceTextureExternalTexture to paint."; + return; } + + DrawFrame(context, bounds, sampling); +} + +void SurfaceTextureExternalTexture::DrawFrame( + PaintContext& context, + const SkRect& bounds, + const DlImageSampling sampling) const { + auto transform = GetCurrentUVTransformation(); + + // Android's SurfaceTexture transform matrix works on texture coordinate + // lookups in the range 0.0-1.0, while Skia's Shader transform matrix works on + // the image itself, as if it were inscribed inside a clip rect. + // An Android transform that scales lookup by 0.5 (displaying 50% of the + // texture) is the same as a Skia transform by 2.0 (scaling 50% of the image + // outside of the virtual "clip rect"), so we invert the incoming matrix. + + SkMatrix inverted; + if (!transform.invert(&inverted)) { + FML_LOG(FATAL) + << "Invalid (not invertable) SurfaceTexture transformation matrix"; + } + transform = inverted; + + if (transform.isIdentity()) { + context.canvas->DrawImage(dl_image_, {0, 0}, sampling, context.paint); + return; + } + + DlAutoCanvasRestore autoRestore(context.canvas, true); + + // The incoming texture is vertically flipped, so we flip it + // back. OpenGL's coordinate system has Positive Y equivalent to up, while + // Skia's coordinate system has Negative Y equvalent to up. + context.canvas->Translate(bounds.x(), bounds.y() + bounds.height()); + context.canvas->Scale(bounds.width(), -bounds.height()); + + DlImageColorSource source(dl_image_, DlTileMode::kClamp, DlTileMode::kClamp, + sampling, &transform); + + DlPaint paintWithShader; + if (context.paint) { + paintWithShader = *context.paint; + } + paintWithShader.setColorSource(&source); + context.canvas->DrawRect(SkRect::MakeWH(1, 1), paintWithShader); } void SurfaceTextureExternalTexture::OnGrContextDestroyed() { @@ -111,22 +136,13 @@ bool SurfaceTextureExternalTexture::ShouldUpdate() { void SurfaceTextureExternalTexture::Update() { jni_facade_->SurfaceTextureUpdateTexImage( fml::jni::ScopedJavaLocalRef(surface_texture_)); - jni_facade_->SurfaceTextureGetTransformMatrix( fml::jni::ScopedJavaLocalRef(surface_texture_), transform_); +} - // Android's SurfaceTexture transform matrix works on texture coordinate - // lookups in the range 0.0-1.0, while Skia's Shader transform matrix works on - // the image itself, as if it were inscribed inside a clip rect. - // An Android transform that scales lookup by 0.5 (displaying 50% of the - // texture) is the same as a Skia transform by 2.0 (scaling 50% of the image - // outside of the virtual "clip rect"), so we invert the incoming matrix. - SkMatrix inverted; - if (!transform_.invert(&inverted)) { - FML_LOG(FATAL) - << "Invalid (not invertable) SurfaceTexture transformation matrix"; - } - transform_ = inverted; +const SkMatrix& SurfaceTextureExternalTexture::GetCurrentUVTransformation() + const { + return transform_; } } // namespace flutter diff --git a/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h b/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h index 289d13a054d..e4dec1062ac 100644 --- a/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h +++ b/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h @@ -54,14 +54,42 @@ class SurfaceTextureExternalTexture : public flutter::Texture { /// virtual void ProcessFrame(PaintContext& context, const SkRect& bounds) = 0; + virtual void DrawFrame(PaintContext& context, + const SkRect& bounds, + const DlImageSampling sampling) const; + + //---------------------------------------------------------------------------- + /// @brief Get the transformation that should be applied to the UV + /// texture coordinates when sampling from this texture. + /// + /// @return The current uv transformation. + /// + const SkMatrix& GetCurrentUVTransformation() const; + //---------------------------------------------------------------------------- /// @brief Provides an opportunity for the subclasses to sever the /// connection between the OpenGL texture resource represented by /// this surface texture and the underlying package handle /// (SkImage, impeller::Texture, etc...). /// + /// @important It is the responsibility of the subclass to ensure that a + /// context is current when this call is made. Subclass can do + /// this by overriding this method, making the context current in + /// the implementation and calling the base class method. + /// virtual void Detach(); + //---------------------------------------------------------------------------- + /// @brief Attaches the given OpenGL texture handle to the surface + /// texture via a bind operation. + /// + /// @important It is the responsibility of the subclass to ensure that a + /// context is current when this call is made. Subclass can do + /// this by overriding this method, making the context current in + /// the implementation and calling the base class method. + /// + /// @param[in] gl_tex_id The gl tex identifier + /// void Attach(int gl_tex_id); bool ShouldUpdate(); @@ -80,10 +108,11 @@ class SurfaceTextureExternalTexture : public flutter::Texture { std::shared_ptr jni_facade_; fml::jni::ScopedJavaGlobalRef surface_texture_; AttachmentState state_ = AttachmentState::kUninitialized; - SkMatrix transform_; sk_sp dl_image_; private: + SkMatrix transform_; + // |Texture| void Paint(PaintContext& context, const SkRect& bounds, diff --git a/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc b/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc new file mode 100644 index 00000000000..bd2bc7b07cd --- /dev/null +++ b/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc @@ -0,0 +1,231 @@ +// 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. + +#include "flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.h" + +#include + +#include +#define GL_GLEXT_PROTOTYPES +#include + +#include "flutter/fml/trace_event.h" +#include "flutter/impeller/display_list/dl_image_impeller.h" +#include "flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h" +#include "flutter/impeller/renderer/backend/vulkan/command_encoder_vk.h" +#include "flutter/impeller/renderer/backend/vulkan/surface_context_vk.h" +#include "flutter/impeller/renderer/backend/vulkan/texture_vk.h" +#include "flutter/impeller/toolkit/android/hardware_buffer.h" + +namespace flutter { + +using namespace impeller; + +SurfaceTextureExternalTextureVKImpeller:: + SurfaceTextureExternalTextureVKImpeller( + std::shared_ptr context, + int64_t id, + const fml::jni::ScopedJavaGlobalRef& surface_texture, + const std::shared_ptr& jni_facade) + : SurfaceTextureExternalTexture(id, surface_texture, jni_facade), + context_(std::move(context)), + trampoline_(std::make_shared()) { + is_valid_ = trampoline_->IsValid(); +} + +SurfaceTextureExternalTextureVKImpeller:: + ~SurfaceTextureExternalTextureVKImpeller() = default; + +enum class LayoutUpdateMode { + kSync, + kAsync, +}; + +static bool SetTextureLayout(const ContextVK& context, + const TextureSourceVK* texture, + vk::ImageLayout layout, + LayoutUpdateMode mode) { + TRACE_EVENT0("flutter", __FUNCTION__); + if (!texture) { + return true; + } + auto command_buffer = context.CreateCommandBuffer(); + if (!command_buffer) { + VALIDATION_LOG + << "Could not create command buffer for texture layout update."; + return false; + } + command_buffer->SetLabel("GLVKTextureLayoutUpdateCB"); + const auto& encoder = CommandBufferVK::Cast(*command_buffer).GetEncoder(); + const auto command_buffer_vk = encoder->GetCommandBuffer(); + + BarrierVK barrier; + barrier.cmd_buffer = command_buffer_vk; + barrier.new_layout = layout; + barrier.src_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput | + impeller::vk::PipelineStageFlagBits::eFragmentShader; + barrier.src_access = vk::AccessFlagBits::eColorAttachmentWrite | + vk::AccessFlagBits::eShaderRead; + barrier.dst_stage = impeller::vk::PipelineStageFlagBits::eFragmentShader; + barrier.dst_access = vk::AccessFlagBits::eShaderRead; + + if (!texture->SetLayout(barrier).ok()) { + VALIDATION_LOG << "Could not encoder layout transition."; + return false; + } + + encoder->EndCommandBuffer(); + + vk::SubmitInfo submit_info; + submit_info.setCommandBuffers(command_buffer_vk); + + // There is no need to track the fence in the encoder since we are going to do + // a sync wait for completion. + vk::UniqueFence fence; + + if (mode == LayoutUpdateMode::kSync) { + auto fence_pair = + context.GetDevice().createFenceUnique(vk::FenceCreateFlags{}); + if (fence_pair.result != impeller::vk::Result::eSuccess) { + VALIDATION_LOG << "Could not create fence."; + return false; + } + fence = std::move(fence_pair.value); + } + + if (context.GetGraphicsQueue()->Submit(submit_info, fence.get()) != + impeller::vk::Result::eSuccess) { + VALIDATION_LOG << "Could not submit layout transition fence."; + return false; + } + + using namespace std::chrono_literals; + + if (fence && + context.GetDevice().waitForFences( + fence.get(), // + VK_TRUE, // + std::chrono::duration_cast(1s).count() // + ) != impeller::vk::Result::eSuccess) { + VALIDATION_LOG << "Could not perform sync wait on fence."; + return false; + } + + return true; +} + +// |SurfaceTextureExternalTexture| +void SurfaceTextureExternalTextureVKImpeller::ProcessFrame( + PaintContext& context, + const SkRect& bounds) { + if (!is_valid_ || !context.aiks_context) { + VALIDATION_LOG << "Invalid external texture."; + return; + } + + const auto& surface_context = + SurfaceContextVK::Cast(*context.aiks_context->GetContext()); + const auto& context_vk = ContextVK::Cast(*surface_context.GetParent()); + + auto dst_texture = + GetCachedTextureSource(surface_context.GetParent(), // + ISize::MakeWH(bounds.width(), bounds.height()) // + ); + if (!dst_texture || !dst_texture->IsValid()) { + VALIDATION_LOG << "Could not fetch trampoline texture target."; + return; + } + + auto current_context = trampoline_->MakeCurrentContext(); + + GLuint src_gl_texture = {}; + glGenTextures(1u, &src_gl_texture); + Attach(src_gl_texture); + Update(); + + SetTextureLayout(context_vk, dst_texture.get(), + vk::ImageLayout::eColorAttachmentOptimal, + LayoutUpdateMode::kSync); + + SkM44 transformation(GetCurrentUVTransformation()); + impeller::Matrix uv_transformation; + transformation.getColMajor(reinterpret_cast(&uv_transformation)); + + glvk::Trampoline::GLTextureInfo src_texture; + src_texture.texture = src_gl_texture; + src_texture.target = GL_TEXTURE_EXTERNAL_OES; + src_texture.uv_transformation = uv_transformation; + + if (!trampoline_->BlitTextureOpenGLToVulkan(src_texture, *dst_texture)) { + VALIDATION_LOG << "Texture copy failed."; + } + + SetTextureLayout(context_vk, dst_texture.get(), + vk::ImageLayout::eShaderReadOnlyOptimal, + LayoutUpdateMode::kAsync); + + glDeleteTextures(1u, &src_gl_texture); + + dl_image_ = DlImageImpeller::Make( + std::make_shared(surface_context.GetParent(), dst_texture)); +} + +// |SurfaceTextureExternalTexture| +void SurfaceTextureExternalTextureVKImpeller::Detach() { + // Detaching from the underlying surface texture requires a context to be + // current. On the other hand, the texture source is a pure Vulkan construct + // and has no EGL related requirements. + auto context = trampoline_->MakeCurrentContext(); + SurfaceTextureExternalTexture::Detach(); + cached_texture_source_.reset(); +} + +std::shared_ptr +SurfaceTextureExternalTextureVKImpeller::GetCachedTextureSource( + const std::shared_ptr& context, + const impeller::ISize& size) { + if (cached_texture_source_ && + cached_texture_source_->GetTextureDescriptor().size == size) { + return cached_texture_source_; + } + cached_texture_source_ = nullptr; + + android::HardwareBufferDescriptor ahb_descriptor; + ahb_descriptor.format = android::HardwareBufferFormat::kR8G8B8A8UNormInt; + ahb_descriptor.size = size.Max(ISize{1u, 1u}); + ahb_descriptor.usage = + android::HardwareBufferUsageFlags::kFrameBufferAttachment | + android::HardwareBufferUsageFlags::kSampledImage; + + if (!ahb_descriptor.IsAllocatable()) { + VALIDATION_LOG << "Invalid hardware buffer texture descriptor."; + return nullptr; + } + + auto ahb = std::make_unique(ahb_descriptor); + if (!ahb->IsValid()) { + VALIDATION_LOG << "Could not allocate hardware buffer."; + return nullptr; + } + + auto texture_source = + std::make_shared(context, std::move(ahb), false); + + if (!texture_source->IsValid()) { + VALIDATION_LOG << "Could not create trampoline texture source."; + return nullptr; + } + cached_texture_source_ = std::move(texture_source); + return cached_texture_source_; +} + +// |SurfaceTextureExternalTexture| +void SurfaceTextureExternalTextureVKImpeller::DrawFrame( + PaintContext& context, + const SkRect& bounds, + const DlImageSampling sampling) const { + context.canvas->DrawImage(dl_image_, {0, 0}, sampling, context.paint); +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.h b/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.h new file mode 100644 index 00000000000..1df76aa6ef2 --- /dev/null +++ b/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_ANDROID_SURFACE_TEXTURE_EXTERNAL_TEXTURE_VK_IMPELLER_H_ +#define FLUTTER_SHELL_PLATFORM_ANDROID_SURFACE_TEXTURE_EXTERNAL_TEXTURE_VK_IMPELLER_H_ + +#include + +#include "flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h" +#include "flutter/impeller/renderer/backend/vulkan/context_vk.h" +#include "flutter/impeller/toolkit/egl/context.h" +#include "flutter/impeller/toolkit/egl/display.h" +#include "flutter/impeller/toolkit/egl/surface.h" +#include "flutter/impeller/toolkit/glvk/trampoline.h" +#include "flutter/shell/platform/android/surface_texture_external_texture.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// @brief An external texture implementation for Vulkan that uses OpenGL +/// to Vulkan texture interop to read SurfaceTexture based image +/// data and render it into a Vulkan texture. +/// +/// Unlike other zero-copy implementations, the OpenGL to Vulkan +/// texture interop has the additional overhead of one device-device +/// copy and allocates one offscreen texture. For this reason, one +/// of the "Image" based external texture implementations are a +/// recommended replacement for this class. For example +/// `ImageExternalTextureVKImpeller`. +/// +/// This implementation is used only as a fallback during the +/// migration away from the SurfaceTexture based plugin APIs. For +/// more information about the plugin API migration, see +/// https://docs.flutter.dev/release/breaking-changes/android-surface-plugins. +/// +/// @see `ImageExternalTextureVKImpeller` +/// +class SurfaceTextureExternalTextureVKImpeller final + : public SurfaceTextureExternalTexture { + public: + SurfaceTextureExternalTextureVKImpeller( + std::shared_ptr context, + int64_t id, + const fml::jni::ScopedJavaGlobalRef& surface_texture, + const std::shared_ptr& jni_facade); + + ~SurfaceTextureExternalTextureVKImpeller() override; + + SurfaceTextureExternalTextureVKImpeller( + const SurfaceTextureExternalTextureVKImpeller&) = delete; + + SurfaceTextureExternalTextureVKImpeller& operator=( + const SurfaceTextureExternalTextureVKImpeller&) = delete; + + private: + std::shared_ptr context_; + std::shared_ptr trampoline_; + std::shared_ptr cached_texture_source_; + bool is_valid_ = false; + + // |SurfaceTextureExternalTexture| + void ProcessFrame(PaintContext& context, const SkRect& bounds) override; + + // |SurfaceTextureExternalTexture| + void Detach() override; + + // |SurfaceTextureExternalTexture| + void DrawFrame(PaintContext& context, + const SkRect& bounds, + const DlImageSampling sampling) const override; + + std::shared_ptr GetCachedTextureSource( + const std::shared_ptr& context, + const impeller::ISize& size); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_ANDROID_SURFACE_TEXTURE_EXTERNAL_TEXTURE_VK_IMPELLER_H_ diff --git a/engine/src/flutter/testing/scenario_app/bin/utils/options.dart b/engine/src/flutter/testing/scenario_app/bin/utils/options.dart index 8de3199dda3..c28fd8e0c6c 100644 --- a/engine/src/flutter/testing/scenario_app/bin/utils/options.dart +++ b/engine/src/flutter/testing/scenario_app/bin/utils/options.dart @@ -46,17 +46,6 @@ extension type const Options._(ArgResults _args) { ); } - // Cannot use forceSurfaceProducerSurfaceTexture with Impeller+Vulkan. - if (options.forceSurfaceProducerSurfaceTexture && - options.enableImpeller && - options.impellerBackend != 'opengles') { - throw const FormatException( - 'Cannot use --force-surface-producer-surface-texture with ' - '--enable-impeller unless --impeller-backend="opengles" is used. See ' - 'https://github.com/flutter/flutter/issues/143539 for details.', - ); - } - return options; } @@ -152,11 +141,7 @@ extension type const Options._(ArgResults _args) { 'rendering strategy. This is used to emulate the behavior of older ' 'devices that do not support ImageReader, or to explicitly test ' 'SurfaceTexture path for rendering plugins still using the older ' - 'createSurfaceTexture() API.' - '\n' - 'Cannot be used with --enable-impeller unless --impeller-backend=' - '"opengles" is used. See ' - 'https://github.com/flutter/flutter/issues/143539 for details.', + 'createSurfaceTexture() API.', negatable: false ) ..addFlag( @@ -322,13 +307,7 @@ extension type const Options._(ArgResults _args) { String? get outputContentsGolden => _args['output-contents-golden'] as String; /// Whether to force the use of `SurfaceTexture` for `SurfaceProducer`. - /// - /// Always returns `false` if `--enable-impeller` is `true` and - /// `--impeller-backend` is not `opengles`. bool get forceSurfaceProducerSurfaceTexture { - if (enableImpeller && impellerBackend != 'opengles') { - return false; - } return _args['force-surface-producer-surface-texture'] as bool; }