[Impeller] Sync presentation when rendering into FlutterImageView. (flutter/engine#44881)

Fixes https://github.com/flutter/flutter/issues/131730

When the Android embedder starts rendering into a FlutterImageView, notify the Impeller context to block on submitKHR.
This commit is contained in:
Jonah Williams 2023-08-28 17:00:54 -07:00 committed by GitHub
parent a62803495b
commit 199e8abb3f
12 changed files with 116 additions and 38 deletions

View File

@ -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 <typename T>
@ -161,6 +166,7 @@ class ContextVK final : public Context,
std::shared_ptr<ResourceManagerVK> resource_manager_;
std::string device_name_;
std::shared_ptr<fml::ConcurrentMessageLoop> raster_message_loop_;
bool sync_presentation_ = false;
const uint64_t hash_;
bool is_valid_ = false;

View File

@ -81,6 +81,10 @@ std::unique_ptr<Surface> SurfaceContextVK::AcquireNextSurface() {
return surface;
}
void SurfaceContextVK::SetSyncPresentation(bool value) {
parent_->SetSyncPresentation(value);
}
#ifdef FML_OS_ANDROID
vk::UniqueSurfaceKHR SurfaceContextVK::CreateAndroidSurface(

View File

@ -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<Surface> AcquireNextSurface();

View File

@ -465,48 +465,51 @@ bool SwapchainImplVK::Present(const std::shared_ptr<SwapchainImageVK>& 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<uint32_t>(index)};
//----------------------------------------------------------------------------
/// Present the image.
///
uint32_t indices[] = {static_cast<uint32_t>(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;
}

View File

@ -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<HostBuffer>& GetHostBufferPool() const { return host_buffer_pool_; }

View File

@ -347,4 +347,8 @@ void AndroidShellHolder::UpdateDisplayMetrics() {
shell_->OnDisplayUpdates(std::move(displays));
}
void AndroidShellHolder::SetIsRenderingToImageView(bool value) {
platform_view_->SetIsRenderingToImageView(value);
}
} // namespace flutter

View File

@ -105,6 +105,8 @@ class AndroidShellHolder {
void UpdateDisplayMetrics();
void SetIsRenderingToImageView(bool value);
private:
const flutter::Settings settings_;
const std::shared_ptr<PlatformViewAndroidJNI> jni_facade_;

View File

@ -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() {

View File

@ -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 ---

View File

@ -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.

View File

@ -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<PlatformViewAndroidJNI> jni_facade_;
std::shared_ptr<AndroidContext> android_context_;

View File

@ -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<void*>(&NotifyLowMemoryWarning),
},
{
.name = "nativeSetIsRenderingToImageView",
.signature = "(JZ)V",
.fnPtr = reinterpret_cast<void*>(&SetIsRenderingToImageView),
},
// Start of methods from FlutterView
{