diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h index 43d4531ff5f..316cf1cbb21 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h @@ -83,6 +83,11 @@ class ContextVK final : public Context, // |Context| void Shutdown() override; + // |Context| + void SetSyncPresentation(bool value) override { sync_presentation_ = value; } + + bool GetSyncPresentation() const { return sync_presentation_; } + void SetOffscreenFormat(PixelFormat pixel_format); template @@ -161,6 +166,7 @@ class ContextVK final : public Context, std::shared_ptr resource_manager_; std::string device_name_; std::shared_ptr raster_message_loop_; + bool sync_presentation_ = false; const uint64_t hash_; bool is_valid_ = false; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc index 7f2fdf82003..083d02c2b5a 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc @@ -81,6 +81,10 @@ std::unique_ptr SurfaceContextVK::AcquireNextSurface() { return surface; } +void SurfaceContextVK::SetSyncPresentation(bool value) { + parent_->SetSyncPresentation(value); +} + #ifdef FML_OS_ANDROID vk::UniqueSurfaceKHR SurfaceContextVK::CreateAndroidSurface( diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.h index 37a38e4dc9c..9bb65484802 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.h @@ -54,6 +54,9 @@ class SurfaceContextVK : public Context, // |Context| void Shutdown() override; + // |Context| + void SetSyncPresentation(bool value) override; + [[nodiscard]] bool SetWindowSurface(vk::UniqueSurfaceKHR surface); std::unique_ptr AcquireNextSurface(); diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc index 88233fa022b..7e563241f38 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc @@ -465,48 +465,51 @@ bool SwapchainImplVK::Present(const std::shared_ptr& image, } } - context.GetConcurrentWorkerTaskRunner()->PostTask( - [&, index, image, current_frame = current_frame_] { - auto context_strong = context_.lock(); - if (!context_strong) { - return; - } + auto task = [&, index, image, current_frame = current_frame_] { + auto context_strong = context_.lock(); + if (!context_strong) { + return; + } - const auto& sync = synchronizers_[current_frame]; + const auto& sync = synchronizers_[current_frame]; - //---------------------------------------------------------------------------- - /// Present the image. - /// - uint32_t indices[] = {static_cast(index)}; + //---------------------------------------------------------------------------- + /// Present the image. + /// + uint32_t indices[] = {static_cast(index)}; - vk::PresentInfoKHR present_info; - present_info.setSwapchains(*swapchain_); - present_info.setImageIndices(indices); - present_info.setWaitSemaphores(*sync->present_ready); + vk::PresentInfoKHR present_info; + present_info.setSwapchains(*swapchain_); + present_info.setImageIndices(indices); + present_info.setWaitSemaphores(*sync->present_ready); - switch (auto result = present_queue_.presentKHR(present_info)) { - case vk::Result::eErrorOutOfDateKHR: - // Caller will recreate the impl on acquisition, not submission. - [[fallthrough]]; - case vk::Result::eErrorSurfaceLostKHR: - // Vulkan guarantees that the set of queue operations will still - // complete successfully. - [[fallthrough]]; - case vk::Result::eSuboptimalKHR: - // Even though we're handling rotation changes via polling, we - // still need to handle the case where the swapchain signals that - // it's suboptimal (i.e. every frame when we are rotated given we - // aren't doing Vulkan pre-rotation). - [[fallthrough]]; - case vk::Result::eSuccess: - return; - default: - VALIDATION_LOG << "Could not present queue: " - << vk::to_string(result); - return; - } - FML_UNREACHABLE(); - }); + switch (auto result = present_queue_.presentKHR(present_info)) { + case vk::Result::eErrorOutOfDateKHR: + // Caller will recreate the impl on acquisition, not submission. + [[fallthrough]]; + case vk::Result::eErrorSurfaceLostKHR: + // Vulkan guarantees that the set of queue operations will still + // complete successfully. + [[fallthrough]]; + case vk::Result::eSuboptimalKHR: + // Even though we're handling rotation changes via polling, we + // still need to handle the case where the swapchain signals that + // it's suboptimal (i.e. every frame when we are rotated given we + // aren't doing Vulkan pre-rotation). + [[fallthrough]]; + case vk::Result::eSuccess: + return; + default: + VALIDATION_LOG << "Could not present queue: " << vk::to_string(result); + return; + } + FML_UNREACHABLE(); + }; + if (context.GetSyncPresentation()) { + task(); + } else { + context.GetConcurrentWorkerTaskRunner()->PostTask(task); + } return true; } diff --git a/engine/src/flutter/impeller/renderer/context.h b/engine/src/flutter/impeller/renderer/context.h index 1eb2662e6c9..3cc847235cd 100644 --- a/engine/src/flutter/impeller/renderer/context.h +++ b/engine/src/flutter/impeller/renderer/context.h @@ -161,6 +161,15 @@ class Context { /// virtual void Shutdown() = 0; + //---------------------------------------------------------------------------- + /// @brief Force the Vulkan presentation (submitKHR) to be performed on + /// the raster task runner. + /// + /// This is required for correct rendering on Android when using + /// the hybrid composition mode. This has no effect on other + /// backends. + virtual void SetSyncPresentation(bool value) {} + //---------------------------------------------------------------------------- /// @brief Accessor for a pool of HostBuffers. Pool& GetHostBufferPool() const { return host_buffer_pool_; } diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder.cc b/engine/src/flutter/shell/platform/android/android_shell_holder.cc index becc6861a60..80eb4839fc4 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder.cc +++ b/engine/src/flutter/shell/platform/android/android_shell_holder.cc @@ -347,4 +347,8 @@ void AndroidShellHolder::UpdateDisplayMetrics() { shell_->OnDisplayUpdates(std::move(displays)); } +void AndroidShellHolder::SetIsRenderingToImageView(bool value) { + platform_view_->SetIsRenderingToImageView(value); +} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder.h b/engine/src/flutter/shell/platform/android/android_shell_holder.h index 30530a89620..f6405a1485b 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder.h +++ b/engine/src/flutter/shell/platform/android/android_shell_holder.h @@ -105,6 +105,8 @@ class AndroidShellHolder { void UpdateDisplayMetrics(); + void SetIsRenderingToImageView(bool value); + private: const flutter::Settings settings_; const std::shared_ptr jni_facade_; diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java index 505400dd82e..1379b483043 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java @@ -143,6 +143,7 @@ public class FlutterImageView extends View implements RenderSurface { switch (kind) { case background: flutterRenderer.swapSurface(imageReader.getSurface()); + flutterRenderer.SetRenderingToImageView(true); break; case overlay: // Do nothing since the attachment is done by the handler of @@ -173,6 +174,12 @@ public class FlutterImageView extends View implements RenderSurface { closeCurrentImage(); invalidate(); isAttachedToFlutterRenderer = false; + if (kind == SurfaceKind.background) { + // The overlay FlutterImageViews seem to be constructed per frame and not + // always used; An overlay FlutterImageView always seems to imply + // a background FlutterImageView. + flutterRenderer.SetRenderingToImageView(false); + } } public void pause() { diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index a1532413b83..1d66eeff9dc 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -740,6 +740,14 @@ public class FlutterJNI { int[] displayFeaturesBounds, int[] displayFeaturesType, int[] displayFeaturesState); + + @UiThread + public void SetIsRenderingToImageView(boolean value) { + nativeSetIsRenderingToImageView(nativeShellHolderId, value); + } + + private native void nativeSetIsRenderingToImageView(long nativeShellHolderId, boolean value); + // ----- End Render Surface Support ----- // ------ Start Touch Interaction Support --- diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index f9ea3285217..f1faeab73e1 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -50,6 +50,7 @@ public class FlutterRenderer implements TextureRegistry { @NonNull private final AtomicLong nextTextureId = new AtomicLong(0L); @Nullable private Surface surface; private boolean isDisplayingFlutterUi = false; + private int isRenderingToImageViewCount = 0; private Handler handler = new Handler(); @NonNull @@ -83,6 +84,19 @@ public class FlutterRenderer implements TextureRegistry { return isDisplayingFlutterUi; } + /** + * Informs the renderer whether the surface it is rendering to is backend by a {@code + * FlutterImageView}, which requires additional synchonization in the Vulkan backend. + */ + public void SetRenderingToImageView(boolean value) { + if (value) { + isRenderingToImageViewCount++; + } else { + isRenderingToImageViewCount--; + } + flutterJNI.SetIsRenderingToImageView(isRenderingToImageViewCount > 0); + } + /** * Adds a listener that is invoked whenever this {@code FlutterRenderer} starts and stops painting * pixels to an Android {@code View} hierarchy. diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.h b/engine/src/flutter/shell/platform/android/platform_view_android.h index 5510fb65ad1..6e4d83fda02 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.h +++ b/engine/src/flutter/shell/platform/android/platform_view_android.h @@ -120,6 +120,12 @@ class PlatformViewAndroid final : public PlatformView { return platform_message_handler_; } + void SetIsRenderingToImageView(bool value) { + if (GetImpellerContext()) { + GetImpellerContext()->SetSyncPresentation(value); + } + } + private: const std::shared_ptr jni_facade_; std::shared_ptr android_context_; diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc index 65fcbd24c7c..aefb58e6a1d 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc @@ -550,6 +550,13 @@ static void NotifyLowMemoryWarning(JNIEnv* env, ANDROID_SHELL_HOLDER->NotifyLowMemoryWarning(); } +static void SetIsRenderingToImageView(JNIEnv* env, + jobject jcaller, + jlong shell_holder, + bool value) { + ANDROID_SHELL_HOLDER->SetIsRenderingToImageView(value); +} + static jboolean FlutterTextUtilsIsEmoji(JNIEnv* env, jobject obj, jint codePoint) { @@ -709,6 +716,11 @@ bool RegisterApi(JNIEnv* env) { .signature = "(J)V", .fnPtr = reinterpret_cast(&NotifyLowMemoryWarning), }, + { + .name = "nativeSetIsRenderingToImageView", + .signature = "(JZ)V", + .fnPtr = reinterpret_cast(&SetIsRenderingToImageView), + }, // Start of methods from FlutterView {