From 749ae65b26af37d7d7df61bf04ae866e484778a6 Mon Sep 17 00:00:00 2001 From: "auto-submit[bot]" <98614782+auto-submit[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:11:25 +0000 Subject: [PATCH 01/10] Reverts "[Impeller] cache onscreen render targets. (#50751)" (#50871) Reverts flutter/engine#50751 Initiated by: jonahwilliams Reason for reverting: breaking flutter tester --enable-impeller workflow Original PR Author: jonahwilliams Reviewed By: {matanlurey} This change reverts the following previous change: Original Description: Currently we cache render pass objects to offscreen render targets only. Caching onscreen doesn't work as 1) the render targets are stored on the texture_vk object and 2) the texture_vk object is recreated for each frame (see https://github.com/flutter/engine/blob/main/impeller/renderer/backend/vulkan/surface_vk.cc#L14 ). To make sure the render pass is cached, move the storage to the texture source. part of https://github.com/flutter/flutter/issues/141750 --- .../backend/vulkan/test/mock_vulkan.cc | 12 ------ .../vulkan/test/swapchain_unittests.cc | 39 ------------------- .../backend/vulkan/texture_source_vk.cc | 18 --------- .../backend/vulkan/texture_source_vk.h | 26 ------------- .../renderer/backend/vulkan/texture_vk.cc | 8 ++-- impeller/renderer/backend/vulkan/texture_vk.h | 2 + 6 files changed, 6 insertions(+), 99 deletions(-) diff --git a/impeller/renderer/backend/vulkan/test/mock_vulkan.cc b/impeller/renderer/backend/vulkan/test/mock_vulkan.cc index c3bc7fbf72186..72a36804bda88 100644 --- a/impeller/renderer/backend/vulkan/test/mock_vulkan.cc +++ b/impeller/renderer/backend/vulkan/test/mock_vulkan.cc @@ -42,8 +42,6 @@ struct MockImage {}; struct MockSemaphore {}; -struct MockFramebuffer {}; - static ISize currentImageSize = ISize{1, 1}; class MockDevice final { @@ -688,14 +686,6 @@ VkResult vkAcquireNextImageKHR(VkDevice device, return VK_SUCCESS; } -VkResult vkCreateFramebuffer(VkDevice device, - const VkFramebufferCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkFramebuffer* pFramebuffer) { - *pFramebuffer = reinterpret_cast(new MockFramebuffer()); - return VK_SUCCESS; -} - PFN_vkVoidFunction GetMockVulkanProcAddress(VkInstance instance, const char* pName) { if (strcmp("vkEnumerateInstanceExtensionProperties", pName) == 0) { @@ -824,8 +814,6 @@ PFN_vkVoidFunction GetMockVulkanProcAddress(VkInstance instance, return (PFN_vkVoidFunction)vkDestroySurfaceKHR; } else if (strcmp("vkAcquireNextImageKHR", pName) == 0) { return (PFN_vkVoidFunction)vkAcquireNextImageKHR; - } else if (strcmp("vkCreateFramebuffer", pName) == 0) { - return (PFN_vkVoidFunction)vkCreateFramebuffer; } return noop; } diff --git a/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc b/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc index f0af23ed75b46..a78d3187b58df 100644 --- a/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc +++ b/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc @@ -6,7 +6,6 @@ #include "gtest/gtest.h" #include "impeller/renderer/backend/vulkan/swapchain_vk.h" #include "impeller/renderer/backend/vulkan/test/mock_vulkan.h" -#include "impeller/renderer/backend/vulkan/texture_vk.h" #include "vulkan/vulkan_enums.hpp" namespace impeller { @@ -53,43 +52,5 @@ TEST(SwapchainTest, RecreateSwapchainWhenSizeChanges) { EXPECT_EQ(image_b->GetSize(), expected_size); } -TEST(SwapchainTest, CachesRenderPassOnSwapchainImage) { - auto const context = MockVulkanContextBuilder().Build(); - - auto surface = CreateSurface(*context); - auto swapchain = - SwapchainVK::Create(context, std::move(surface), ISize{1, 1}); - - EXPECT_TRUE(swapchain->IsValid()); - - // We should create 3 swapchain images with the current mock setup. However, - // we will only ever return the first image, so the render pass and - // framebuffer will be cached after one call to AcquireNextDrawable. - auto drawable = swapchain->AcquireNextDrawable(); - RenderTarget render_target = drawable->GetTargetRenderPassDescriptor(); - - auto texture = render_target.GetRenderTargetTexture(); - auto& texture_vk = TextureVK::Cast(*texture); - EXPECT_EQ(texture_vk.GetFramebuffer(), nullptr); - EXPECT_EQ(texture_vk.GetRenderPass(), nullptr); - - auto command_buffer = context->CreateCommandBuffer(); - auto render_pass = command_buffer->CreateRenderPass(render_target); - - render_pass->EncodeCommands(); - - EXPECT_NE(texture_vk.GetFramebuffer(), nullptr); - EXPECT_NE(texture_vk.GetRenderPass(), nullptr); - - { - auto drawable = swapchain->AcquireNextDrawable(); - auto texture = render_target.GetRenderTargetTexture(); - auto& texture_vk = TextureVK::Cast(*texture); - - EXPECT_NE(texture_vk.GetFramebuffer(), nullptr); - EXPECT_NE(texture_vk.GetRenderPass(), nullptr); - } -} - } // namespace testing } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/texture_source_vk.cc b/impeller/renderer/backend/vulkan/texture_source_vk.cc index f200bc5778aaf..4a39bce151bd8 100644 --- a/impeller/renderer/backend/vulkan/texture_source_vk.cc +++ b/impeller/renderer/backend/vulkan/texture_source_vk.cc @@ -58,22 +58,4 @@ fml::Status TextureSourceVK::SetLayout(const BarrierVK& barrier) const { return {}; } -void TextureSourceVK::SetFramebuffer( - const SharedHandleVK& framebuffer) { - framebuffer_ = framebuffer; -} - -void TextureSourceVK::SetRenderPass( - const SharedHandleVK& render_pass) { - render_pass_ = render_pass; -} - -SharedHandleVK TextureSourceVK::GetFramebuffer() const { - return framebuffer_; -} - -SharedHandleVK TextureSourceVK::GetRenderPass() const { - return render_pass_; -} - } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/texture_source_vk.h b/impeller/renderer/backend/vulkan/texture_source_vk.h index 502a9a180dfcf..4f76a8067cf4f 100644 --- a/impeller/renderer/backend/vulkan/texture_source_vk.h +++ b/impeller/renderer/backend/vulkan/texture_source_vk.h @@ -65,38 +65,12 @@ class TextureSourceVK { /// Whether or not this is a swapchain image. virtual bool IsSwapchainImage() const = 0; - /// Store the last framebuffer object used with this texture. - /// - /// This field is only set if this texture is used as the resolve texture - /// of a render pass. By construction, this framebuffer should be compatible - /// with any future render passes. - void SetFramebuffer(const SharedHandleVK& framebuffer); - - /// Store the last render pass object used with this texture. - /// - /// This field is only set if this texture is used as the resolve texture - /// of a render pass. By construction, this framebuffer should be compatible - /// with any future render passes. - void SetRenderPass(const SharedHandleVK& render_pass); - - /// Retrieve the last framebuffer object used with this texture. - /// - /// May be nullptr if no previous framebuffer existed. - SharedHandleVK GetFramebuffer() const; - - /// Retrieve the last render pass object used with this texture. - /// - /// May be nullptr if no previous render pass existed. - SharedHandleVK GetRenderPass() const; - protected: const TextureDescriptor desc_; explicit TextureSourceVK(TextureDescriptor desc); private: - SharedHandleVK framebuffer_ = nullptr; - SharedHandleVK render_pass_ = nullptr; mutable RWMutex layout_mutex_; mutable vk::ImageLayout layout_ IPLR_GUARDED_BY(layout_mutex_) = vk::ImageLayout::eUndefined; diff --git a/impeller/renderer/backend/vulkan/texture_vk.cc b/impeller/renderer/backend/vulkan/texture_vk.cc index cd3d93ff7fb0d..b105d31b96e85 100644 --- a/impeller/renderer/backend/vulkan/texture_vk.cc +++ b/impeller/renderer/backend/vulkan/texture_vk.cc @@ -175,20 +175,20 @@ vk::ImageView TextureVK::GetRenderTargetView() const { void TextureVK::SetFramebuffer( const SharedHandleVK& framebuffer) { - source_->SetFramebuffer(framebuffer); + framebuffer_ = framebuffer; } void TextureVK::SetRenderPass( const SharedHandleVK& render_pass) { - source_->SetRenderPass(render_pass); + render_pass_ = render_pass; } SharedHandleVK TextureVK::GetFramebuffer() const { - return source_->GetFramebuffer(); + return framebuffer_; } SharedHandleVK TextureVK::GetRenderPass() const { - return source_->GetRenderPass(); + return render_pass_; } } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/texture_vk.h b/impeller/renderer/backend/vulkan/texture_vk.h index 7a8be812eab31..5826e3df78174 100644 --- a/impeller/renderer/backend/vulkan/texture_vk.h +++ b/impeller/renderer/backend/vulkan/texture_vk.h @@ -73,6 +73,8 @@ class TextureVK final : public Texture, public BackendCast { private: std::weak_ptr context_; std::shared_ptr source_; + SharedHandleVK framebuffer_ = nullptr; + SharedHandleVK render_pass_ = nullptr; // |Texture| void SetLabel(std::string_view label) override; From 05ffcb1cf7d668ae85d3e144b9f4c9fc6f8e1673 Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Thu, 22 Feb 2024 17:31:08 +0000 Subject: [PATCH 02/10] Use RBE on more Windows builders (#50866) --- ci/builders/windows_android_aot_engine.json | 42 ++++++++++++++++++--- ci/builders/windows_arm_host_engine.json | 21 ++++++++--- ci/builders/windows_host_engine.json | 21 ++++++++--- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/ci/builders/windows_android_aot_engine.json b/ci/builders/windows_android_aot_engine.json index 933478b3523b8..a35dad5ea2777 100644 --- a/ci/builders/windows_android_aot_engine.json +++ b/ci/builders/windows_android_aot_engine.json @@ -16,10 +16,15 @@ "device_type=none", "os=Windows-10" ], + "gclient_variables": { + "use_rbe": true + }, "gn": [ "--runtime-mode", "profile", - "--android" + "--android", + "--no-goma", + "--rbe" ], "name": "android_profile", "ninja": { @@ -45,11 +50,16 @@ "device_type=none", "os=Windows-10" ], + "gclient_variables": { + "use_rbe": true + }, "gn": [ "--runtime-mode", "profile", "--android", - "--android-cpu=arm64" + "--android-cpu=arm64", + "--no-goma", + "--rbe" ], "name": "android_profile_arm64", "ninja": { @@ -75,11 +85,16 @@ "device_type=none", "os=Windows-10" ], + "gclient_variables": { + "use_rbe": true + }, "gn": [ "--runtime-mode", "profile", "--android", - "--android-cpu=x64" + "--android-cpu=x64", + "--no-goma", + "--rbe" ], "name": "android_profile_x64", "ninja": { @@ -105,10 +120,15 @@ "device_type=none", "os=Windows-10" ], + "gclient_variables": { + "use_rbe": true + }, "gn": [ "--runtime-mode", "release", - "--android" + "--android", + "--no-goma", + "--rbe" ], "name": "android_release", "ninja": { @@ -134,11 +154,16 @@ "device_type=none", "os=Windows-10" ], + "gclient_variables": { + "use_rbe": true + }, "gn": [ "--runtime-mode", "release", "--android", - "--android-cpu=arm64" + "--android-cpu=arm64", + "--no-goma", + "--rbe" ], "name": "android_release_arm64", "ninja": { @@ -164,11 +189,16 @@ "device_type=none", "os=Windows-10" ], + "gclient_variables": { + "use_rbe": true + }, "gn": [ "--runtime-mode", "release", "--android", - "--android-cpu=x64" + "--android-cpu=x64", + "--no-goma", + "--rbe" ], "name": "android_release_x64", "ninja": { diff --git a/ci/builders/windows_arm_host_engine.json b/ci/builders/windows_arm_host_engine.json index 406533a6d6fa2..8912dade7ef74 100644 --- a/ci/builders/windows_arm_host_engine.json +++ b/ci/builders/windows_arm_host_engine.json @@ -22,14 +22,17 @@ "os=Windows-10" ], "gclient_variables": { - "download_android_deps": false + "download_android_deps": false, + "use_rbe": true }, "gn": [ "--runtime-mode", "debug", "--no-lto", "--windows-cpu", - "arm64" + "arm64", + "--no-goma", + "--rbe" ], "name": "host_debug_arm64", "ninja": { @@ -61,14 +64,17 @@ "os=Windows-10" ], "gclient_variables": { - "download_android_deps": false + "download_android_deps": false, + "use_rbe": true }, "gn": [ "--runtime-mode", "profile", "--no-lto", "--windows-cpu", - "arm64" + "arm64", + "--no-goma", + "--rbe" ], "name": "host_profile_arm64", "ninja": { @@ -97,7 +103,8 @@ "os=Windows-10" ], "gclient_variables": { - "download_android_deps": false + "download_android_deps": false, + "use_rbe": true }, "generators": {}, "gn": [ @@ -105,7 +112,9 @@ "release", "--no-lto", "--windows-cpu", - "arm64" + "arm64", + "--no-goma", + "--rbe" ], "name": "host_release_arm64", "ninja": { diff --git a/ci/builders/windows_host_engine.json b/ci/builders/windows_host_engine.json index 1a9903e22cb62..bca555a9f4499 100644 --- a/ci/builders/windows_host_engine.json +++ b/ci/builders/windows_host_engine.json @@ -22,12 +22,15 @@ "os=Windows-10" ], "gclient_variables": { - "download_android_deps": false + "download_android_deps": false, + "use_rbe": true }, "gn": [ "--runtime-mode", "debug", - "--no-lto" + "--no-lto", + "--no-goma", + "--rbe" ], "name": "host_debug", "ninja": { @@ -73,12 +76,15 @@ "os=Windows-10" ], "gclient_variables": { - "download_android_deps": false + "download_android_deps": false, + "use_rbe": true }, "gn": [ "--runtime-mode", "profile", - "--no-lto" + "--no-lto", + "--no-goma", + "--rbe" ], "name": "host_profile", "ninja": { @@ -107,13 +113,16 @@ "os=Windows-10" ], "gclient_variables": { - "download_android_deps": false + "download_android_deps": false, + "use_rbe": true }, "generators": {}, "gn": [ "--runtime-mode", "release", - "--no-lto" + "--no-lto", + "--no-goma", + "--rbe" ], "name": "host_release", "ninja": { From e922da61d808dd1784df84c6aa310a352177ccfd Mon Sep 17 00:00:00 2001 From: "auto-submit[bot]" <98614782+auto-submit[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:07:17 +0000 Subject: [PATCH 03/10] Reverts "Remove WindowManager reflection in SingleViewPresentation.java (#49996)" (#50873) Reverts flutter/engine#49996 Initiated by: gmackall Reason for reverting: b/326363243 Original PR Author: gmackall Reviewed By: {johnmccutchan, reidbaker} This change reverts the following previous change: Original Description: Fixes https://github.com/flutter/flutter/issues/106449. Changes it to a static proxy, as the comment recommended. This does mean we will have to update it to override new methods as they are added to the interface when updating the version of the Android sdk we use. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../platform/SingleViewPresentation.java | 124 ++++++++---------- .../platform/SingleViewPresentationTest.java | 116 ---------------- 2 files changed, 56 insertions(+), 184 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java index d8dc53049a5be..b7ca559ac7c64 100644 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -22,18 +22,17 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.view.WindowMetrics; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.annotation.VisibleForTesting; import io.flutter.Log; -import java.util.concurrent.Executor; -import java.util.function.Consumer; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; /* * A presentation used for hosting a single Android view in a virtual display. @@ -360,7 +359,7 @@ public Object getSystemService(String name) { private WindowManager getWindowManager() { if (windowManager == null) { - windowManager = windowManagerHandler; + windowManager = windowManagerHandler.getWindowManager(); } return windowManager; } @@ -378,18 +377,21 @@ private boolean isCalledFromAlertDialog() { } /* - * A static proxy handler for a WindowManager with custom overrides. + * A dynamic proxy handler for a WindowManager with custom overrides. * * The presentation's window manager delegates all calls to the default window manager. * WindowManager#addView calls triggered by views that are attached to the virtual display are crashing * (see: https://github.com/flutter/flutter/issues/20714). This was triggered when selecting text in an embedded * WebView (as the selection handles are implemented as popup windows). * - * This static proxy overrides the addView, removeView, removeViewImmediate, and updateViewLayout methods - * to prevent these crashes, and forwards all other calls to the delegate. + * This dynamic proxy overrides the addView, removeView, removeViewImmediate, and updateViewLayout methods + * to prevent these crashes. + * + * This will be more efficient as a static proxy that's not using reflection, but as the engine is currently + * not being built against the latest Android SDK we cannot override all relevant method. + * Tracking issue for upgrading the engine's Android sdk: https://github.com/flutter/flutter/issues/20717 */ - @VisibleForTesting - static class WindowManagerHandler implements WindowManager { + static class WindowManagerHandler implements InvocationHandler { private static final String TAG = "PlatformViewsController"; private final WindowManager delegate; @@ -400,86 +402,72 @@ static class WindowManagerHandler implements WindowManager { fakeWindowRootView = fakeWindowViewGroup; } - @Override - @Deprecated - public Display getDefaultDisplay() { - return delegate.getDefaultDisplay(); + public WindowManager getWindowManager() { + return (WindowManager) + Proxy.newProxyInstance( + WindowManager.class.getClassLoader(), new Class[] {WindowManager.class}, this); } @Override - public void removeViewImmediate(View view) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called removeViewImmediate while detached from presentation"); - return; + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "addView": + addView(args); + return null; + case "removeView": + removeView(args); + return null; + case "removeViewImmediate": + removeViewImmediate(args); + return null; + case "updateViewLayout": + updateViewLayout(args); + return null; + } + try { + return method.invoke(delegate, args); + } catch (InvocationTargetException e) { + throw e.getCause(); } - view.clearAnimation(); - fakeWindowRootView.removeView(view); } - @Override - public void addView(View view, ViewGroup.LayoutParams params) { + private void addView(Object[] args) { if (fakeWindowRootView == null) { Log.w(TAG, "Embedded view called addView while detached from presentation"); return; } - fakeWindowRootView.addView(view, params); + View view = (View) args[0]; + WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) args[1]; + fakeWindowRootView.addView(view, layoutParams); } - @Override - public void updateViewLayout(View view, ViewGroup.LayoutParams params) { + private void removeView(Object[] args) { if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called updateViewLayout while detached from presentation"); + Log.w(TAG, "Embedded view called removeView while detached from presentation"); return; } - fakeWindowRootView.updateViewLayout(view, params); + View view = (View) args[0]; + fakeWindowRootView.removeView(view); } - @Override - public void removeView(View view) { + private void removeViewImmediate(Object[] args) { if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called removeView while detached from presentation"); + Log.w(TAG, "Embedded view called removeViewImmediate while detached from presentation"); return; } + View view = (View) args[0]; + view.clearAnimation(); fakeWindowRootView.removeView(view); } - @RequiresApi(api = Build.VERSION_CODES.R) - @NonNull - @Override - public WindowMetrics getCurrentWindowMetrics() { - return delegate.getCurrentWindowMetrics(); - } - - @RequiresApi(api = Build.VERSION_CODES.R) - @NonNull - @Override - public WindowMetrics getMaximumWindowMetrics() { - return delegate.getMaximumWindowMetrics(); - } - - @RequiresApi(api = Build.VERSION_CODES.S) - @Override - public boolean isCrossWindowBlurEnabled() { - return delegate.isCrossWindowBlurEnabled(); - } - - @RequiresApi(api = Build.VERSION_CODES.S) - @Override - public void addCrossWindowBlurEnabledListener(@NonNull Consumer listener) { - delegate.addCrossWindowBlurEnabledListener(listener); - } - - @RequiresApi(api = Build.VERSION_CODES.S) - @Override - public void addCrossWindowBlurEnabledListener( - @NonNull Executor executor, @NonNull Consumer listener) { - delegate.addCrossWindowBlurEnabledListener(executor, listener); - } - - @RequiresApi(api = Build.VERSION_CODES.S) - @Override - public void removeCrossWindowBlurEnabledListener(@NonNull Consumer listener) { - delegate.removeCrossWindowBlurEnabledListener(listener); + private void updateViewLayout(Object[] args) { + if (fakeWindowRootView == null) { + Log.w(TAG, "Embedded view called updateViewLayout while detached from presentation"); + return; + } + View view = (View) args[0]; + WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) args[1]; + fakeWindowRootView.updateViewLayout(view, layoutParams); } } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java b/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java index d99d344568d25..d27e08bbbdc97 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java @@ -7,26 +7,18 @@ import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.R; -import static android.os.Build.VERSION_CODES.S; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.annotation.TargetApi; import android.content.Context; import android.hardware.display.DisplayManager; import android.view.Display; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.util.concurrent.Executor; -import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @@ -91,112 +83,4 @@ public void returnsOuterContextInputMethodManager_createDisplayContext() { // Android OS (or Robolectric's shadow, in this case). assertEquals(expected, actual); } - - @Test - @Config(minSdk = R) - public void windowManagerHandler_passesCorrectlyToFakeWindowViewGroup() { - // Mock the WindowManager and FakeWindowViewGroup that get used by the WindowManagerHandler. - WindowManager mockWindowManager = mock(WindowManager.class); - SingleViewPresentation.FakeWindowViewGroup mockFakeWindowViewGroup = - mock(SingleViewPresentation.FakeWindowViewGroup.class); - - View mockView = mock(View.class); - ViewGroup.LayoutParams mockLayoutParams = mock(ViewGroup.LayoutParams.class); - - SingleViewPresentation.WindowManagerHandler windowManagerHandler = - new SingleViewPresentation.WindowManagerHandler(mockWindowManager, mockFakeWindowViewGroup); - - // removeViewImmediate - windowManagerHandler.removeViewImmediate(mockView); - verify(mockView).clearAnimation(); - verify(mockFakeWindowViewGroup).removeView(mockView); - verifyNoInteractions(mockWindowManager); - - // addView - windowManagerHandler.addView(mockView, mockLayoutParams); - verify(mockFakeWindowViewGroup).addView(mockView, mockLayoutParams); - verifyNoInteractions(mockWindowManager); - - // updateViewLayout - windowManagerHandler.updateViewLayout(mockView, mockLayoutParams); - verify(mockFakeWindowViewGroup).updateViewLayout(mockView, mockLayoutParams); - verifyNoInteractions(mockWindowManager); - - // removeView - windowManagerHandler.updateViewLayout(mockView, mockLayoutParams); - verify(mockFakeWindowViewGroup).removeView(mockView); - verifyNoInteractions(mockWindowManager); - } - - @Test - @Config(minSdk = R) - public void windowManagerHandler_logAndReturnEarly_whenFakeWindowViewGroupIsNull() { - // Mock the WindowManager and FakeWindowViewGroup that get used by the WindowManagerHandler. - WindowManager mockWindowManager = mock(WindowManager.class); - - View mockView = mock(View.class); - ViewGroup.LayoutParams mockLayoutParams = mock(ViewGroup.LayoutParams.class); - - SingleViewPresentation.WindowManagerHandler windowManagerHandler = - new SingleViewPresentation.WindowManagerHandler(mockWindowManager, null); - - // removeViewImmediate - windowManagerHandler.removeViewImmediate(mockView); - verifyNoInteractions(mockView); - verifyNoInteractions(mockWindowManager); - - // addView - windowManagerHandler.addView(mockView, mockLayoutParams); - verifyNoInteractions(mockWindowManager); - - // updateViewLayout - windowManagerHandler.updateViewLayout(mockView, mockLayoutParams); - verifyNoInteractions(mockWindowManager); - - // removeView - windowManagerHandler.updateViewLayout(mockView, mockLayoutParams); - verifyNoInteractions(mockWindowManager); - } - - // This section tests that WindowManagerHandler forwards all of the non-special case calls to the - // delegate WindowManager. Because this must include some deprecated WindowManager method calls - // (because the proxy overrides every method), we suppress deprecation warnings here. - @Test - @Config(minSdk = S) - @SuppressWarnings("deprecation") - public void windowManagerHandler_forwardsAllOtherCallsToDelegate() { - // Mock the WindowManager and FakeWindowViewGroup that get used by the WindowManagerHandler. - WindowManager mockWindowManager = mock(WindowManager.class); - SingleViewPresentation.FakeWindowViewGroup mockFakeWindowViewGroup = - mock(SingleViewPresentation.FakeWindowViewGroup.class); - - SingleViewPresentation.WindowManagerHandler windowManagerHandler = - new SingleViewPresentation.WindowManagerHandler(mockWindowManager, mockFakeWindowViewGroup); - - // Verify that all other calls get forwarded to the delegate. - Executor mockExecutor = mock(Executor.class); - @SuppressWarnings("Unchecked cast") - Consumer mockListener = (Consumer) mock(Consumer.class); - - windowManagerHandler.getDefaultDisplay(); - verify(mockWindowManager).getDefaultDisplay(); - - windowManagerHandler.getCurrentWindowMetrics(); - verify(mockWindowManager).getCurrentWindowMetrics(); - - windowManagerHandler.getMaximumWindowMetrics(); - verify(mockWindowManager).getMaximumWindowMetrics(); - - windowManagerHandler.isCrossWindowBlurEnabled(); - verify(mockWindowManager).isCrossWindowBlurEnabled(); - - windowManagerHandler.addCrossWindowBlurEnabledListener(mockListener); - verify(mockWindowManager).addCrossWindowBlurEnabledListener(mockListener); - - windowManagerHandler.addCrossWindowBlurEnabledListener(mockExecutor, mockListener); - verify(mockWindowManager).addCrossWindowBlurEnabledListener(mockExecutor, mockListener); - - windowManagerHandler.removeCrossWindowBlurEnabledListener(mockListener); - verify(mockWindowManager).removeCrossWindowBlurEnabledListener(mockListener); - } } From 843ecc57e4f9d6da5ae15afc7718682e20fa5841 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 22 Feb 2024 11:52:42 -0800 Subject: [PATCH 04/10] [Impeller] Add stroke benchmarks that create UVs with no transform (#50847) The current stroke benchmarks exercise their PositionUVWriter variants with a worst case texture mapping that includes an effect transform. These benchmarks won't track any improvements we make to the case that does not include a transform. This PR adds new benchmarks that track performance of computing texture coords with just a set of texture bounds and no transform. --- impeller/geometry/geometry_benchmarks.cc | 56 +++++++++++++----------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/impeller/geometry/geometry_benchmarks.cc b/impeller/geometry/geometry_benchmarks.cc index 484744ad05258..98d17cf7d8c15 100644 --- a/impeller/geometry/geometry_benchmarks.cc +++ b/impeller/geometry/geometry_benchmarks.cc @@ -97,20 +97,28 @@ static void BM_Polyline(benchmark::State& state, Args&&... args) { state.counters["TotalPointCount"] = point_count; } +enum class UVMode { + kNoUV, + kUVRect, + kUVRectTx, +}; + template static void BM_StrokePolyline(benchmark::State& state, Args&&... args) { auto args_tuple = std::make_tuple(std::move(args)...); auto path = std::get(args_tuple); auto cap = std::get(args_tuple); auto join = std::get(args_tuple); - auto generate_uv = std::get(args_tuple); + auto generate_uv = std::get(args_tuple); const Scalar stroke_width = 5.0f; const Scalar miter_limit = 10.0f; const Scalar scale = 1.0f; const Point texture_origin = Point(0, 0); const Size texture_size = Size(100, 100); - const Matrix effect_transform = Matrix::MakeScale({2.0f, 2.0f, 1.0f}); + const Matrix effect_transform = (generate_uv == UVMode::kUVRectTx) + ? Matrix::MakeScale({2.0f, 2.0f, 1.0f}) + : Matrix(); auto points = std::make_unique>(); points->reserve(2048); @@ -123,15 +131,15 @@ static void BM_StrokePolyline(benchmark::State& state, Args&&... args) { size_t point_count = 0u; size_t single_point_count = 0u; while (state.KeepRunning()) { - if (generate_uv) { + if (generate_uv == UVMode::kNoUV) { + auto vertices = ImpellerBenchmarkAccessor::GenerateSolidStrokeVertices( + polyline, stroke_width, miter_limit, join, cap, scale); + single_point_count = vertices.size(); + } else { auto vertices = ImpellerBenchmarkAccessor::GenerateSolidStrokeVerticesUV( polyline, stroke_width, miter_limit, join, cap, scale, // texture_origin, texture_size, effect_transform); single_point_count = vertices.size(); - } else { - auto vertices = ImpellerBenchmarkAccessor::GenerateSolidStrokeVertices( - polyline, stroke_width, miter_limit, join, cap, scale); - single_point_count = vertices.size(); } point_count += single_point_count; } @@ -157,24 +165,21 @@ static void BM_Convex(benchmark::State& state, Args&&... args) { state.counters["TotalPointCount"] = point_count; } -#define MAKE_STROKE_BENCHMARK_CAPTURE(path, cap, join, closed) \ - BENCHMARK_CAPTURE(BM_StrokePolyline, stroke_##path##_##cap##_##join, \ - Create##path(closed), Cap::k##cap, Join::k##join, false) - -#define MAKE_STROKE_BENCHMARK_CAPTURE_UV(path, cap, join, closed) \ - BENCHMARK_CAPTURE(BM_StrokePolyline, stroke_##path##_##cap##_##join##_uv, \ - Create##path(closed), Cap::k##cap, Join::k##join, true) +#define MAKE_STROKE_BENCHMARK_CAPTURE(path, cap, join, closed, uvname, uvtype) \ + BENCHMARK_CAPTURE(BM_StrokePolyline, stroke_##path##_##cap##_##join##uvname, \ + Create##path(closed), Cap::k##cap, Join::k##join, uvtype) -#define MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, uv) \ - MAKE_STROKE_BENCHMARK_CAPTURE##uv(path, Butt, Bevel, false); \ - MAKE_STROKE_BENCHMARK_CAPTURE##uv(path, Butt, Miter, false); \ - MAKE_STROKE_BENCHMARK_CAPTURE##uv(path, Butt, Round, false); \ - MAKE_STROKE_BENCHMARK_CAPTURE##uv(path, Square, Bevel, false); \ - MAKE_STROKE_BENCHMARK_CAPTURE##uv(path, Round, Bevel, false) +#define MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, uvname, uvtype) \ + MAKE_STROKE_BENCHMARK_CAPTURE(path, Butt, Bevel, false, uvname, uvtype); \ + MAKE_STROKE_BENCHMARK_CAPTURE(path, Butt, Miter, false, uvname, uvtype); \ + MAKE_STROKE_BENCHMARK_CAPTURE(path, Butt, Round, false, uvname, uvtype); \ + MAKE_STROKE_BENCHMARK_CAPTURE(path, Square, Bevel, false, uvname, uvtype); \ + MAKE_STROKE_BENCHMARK_CAPTURE(path, Round, Bevel, false, uvname, uvtype) -#define MAKE_STROKE_BENCHMARK_CAPTURE_UVS(path) \ - MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, ); \ - MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, _UV) +#define MAKE_STROKE_BENCHMARK_CAPTURE_UVS(path) \ + MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, , UVMode::kNoUV); \ + MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, _uv, UVMode::kUVRectTx); \ + MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, _uvNoTx, UVMode::kUVRect) BENCHMARK_CAPTURE(BM_Polyline, cubic_polyline, CreateCubic(true), false); BENCHMARK_CAPTURE(BM_Polyline, cubic_polyline_tess, CreateCubic(true), true); @@ -201,8 +206,9 @@ BENCHMARK_CAPTURE(BM_Polyline, MAKE_STROKE_BENCHMARK_CAPTURE_UVS(Quadratic); BENCHMARK_CAPTURE(BM_Convex, rrect_convex, CreateRRect(), true); -MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, ); -MAKE_STROKE_BENCHMARK_CAPTURE_UV(RRect, Butt, Bevel, ); +MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, , , UVMode::kNoUV); +MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, , _uv, UVMode::kUVRectTx); +MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, , _uvNoTx, UVMode::kUVRect); namespace { From 3ee6f251a879f6de188573a25b7276abe1dfeb03 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Thu, 22 Feb 2024 11:52:44 -0800 Subject: [PATCH 05/10] Implement frame timing callbacks in Skwasm. (#50737) Fixes https://github.com/flutter/flutter/issues/140429 Some notes here: * Refactored the frame timing systems so that we can deal with asynchronous rendering. * Consolidated rendering of multiple pictures in skwasm into a single call, so that the rasterization can be properly measured. * Pulled the frame timings tests into the `ui` test suite so that they run on all renderers (including skwasm). --- ci/licenses_golden/licenses_flutter | 2 + lib/web_ui/lib/src/engine.dart | 1 + .../lib/src/engine/canvaskit/rasterizer.dart | 1 + .../lib/src/engine/canvaskit/renderer.dart | 16 +-- lib/web_ui/lib/src/engine/dom.dart | 2 +- .../lib/src/engine/frame_timing_recorder.dart | 100 +++++++++++++++ lib/web_ui/lib/src/engine/html/renderer.dart | 7 +- lib/web_ui/lib/src/engine/html/scene.dart | 12 +- .../lib/src/engine/html/scene_builder.dart | 9 +- lib/web_ui/lib/src/engine/initialization.dart | 17 +-- lib/web_ui/lib/src/engine/profiler.dart | 115 ------------------ lib/web_ui/lib/src/engine/scene_view.dart | 59 ++++++--- .../src/engine/skwasm/skwasm_impl/image.dart | 9 +- .../skwasm/skwasm_impl/raw/raw_surface.dart | 6 +- .../engine/skwasm/skwasm_impl/renderer.dart | 9 +- .../engine/skwasm/skwasm_impl/surface.dart | 32 ++++- lib/web_ui/skwasm/library_skwasm_support.js | 42 +++++-- lib/web_ui/skwasm/skwasm_support.h | 22 ++-- lib/web_ui/skwasm/surface.cpp | 75 ++++++++---- lib/web_ui/skwasm/surface.h | 7 +- .../test/canvaskit/frame_timings_test.dart | 23 ---- .../test/common/frame_timings_common.dart | 53 -------- lib/web_ui/test/engine/scene_view_test.dart | 34 +++--- .../engine/surface/frame_timings_test.dart | 23 ---- lib/web_ui/test/ui/frame_timings_test.dart | 62 ++++++++++ 25 files changed, 400 insertions(+), 338 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/frame_timing_recorder.dart delete mode 100644 lib/web_ui/test/canvaskit/frame_timings_test.dart delete mode 100644 lib/web_ui/test/common/frame_timings_common.dart delete mode 100644 lib/web_ui/test/engine/surface/frame_timings_test.dart create mode 100644 lib/web_ui/test/ui/frame_timings_test.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d287ed8733f88..2d40298c8be5d 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -10324,6 +10324,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/font_fallback_data.dart + ../ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/font_fallbacks.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/frame_timing_recorder.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart + ../../../flutter/LICENSE @@ -13161,6 +13162,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_fallback_data.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_fallbacks.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_timing_recorder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 9697966fe3bf0..0fbda333aac87 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -65,6 +65,7 @@ export 'engine/font_fallback_data.dart'; export 'engine/font_fallbacks.dart'; export 'engine/fonts.dart'; export 'engine/frame_reference.dart'; +export 'engine/frame_timing_recorder.dart'; export 'engine/html/backdrop_filter.dart'; export 'engine/html/bitmap_canvas.dart'; export 'engine/html/canvas.dart'; diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index 58a23e871cc92..bdf6d744f7152 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -131,6 +131,7 @@ abstract class DisplayCanvas { typedef RenderRequest = ({ ui.Scene scene, Completer completer, + FrameTimingRecorder? recorder, }); /// A per-view queue of render requests. Only contains the current render diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index f4fdaef67c0f9..7d673a63c6b3e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -417,16 +417,17 @@ class CanvasKitRenderer implements Renderer { "Unable to render to a view which hasn't been registered"); final ViewRasterizer rasterizer = _rasterizers[view.viewId]!; final RenderQueue renderQueue = rasterizer.queue; + final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled ? FrameTimingRecorder() : null; if (renderQueue.current != null) { // If a scene is already queued up, drop it and queue this one up instead // so that the scene view always displays the most recently requested scene. renderQueue.next?.completer.complete(); final Completer completer = Completer(); - renderQueue.next = (scene: scene, completer: completer); + renderQueue.next = (scene: scene, completer: completer, recorder: recorder); return completer.future; } final Completer completer = Completer(); - renderQueue.current = (scene: scene, completer: completer); + renderQueue.current = (scene: scene, completer: completer, recorder: recorder); unawaited(_kickRenderLoop(rasterizer)); return completer.future; } @@ -435,7 +436,7 @@ class CanvasKitRenderer implements Renderer { final RenderQueue renderQueue = rasterizer.queue; final RenderRequest current = renderQueue.current!; try { - await _renderScene(current.scene, rasterizer); + await _renderScene(current.scene, rasterizer, current.recorder); current.completer.complete(); } catch (error, stackTrace) { current.completer.completeError(error, stackTrace); @@ -449,7 +450,7 @@ class CanvasKitRenderer implements Renderer { } } - Future _renderScene(ui.Scene scene, ViewRasterizer rasterizer) async { + Future _renderScene(ui.Scene scene, ViewRasterizer rasterizer, FrameTimingRecorder? recorder) async { // "Build finish" and "raster start" happen back-to-back because we // render on the same thread, so there's no overhead from hopping to // another thread. @@ -457,11 +458,12 @@ class CanvasKitRenderer implements Renderer { // CanvasKit works differently from the HTML renderer in that in HTML // we update the DOM in SceneBuilder.build, which is these function calls // here are CanvasKit-only. - frameTimingsOnBuildFinish(); - frameTimingsOnRasterStart(); + recorder?.recordBuildFinish(); + recorder?.recordRasterStart(); await rasterizer.draw((scene as LayerScene).layerTree); - frameTimingsOnRasterFinish(); + recorder?.recordRasterFinish(); + recorder?.submitTimings(); } // Map from view id to the associated Rasterizer for that view. diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index 750587774c313..386666d6797ae 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -1487,7 +1487,7 @@ class DomCanvasRenderingContextBitmapRenderer {} extension DomCanvasRenderingContextBitmapRendererExtension on DomCanvasRenderingContextBitmapRenderer { - external void transferFromImageBitmap(DomImageBitmap bitmap); + external void transferFromImageBitmap(DomImageBitmap? bitmap); } @JS('ImageData') diff --git a/lib/web_ui/lib/src/engine/frame_timing_recorder.dart b/lib/web_ui/lib/src/engine/frame_timing_recorder.dart new file mode 100644 index 0000000000000..ec2944bb41327 --- /dev/null +++ b/lib/web_ui/lib/src/engine/frame_timing_recorder.dart @@ -0,0 +1,100 @@ +// 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 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +class FrameTimingRecorder { + final int _vsyncStartMicros = _currentFrameVsyncStart; + final int _buildStartMicros = _currentFrameBuildStart; + + int? _buildFinishMicros; + int? _rasterStartMicros; + int? _rasterFinishMicros; + + /// Collects frame timings from frames. + /// + /// This list is periodically reported to the framework (see [_kFrameTimingsSubmitInterval]). + static List _frameTimings = []; + + /// These two metrics are collected early in the process, before the respective + /// scene builders are created. These are instead treated as global state, which + /// are used to initialize any recorders that are created by the scene builders. + static int _currentFrameVsyncStart = 0; + static int _currentFrameBuildStart = 0; + + static void recordCurrentFrameVsync() { + if (frameTimingsEnabled) { + _currentFrameVsyncStart = _nowMicros(); + } + } + + static void recordCurrentFrameBuildStart() { + if (frameTimingsEnabled) { + _currentFrameBuildStart = _nowMicros(); + } + } + + /// The last time (in microseconds) we submitted frame timings. + static int _frameTimingsLastSubmitTime = _nowMicros(); + /// The amount of time in microseconds we wait between submitting + /// frame timings. + static const int _kFrameTimingsSubmitInterval = 100000; // 100 milliseconds + + /// Whether we are collecting [ui.FrameTiming]s. + static bool get frameTimingsEnabled { + return EnginePlatformDispatcher.instance.onReportTimings != null; + } + + /// Current timestamp in microseconds taken from the high-precision + /// monotonically increasing timer. + /// + /// See also: + /// + /// * https://developer.mozilla.org/en-US/docs/Web/API/Performance/now, + /// particularly notes about Firefox rounding to 1ms for security reasons, + /// which can be bypassed in tests by setting certain browser options. + static int _nowMicros() { + return (domWindow.performance.now() * 1000).toInt(); + } + + void recordBuildFinish([int? buildFinish]) { + assert(_buildFinishMicros == null, "can't record build finish more than once"); + _buildFinishMicros = buildFinish ?? _nowMicros(); + } + + void recordRasterStart([int? rasterStart]) { + assert(_rasterStartMicros == null, "can't record raster start more than once"); + _rasterStartMicros = rasterStart ?? _nowMicros(); + } + + void recordRasterFinish([int? rasterFinish]) { + assert(_rasterFinishMicros == null, "can't record raster finish more than once"); + _rasterFinishMicros = rasterFinish ?? _nowMicros(); + } + + void submitTimings() { + assert( + _buildFinishMicros != null && + _rasterStartMicros != null && + _rasterFinishMicros != null, + 'Attempted to submit an incomplete timings.' + ); + final ui.FrameTiming timing = ui.FrameTiming( + vsyncStart: _vsyncStartMicros, + buildStart: _buildStartMicros, + buildFinish: _buildFinishMicros!, + rasterStart: _rasterStartMicros!, + rasterFinish: _rasterFinishMicros!, + rasterFinishWallTime: _rasterFinishMicros!, + ); + _frameTimings.add(timing); + final int now = _nowMicros(); + if (now - _frameTimingsLastSubmitTime > _kFrameTimingsSubmitInterval) { + _frameTimingsLastSubmitTime = now; + EnginePlatformDispatcher.instance.invokeOnReportTimings(_frameTimings); + _frameTimings = []; + } + } +} diff --git a/lib/web_ui/lib/src/engine/html/renderer.dart b/lib/web_ui/lib/src/engine/html/renderer.dart index c9febef391e37..b41fac3739234 100644 --- a/lib/web_ui/lib/src/engine/html/renderer.dart +++ b/lib/web_ui/lib/src/engine/html/renderer.dart @@ -323,8 +323,11 @@ class HtmlRenderer implements Renderer { @override Future renderScene(ui.Scene scene, ui.FlutterView view) async { final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!; - implicitView.dom.setScene((scene as SurfaceScene).webOnlyRootElement!); - frameTimingsOnRasterFinish(); + scene as SurfaceScene; + implicitView.dom.setScene(scene.webOnlyRootElement!); + final FrameTimingRecorder? recorder = scene.timingRecorder; + recorder?.recordRasterFinish(); + recorder?.submitTimings(); } @override diff --git a/lib/web_ui/lib/src/engine/html/scene.dart b/lib/web_ui/lib/src/engine/html/scene.dart index f15d043447e4b..b4deb9a6dac69 100644 --- a/lib/web_ui/lib/src/engine/html/scene.dart +++ b/lib/web_ui/lib/src/engine/html/scene.dart @@ -2,22 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:ui/src/engine/display.dart'; +import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import '../dom.dart'; -import '../vector_math.dart'; -import '../window.dart'; -import 'surface.dart'; - class SurfaceScene implements ui.Scene { /// This class is created by the engine, and should not be instantiated /// or extended directly. /// /// To create a Scene object, use a [SceneBuilder]. - SurfaceScene(this.webOnlyRootElement); + SurfaceScene(this.webOnlyRootElement, { + required this.timingRecorder, + }); final DomElement? webOnlyRootElement; + final FrameTimingRecorder? timingRecorder; /// Creates a raster image representation of the current state of the scene. /// This is a slow operation that is performed on a background thread. diff --git a/lib/web_ui/lib/src/engine/html/scene_builder.dart b/lib/web_ui/lib/src/engine/html/scene_builder.dart index e721aa5a5576b..701bb11ef92ad 100644 --- a/lib/web_ui/lib/src/engine/html/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/html/scene_builder.dart @@ -7,7 +7,7 @@ import 'dart:typed_data'; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; -import '../../engine.dart' show kProfileApplyFrame, kProfilePrerollFrame; +import '../../engine.dart' show FrameTimingRecorder, kProfileApplyFrame, kProfilePrerollFrame; import '../display.dart'; import '../dom.dart'; import '../picture.dart'; @@ -511,8 +511,9 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { // In the HTML renderer we time the beginning of the rasterization phase // (counter-intuitively) in SceneBuilder.build because DOM updates happen // here. This is different from CanvasKit. - frameTimingsOnBuildFinish(); - frameTimingsOnRasterStart(); + final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled ? FrameTimingRecorder() : null; + recorder?.recordBuildFinish(); + recorder?.recordRasterStart(); timeAction(kProfilePrerollFrame, () { while (_surfaceStack.length > 1) { // Auto-pop layers that were pushed without a corresponding pop. @@ -528,7 +529,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { } commitScene(_persistedScene); _lastFrameScene = _persistedScene; - return SurfaceScene(_persistedScene.rootElement); + return SurfaceScene(_persistedScene.rootElement, timingRecorder: recorder); }); } diff --git a/lib/web_ui/lib/src/engine/initialization.dart b/lib/web_ui/lib/src/engine/initialization.dart index 0dab016be43ba..745f8b2e84cf7 100644 --- a/lib/web_ui/lib/src/engine/initialization.dart +++ b/lib/web_ui/lib/src/engine/initialization.dart @@ -158,7 +158,15 @@ Future initializeEngineServices({ if (!waitingForAnimation) { waitingForAnimation = true; domWindow.requestAnimationFrame((JSNumber highResTime) { - frameTimingsOnVsync(); + FrameTimingRecorder.recordCurrentFrameVsync(); + + // In Flutter terminology "building a frame" consists of "beginning + // frame" and "drawing frame". + // + // We do not call `recordBuildFinish` from here because + // part of the rasterization process, particularly in the HTML + // renderer, takes place in the `SceneBuilder.build()`. + FrameTimingRecorder.recordCurrentFrameBuildStart(); // Reset immediately, because `frameHandler` can schedule more frames. waitingForAnimation = false; @@ -171,13 +179,6 @@ Future initializeEngineServices({ final int highResTimeMicroseconds = (1000 * highResTime.toDartDouble).toInt(); - // In Flutter terminology "building a frame" consists of "beginning - // frame" and "drawing frame". - // - // We do not call `frameTimingsOnBuildFinish` from here because - // part of the rasterization process, particularly in the HTML - // renderer, takes place in the `SceneBuilder.build()`. - frameTimingsOnBuildStart(); if (EnginePlatformDispatcher.instance.onBeginFrame != null) { EnginePlatformDispatcher.instance.invokeOnBeginFrame( Duration(microseconds: highResTimeMicroseconds)); diff --git a/lib/web_ui/lib/src/engine/profiler.dart b/lib/web_ui/lib/src/engine/profiler.dart index ffabd12d2156b..d5ef8b3fa831b 100644 --- a/lib/web_ui/lib/src/engine/profiler.dart +++ b/lib/web_ui/lib/src/engine/profiler.dart @@ -5,11 +5,8 @@ import 'dart:async'; import 'dart:js_interop'; -import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; -import 'dom.dart'; -import 'platform_dispatcher.dart'; import 'util.dart'; // TODO(mdebbar): Deprecate this and remove it. @@ -127,118 +124,6 @@ class Profiler { } } -/// Whether we are collecting [ui.FrameTiming]s. -bool get _frameTimingsEnabled { - return EnginePlatformDispatcher.instance.onReportTimings != null; -} - -/// Collects frame timings from frames. -/// -/// This list is periodically reported to the framework (see -/// [_kFrameTimingsSubmitInterval]). -List _frameTimings = []; - -/// The amount of time in microseconds we wait between submitting -/// frame timings. -const int _kFrameTimingsSubmitInterval = 100000; // 100 milliseconds - -/// The last time (in microseconds) we submitted frame timings. -int _frameTimingsLastSubmitTime = _nowMicros(); - -// These variables store individual [ui.FrameTiming] properties. -int _vsyncStartMicros = -1; -int _buildStartMicros = -1; -int _buildFinishMicros = -1; -int _rasterStartMicros = -1; -int _rasterFinishMicros = -1; - -/// Records the vsync timestamp for this frame. -void frameTimingsOnVsync() { - if (!_frameTimingsEnabled) { - return; - } - _vsyncStartMicros = _nowMicros(); -} - -/// Records the time when the framework started building the frame. -void frameTimingsOnBuildStart() { - if (!_frameTimingsEnabled) { - return; - } - _buildStartMicros = _nowMicros(); -} - -/// Records the time when the framework finished building the frame. -void frameTimingsOnBuildFinish() { - if (!_frameTimingsEnabled) { - return; - } - _buildFinishMicros = _nowMicros(); -} - -/// Records the time when the framework started rasterizing the frame. -/// -/// On the web, this value is almost always the same as [_buildFinishMicros] -/// because it's single-threaded so there's no delay between building -/// and rasterization. -/// -/// This also means different things between HTML and CanvasKit renderers. -/// -/// In HTML "rasterization" only captures DOM updates, but not the work that -/// the browser performs after the DOM updates are committed. The browser -/// does not report that information. -/// -/// CanvasKit captures everything because we control the rasterization -/// process, so we know exactly when rasterization starts and ends. -void frameTimingsOnRasterStart() { - if (!_frameTimingsEnabled) { - return; - } - _rasterStartMicros = _nowMicros(); -} - -/// Records the time when the framework started rasterizing the frame. -/// -/// See [_frameTimingsOnRasterStart] for more details on what rasterization -/// timings mean on the web. -void frameTimingsOnRasterFinish() { - if (!_frameTimingsEnabled) { - return; - } - final int now = _nowMicros(); - _rasterFinishMicros = now; - _frameTimings.add(ui.FrameTiming( - vsyncStart: _vsyncStartMicros, - buildStart: _buildStartMicros, - buildFinish: _buildFinishMicros, - rasterStart: _rasterStartMicros, - rasterFinish: _rasterFinishMicros, - rasterFinishWallTime: _rasterFinishMicros, - )); - _vsyncStartMicros = -1; - _buildStartMicros = -1; - _buildFinishMicros = -1; - _rasterStartMicros = -1; - _rasterFinishMicros = -1; - if (now - _frameTimingsLastSubmitTime > _kFrameTimingsSubmitInterval) { - _frameTimingsLastSubmitTime = now; - EnginePlatformDispatcher.instance.invokeOnReportTimings(_frameTimings); - _frameTimings = []; - } -} - -/// Current timestamp in microseconds taken from the high-precision -/// monotonically increasing timer. -/// -/// See also: -/// -/// * https://developer.mozilla.org/en-US/docs/Web/API/Performance/now, -/// particularly notes about Firefox rounding to 1ms for security reasons, -/// which can be bypassed in tests by setting certain browser options. -int _nowMicros() { - return (domWindow.performance.now() * 1000).toInt(); -} - /// Counts various events that take place while the app is running. /// /// This class will slow down the app, and therefore should be disabled while diff --git a/lib/web_ui/lib/src/engine/scene_view.dart b/lib/web_ui/lib/src/engine/scene_view.dart index 23010c6db0bff..b137b70f4f909 100644 --- a/lib/web_ui/lib/src/engine/scene_view.dart +++ b/lib/web_ui/lib/src/engine/scene_view.dart @@ -9,20 +9,31 @@ import 'package:ui/ui.dart' as ui; const String kCanvasContainerTag = 'flt-canvas-container'; +typedef RenderResult = ({ + List imageBitmaps, + int rasterStartMicros, + int rasterEndMicros, +}); + // This is an interface that renders a `ScenePicture` as a `DomImageBitmap`. // It is optionally asynchronous. It is required for the `EngineSceneView` to // composite pictures into the canvases in the DOM tree it builds. abstract class PictureRenderer { - FutureOr renderPicture(ScenePicture picture); + FutureOr renderPictures(List picture); } class _SceneRender { - _SceneRender(this.scene, this._completer) { + _SceneRender( + this.scene, + this._completer, { + this.recorder, + }) { scene.beginRender(); } final EngineScene scene; final Completer _completer; + final FrameTimingRecorder? recorder; void done() { scene.endRender(); @@ -47,24 +58,24 @@ class EngineSceneView { _SceneRender? _currentRender; _SceneRender? _nextRender; - Future renderScene(EngineScene scene) { + Future renderScene(EngineScene scene, FrameTimingRecorder? recorder) { if (_currentRender != null) { // If a scene is already queued up, drop it and queue this one up instead // so that the scene view always displays the most recently requested scene. _nextRender?.done(); final Completer completer = Completer(); - _nextRender = _SceneRender(scene, completer); + _nextRender = _SceneRender(scene, completer, recorder: recorder); return completer.future; } final Completer completer = Completer(); - _currentRender = _SceneRender(scene, completer); + _currentRender = _SceneRender(scene, completer, recorder: recorder); _kickRenderLoop(); return completer.future; } Future _kickRenderLoop() async { final _SceneRender current = _currentRender!; - await _renderScene(current.scene); + await _renderScene(current.scene, current.recorder); current.done(); _currentRender = _nextRender; _nextRender = null; @@ -75,19 +86,33 @@ class EngineSceneView { } } - Future _renderScene(EngineScene scene) async { + Future _renderScene(EngineScene scene, FrameTimingRecorder? recorder) async { final List slices = scene.rootLayer.slices; - final Iterable> renderFutures = slices.map( - (LayerSlice slice) async => switch (slice) { - PlatformViewSlice() => null, - PictureSlice() => pictureRenderer.renderPicture(slice.picture), - } - ); - final List renderedBitmaps = await Future.wait(renderFutures); + final List picturesToRender = []; + for (final LayerSlice slice in slices) { + if (slice is PictureSlice) { + picturesToRender.add(slice.picture); + } + } + final Map renderMap; + if (picturesToRender.isNotEmpty) { + final RenderResult renderResult = await pictureRenderer.renderPictures(picturesToRender); + renderMap = { + for (int i = 0; i < picturesToRender.length; i++) + picturesToRender[i]: renderResult.imageBitmaps[i], + }; + recorder?.recordRasterStart(renderResult.rasterStartMicros); + recorder?.recordRasterFinish(renderResult.rasterEndMicros); + } else { + renderMap = {}; + recorder?.recordRasterStart(); + recorder?.recordRasterFinish(); + } + recorder?.submitTimings(); + final List reusableContainers = List.from(containers); final List newContainers = []; - for (int i = 0; i < slices.length; i++) { - final LayerSlice slice = slices[i]; + for (final LayerSlice slice in slices) { switch (slice) { case PictureSlice(): PictureSliceContainer? container; @@ -106,7 +131,7 @@ class EngineSceneView { container = PictureSliceContainer(slice.picture.cullRect); } container.updateContents(); - container.renderBitmap(renderedBitmaps[i]!); + container.renderBitmap(renderMap[slice.picture]!); newContainers.add(container); case PlatformViewSlice(): diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart index ee32ffd987350..2b800ba276964 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart @@ -62,9 +62,9 @@ class SkwasmImage extends SkwasmObjectWrapper implements ui.Image { final ui.Canvas canvas = ui.Canvas(recorder); canvas.drawImage(this, ui.Offset.zero, ui.Paint()); final DomImageBitmap bitmap = - await (renderer as SkwasmRenderer).surface.renderPicture( - recorder.endRecording() as SkwasmPicture, - ); + (await (renderer as SkwasmRenderer).surface.renderPictures( + [recorder.endRecording() as SkwasmPicture], + )).imageBitmaps.single; final DomOffscreenCanvas offscreenCanvas = createDomOffscreenCanvas(bitmap.width.toDartInt, bitmap.height.toDartInt); final DomCanvasRenderingContextBitmapRenderer context = @@ -75,8 +75,7 @@ class SkwasmImage extends SkwasmObjectWrapper implements ui.Image { // Zero out the contents of the canvas so that resources can be reclaimed // by the browser. - offscreenCanvas.width = 0; - offscreenCanvas.height = 0; + context.transferFromImageBitmap(null); return ByteData.view(arrayBuffer.toDart); } else { return (renderer as SkwasmRenderer).surface.rasterizeImage(this, format); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart index 3831188c4df63..22b7462eec9bc 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart @@ -35,10 +35,10 @@ external void surfaceSetCallbackHandler( isLeaf: true) external void surfaceDestroy(SurfaceHandle surface); -@Native( - symbol: 'surface_renderPicture', +@Native, Int)>( + symbol: 'surface_renderPictures', isLeaf: true) -external CallbackId surfaceRenderPicture(SurfaceHandle surface, PictureHandle picture); +external CallbackId surfaceRenderPictures(SurfaceHandle surface, Pointer picture, int count); @Native renderScene(ui.Scene scene, ui.FlutterView view) { + final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled ? FrameTimingRecorder() : null; + recorder?.recordBuildFinish(); + view as EngineFlutterView; assert(view is EngineFlutterWindow, 'Skwasm does not support multi-view mode yet'); final EngineSceneView sceneView = _getSceneViewForView(view); - return sceneView.renderScene(scene as EngineScene); + return sceneView.renderScene(scene as EngineScene, recorder); } EngineSceneView _getSceneViewForView(EngineFlutterView view) { @@ -477,6 +480,6 @@ class SkwasmPictureRenderer implements PictureRenderer { SkwasmSurface surface; @override - FutureOr renderPicture(ScenePicture picture) => - surface.renderPicture(picture as SkwasmPicture); + FutureOr renderPictures(List pictures) => + surface.renderPictures(pictures.cast()); } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart index 34af06d0d1fd7..eddcc19ff402b 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart @@ -12,6 +12,17 @@ import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; +@JS() +@staticInterop +@anonymous +class RasterResult {} + +extension RasterResultExtension on RasterResult { + external JSNumber get rasterStartMilliseconds; + external JSNumber get rasterEndMilliseconds; + external JSArray get imageBitmaps; +} + @pragma('wasm:export') WasmVoid callbackHandler(WasmI32 callbackId, WasmI32 context, WasmExternRef? jsContext) { // Actually hide this call behind whether skwasm is enabled. Otherwise, the SkwasmCallbackHandler @@ -78,11 +89,22 @@ class SkwasmSurface { surfaceSetCallbackHandler(handle, SkwasmCallbackHandler.instance.callbackPointer); } - Future renderPicture(SkwasmPicture picture) async { - final int callbackId = surfaceRenderPicture(handle, picture.handle); - final DomImageBitmap bitmap = (await SkwasmCallbackHandler.instance.registerCallback(callbackId)) as DomImageBitmap; - return bitmap; - } + Future renderPictures(List pictures) => + withStackScope((StackScope scope) async { + final Pointer pictureHandles = + scope.allocPointerArray(pictures.length).cast(); + for (int i = 0; i < pictures.length; i++) { + pictureHandles[i] = pictures[i].handle; + } + final int callbackId = surfaceRenderPictures(handle, pictureHandles, pictures.length); + final RasterResult rasterResult = (await SkwasmCallbackHandler.instance.registerCallback(callbackId)) as RasterResult; + final RenderResult result = ( + imageBitmaps: rasterResult.imageBitmaps.toDart.cast(), + rasterStartMicros: (rasterResult.rasterStartMilliseconds.toDartDouble * 1000).toInt(), + rasterEndMicros: (rasterResult.rasterEndMilliseconds.toDartDouble * 1000).toInt(), + ); + return result; + }); Future rasterizeImage(SkwasmImage image, ui.ImageByteFormat format) async { final int callbackId = surfaceRasterizeImage( diff --git a/lib/web_ui/skwasm/library_skwasm_support.js b/lib/web_ui/skwasm/library_skwasm_support.js index 5e62614e009c4..76cbac2db2a87 100644 --- a/lib/web_ui/skwasm/library_skwasm_support.js +++ b/lib/web_ui/skwasm/library_skwasm_support.js @@ -27,11 +27,18 @@ mergeInto(LibraryManager.library, { return; } switch (skwasmMessage) { - case 'renderPicture': - _surface_renderPictureOnWorker(data.surface, data.picture, data.callbackId); + case 'renderPictures': + _surface_renderPicturesOnWorker(data.surface, data.pictures, data.pictureCount, data.callbackId, performance.now()); return; case 'onRenderComplete': - _surface_onRenderComplete(data.surface, data.callbackId, data.imageBitmap); + _surface_onRenderComplete( + data.surface, + data.callbackId, { + "imageBitmaps": data.imageBitmaps, + "rasterStartMilliseconds": data.rasterStart, + "rasterEndMilliseconds": data.rasterEnd, + }, + ); return; case 'setAssociatedObject': associatedObjectsMap.set(data.pointer, data.object); @@ -54,11 +61,12 @@ mergeInto(LibraryManager.library, { PThread.pthreads[threadId].addEventListener("message", eventListener); } }; - _skwasm_dispatchRenderPicture = function(threadId, surfaceHandle, pictureHandle, callbackId) { + _skwasm_dispatchRenderPictures = function(threadId, surfaceHandle, pictures, pictureCount, callbackId) { PThread.pthreads[threadId].postMessage({ - skwasmMessage: 'renderPicture', + skwasmMessage: 'renderPictures', surface: surfaceHandle, - picture: pictureHandle, + pictures, + pictureCount, callbackId, }); }; @@ -85,15 +93,23 @@ mergeInto(LibraryManager.library, { canvas.width = width; canvas.height = height; }; - _skwasm_captureImageBitmap = async function(surfaceHandle, contextHandle, callbackId, width, height) { + _skwasm_captureImageBitmap = function(contextHandle, width, height, imagePromises) { + if (!imagePromises) imagePromises = Array(); const canvas = handleToCanvasMap.get(contextHandle); - const imageBitmap = await createImageBitmap(canvas, 0, 0, width, height); + imagePromises.push(createImageBitmap(canvas, 0, 0, width, height)); + return imagePromises; + }; + _skwasm_resolveAndPostImages = async function(surfaceHandle, imagePromises, rasterStart, callbackId) { + const imageBitmaps = imagePromises ? await Promise.all(imagePromises) : []; + const rasterEnd = performance.now(); postMessage({ skwasmMessage: 'onRenderComplete', surface: surfaceHandle, callbackId, - imageBitmap, - }, [imageBitmap]); + imageBitmaps, + rasterStart, + rasterEnd, + }, [...imageBitmaps]); }; _skwasm_createGlTextureFromTextureSource = function(textureSource, width, height) { const glCtx = GL.currentContext.GLctx; @@ -125,14 +141,16 @@ mergeInto(LibraryManager.library, { skwasm_disposeAssociatedObjectOnThread__deps: ['$skwasm_support_setup'], skwasm_registerMessageListener: function() {}, skwasm_registerMessageListener__deps: ['$skwasm_support_setup'], - skwasm_dispatchRenderPicture: function() {}, - skwasm_dispatchRenderPicture__deps: ['$skwasm_support_setup'], + skwasm_dispatchRenderPictures: function() {}, + skwasm_dispatchRenderPictures__deps: ['$skwasm_support_setup'], skwasm_createOffscreenCanvas: function () {}, skwasm_createOffscreenCanvas__deps: ['$skwasm_support_setup'], skwasm_resizeCanvas: function () {}, skwasm_resizeCanvas__deps: ['$skwasm_support_setup'], skwasm_captureImageBitmap: function () {}, skwasm_captureImageBitmap__deps: ['$skwasm_support_setup'], + skwasm_resolveAndPostImages: function () {}, + skwasm_resolveAndPostImages__deps: ['$skwasm_support_setup'], skwasm_createGlTextureFromTextureSource: function () {}, skwasm_createGlTextureFromTextureSource__deps: ['$skwasm_support_setup'], }); diff --git a/lib/web_ui/skwasm/skwasm_support.h b/lib/web_ui/skwasm/skwasm_support.h index ce36a192a69b6..c9132b89dd166 100644 --- a/lib/web_ui/skwasm/skwasm_support.h +++ b/lib/web_ui/skwasm/skwasm_support.h @@ -23,17 +23,21 @@ extern SkwasmObject skwasm_getAssociatedObject(void* pointer); extern void skwasm_disposeAssociatedObjectOnThread(unsigned long threadId, void* pointer); extern void skwasm_registerMessageListener(pthread_t threadId); -extern void skwasm_dispatchRenderPicture(unsigned long threadId, - Skwasm::Surface* surface, - SkPicture* picture, - uint32_t callbackId); +extern void skwasm_dispatchRenderPictures(unsigned long threadId, + Skwasm::Surface* surface, + sk_sp* pictures, + int count, + uint32_t callbackId); extern uint32_t skwasm_createOffscreenCanvas(int width, int height); extern void skwasm_resizeCanvas(uint32_t contextHandle, int width, int height); -extern void skwasm_captureImageBitmap(Skwasm::Surface* surfaceHandle, - uint32_t contextHandle, - uint32_t bitmapId, - int width, - int height); +extern SkwasmObject skwasm_captureImageBitmap(uint32_t contextHandle, + int width, + int height, + SkwasmObject imagePromises); +extern void skwasm_resolveAndPostImages(Skwasm::Surface* surface, + SkwasmObject imagePromises, + double rasterStart, + uint32_t callbackId); extern unsigned int skwasm_createGlTextureFromTextureSource( SkwasmObject textureSource, int width, diff --git a/lib/web_ui/skwasm/surface.cpp b/lib/web_ui/skwasm/surface.cpp index 28b64bd25a328..a1c24b4bad83e 100644 --- a/lib/web_ui/skwasm/surface.cpp +++ b/lib/web_ui/skwasm/surface.cpp @@ -39,11 +39,19 @@ void Surface::dispose() { } // Main thread only -uint32_t Surface::renderPicture(SkPicture* picture) { +uint32_t Surface::renderPictures(SkPicture** pictures, int count) { assert(emscripten_is_main_browser_thread()); uint32_t callbackId = ++_currentCallbackId; - picture->ref(); - skwasm_dispatchRenderPicture(_thread, this, picture, callbackId); + std::unique_ptr[]> picturePointers = + std::make_unique[]>(count); + for (int i = 0; i < count; i++) { + picturePointers[i] = sk_ref_sp(pictures[i]); + } + + // Releasing picturePointers here and will recreate the unique_ptr on the + // other thread See surface_renderPicturesOnWorker + skwasm_dispatchRenderPictures(_thread, this, picturePointers.release(), count, + callbackId); return callbackId; } @@ -136,20 +144,31 @@ void Surface::_recreateSurface() { } // Worker thread only -void Surface::renderPictureOnWorker(SkPicture* picture, uint32_t callbackId) { - SkRect pictureRect = picture->cullRect(); - SkIRect roundedOutRect; - pictureRect.roundOut(&roundedOutRect); - _resizeCanvasToFit(roundedOutRect.width(), roundedOutRect.height()); - SkMatrix matrix = - SkMatrix::Translate(-roundedOutRect.fLeft, -roundedOutRect.fTop); - makeCurrent(_glContext); - auto canvas = _surface->getCanvas(); - canvas->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc); - canvas->drawPicture(sk_ref_sp(picture), &matrix, nullptr); - _grContext->flush(_surface.get()); - skwasm_captureImageBitmap(this, _glContext, callbackId, - roundedOutRect.width(), roundedOutRect.height()); +void Surface::renderPicturesOnWorker(sk_sp* pictures, + int pictureCount, + uint32_t callbackId, + double rasterStart) { + // This is populated by the `captureImageBitmap` call the first time it is + // passed in. + SkwasmObject imagePromiseArray = __builtin_wasm_ref_null_extern(); + for (int i = 0; i < pictureCount; i++) { + sk_sp picture = pictures[i]; + SkRect pictureRect = picture->cullRect(); + SkIRect roundedOutRect; + pictureRect.roundOut(&roundedOutRect); + _resizeCanvasToFit(roundedOutRect.width(), roundedOutRect.height()); + SkMatrix matrix = + SkMatrix::Translate(-roundedOutRect.fLeft, -roundedOutRect.fTop); + makeCurrent(_glContext); + auto canvas = _surface->getCanvas(); + canvas->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc); + canvas->drawPicture(picture, &matrix, nullptr); + _grContext->flush(_surface.get()); + imagePromiseArray = + skwasm_captureImageBitmap(_glContext, roundedOutRect.width(), + roundedOutRect.height(), imagePromiseArray); + } + skwasm_resolveAndPostImages(this, imagePromiseArray, rasterStart, callbackId); } void Surface::_rasterizeImage(SkImage* image, @@ -225,16 +244,22 @@ SKWASM_EXPORT void surface_destroy(Surface* surface) { surface->dispose(); } -SKWASM_EXPORT uint32_t surface_renderPicture(Surface* surface, - SkPicture* picture) { - return surface->renderPicture(picture); +SKWASM_EXPORT uint32_t surface_renderPictures(Surface* surface, + SkPicture** pictures, + int count) { + return surface->renderPictures(pictures, count); } -SKWASM_EXPORT void surface_renderPictureOnWorker(Surface* surface, - SkPicture* picture, - uint32_t callbackId) { - surface->renderPictureOnWorker(picture, callbackId); - picture->unref(); +SKWASM_EXPORT void surface_renderPicturesOnWorker(Surface* surface, + sk_sp* pictures, + int pictureCount, + uint32_t callbackId, + double rasterStart) { + // This will release the pictures when they leave scope. + std::unique_ptr> picturesPointer = + std::unique_ptr>(pictures); + surface->renderPicturesOnWorker(pictures, pictureCount, callbackId, + rasterStart); } SKWASM_EXPORT uint32_t surface_rasterizeImage(Surface* surface, diff --git a/lib/web_ui/skwasm/surface.h b/lib/web_ui/skwasm/surface.h index 3577a947c782a..7e1d5fbb526e1 100644 --- a/lib/web_ui/skwasm/surface.h +++ b/lib/web_ui/skwasm/surface.h @@ -62,7 +62,7 @@ class Surface { // Main thread only void dispose(); - uint32_t renderPicture(SkPicture* picture); + uint32_t renderPictures(SkPicture** picture, int count); uint32_t rasterizeImage(SkImage* image, ImageByteFormat format); void setCallbackHandler(CallbackHandler* callbackHandler); void onRenderComplete(uint32_t callbackId, SkwasmObject imageBitmap); @@ -72,7 +72,10 @@ class Surface { SkwasmObject textureSource); // Worker thread - void renderPictureOnWorker(SkPicture* picture, uint32_t callbackId); + void renderPicturesOnWorker(sk_sp* picture, + int pictureCount, + uint32_t callbackId, + double rasterStart); private: void _runWorker(); diff --git a/lib/web_ui/test/canvaskit/frame_timings_test.dart b/lib/web_ui/test/canvaskit/frame_timings_test.dart deleted file mode 100644 index 0cacd4246616b..0000000000000 --- a/lib/web_ui/test/canvaskit/frame_timings_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -// 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 'package:test/bootstrap/browser.dart'; -import 'package:test/test.dart'; - -import '../common/frame_timings_common.dart'; -import 'common.dart'; - -void main() { - internalBootstrapBrowserTest(() => testMain); -} - -void testMain() { - group('frame timings', () { - setUpCanvasKitTest(withImplicitView: true); - - test('collects frame timings', () async { - await runFrameTimingsTest(); - }); - }); -} diff --git a/lib/web_ui/test/common/frame_timings_common.dart b/lib/web_ui/test/common/frame_timings_common.dart deleted file mode 100644 index 314e1a808861e..0000000000000 --- a/lib/web_ui/test/common/frame_timings_common.dart +++ /dev/null @@ -1,53 +0,0 @@ -// 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 'dart:async'; - -import 'package:test/test.dart'; -import 'package:ui/src/engine.dart' show EnginePlatformDispatcher; -import 'package:ui/ui.dart' as ui; - -/// Tests frame timings in a renderer-agnostic way. -/// -/// See CanvasKit-specific and HTML-specific test files `frame_timings_test.dart`. -Future runFrameTimingsTest() async { - final EnginePlatformDispatcher dispatcher = ui.PlatformDispatcher.instance as EnginePlatformDispatcher; - - List? timings; - dispatcher.onReportTimings = (List data) { - timings = data; - }; - Completer frameDone = Completer(); - dispatcher.onDrawFrame = () { - final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); - sceneBuilder - ..pushOffset(0, 0) - ..pop(); - dispatcher.render(sceneBuilder.build()).then((_) { - frameDone.complete(); - }); - }; - - // Frame 1. - dispatcher.scheduleFrame(); - await frameDone.future; - expect(timings, isNull, reason: "100 ms hasn't passed yet"); - await Future.delayed(const Duration(milliseconds: 150)); - - // Frame 2. - frameDone = Completer(); - dispatcher.scheduleFrame(); - await frameDone.future; - expect(timings, hasLength(2), reason: '100 ms passed. 2 frames pumped.'); - for (final ui.FrameTiming timing in timings!) { - expect(timing.vsyncOverhead, greaterThanOrEqualTo(Duration.zero)); - expect(timing.buildDuration, greaterThanOrEqualTo(Duration.zero)); - expect(timing.rasterDuration, greaterThanOrEqualTo(Duration.zero)); - expect(timing.totalSpan, greaterThanOrEqualTo(Duration.zero)); - expect(timing.layerCacheCount, equals(0)); - expect(timing.layerCacheBytes, equals(0)); - expect(timing.pictureCacheCount, equals(0)); - expect(timing.pictureCacheBytes, equals(0)); - } -} diff --git a/lib/web_ui/test/engine/scene_view_test.dart b/lib/web_ui/test/engine/scene_view_test.dart index 48e84b717f5eb..93d54b09b226f 100644 --- a/lib/web_ui/test/engine/scene_view_test.dart +++ b/lib/web_ui/test/engine/scene_view_test.dart @@ -24,17 +24,23 @@ class StubPictureRenderer implements PictureRenderer { createDomCanvasElement(width: 500, height: 500); @override - Future renderPicture(ScenePicture picture) async { - renderedPictures.add(picture); - final ui.Rect cullRect = picture.cullRect; - final DomImageBitmap bitmap = - await createImageBitmap(scratchCanvasElement as JSObject, ( - x: 0, - y: 0, - width: cullRect.width.toInt(), - height: cullRect.height.toInt(), - )); - return bitmap; + Future renderPictures(List pictures) async { + renderedPictures.addAll(pictures); + final List bitmaps = await Future.wait(pictures.map((ScenePicture picture) { + final ui.Rect cullRect = picture.cullRect; + final Future bitmap = createImageBitmap(scratchCanvasElement as JSObject, ( + x: 0, + y: 0, + width: cullRect.width.toInt(), + height: cullRect.height.toInt(), + )); + return bitmap; + })); + return ( + imageBitmaps: bitmaps, + rasterStartMicros: 0, + rasterEndMicros: 0, + ); } List renderedPictures = []; @@ -65,7 +71,7 @@ void testMain() { final EngineRootLayer rootLayer = EngineRootLayer(); rootLayer.slices.add(PictureSlice(picture)); final EngineScene scene = EngineScene(rootLayer); - await sceneView.renderScene(scene); + await sceneView.renderScene(scene, null); final DomElement sceneElement = sceneView.sceneElement; final List children = sceneElement.children.toList(); @@ -100,7 +106,7 @@ void testMain() { final EngineRootLayer rootLayer = EngineRootLayer(); rootLayer.slices.add(PlatformViewSlice([platformView], null)); final EngineScene scene = EngineScene(rootLayer); - await sceneView.renderScene(scene); + await sceneView.renderScene(scene, null); final DomElement sceneElement = sceneView.sceneElement; final List children = sceneElement.children.toList(); @@ -134,7 +140,7 @@ void testMain() { final EngineRootLayer rootLayer = EngineRootLayer(); rootLayer.slices.add(PictureSlice(picture)); final EngineScene scene = EngineScene(rootLayer); - renderFutures.add(sceneView.renderScene(scene)); + renderFutures.add(sceneView.renderScene(scene, null)); } await Future.wait(renderFutures); diff --git a/lib/web_ui/test/engine/surface/frame_timings_test.dart b/lib/web_ui/test/engine/surface/frame_timings_test.dart deleted file mode 100644 index 14ec8f2e353da..0000000000000 --- a/lib/web_ui/test/engine/surface/frame_timings_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -// 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 'package:test/bootstrap/browser.dart'; -import 'package:test/test.dart'; - -import '../../common/frame_timings_common.dart'; -import '../../common/test_initialization.dart'; - -void main() { - internalBootstrapBrowserTest(() => testMain); -} - -void testMain() { - setUp(() async { - await bootstrapAndRunApp(withImplicitView: true); - }); - - test('collects frame timings', () async { - await runFrameTimingsTest(); - }); -} diff --git a/lib/web_ui/test/ui/frame_timings_test.dart b/lib/web_ui/test/ui/frame_timings_test.dart new file mode 100644 index 0000000000000..62f83b71d4d09 --- /dev/null +++ b/lib/web_ui/test/ui/frame_timings_test.dart @@ -0,0 +1,62 @@ +// 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 'dart:async'; + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +import '../common/test_initialization.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() { + setUp(() async { + await bootstrapAndRunApp(withImplicitView: true); + }); + + test('collects frame timings', () async { + final EnginePlatformDispatcher dispatcher = ui.PlatformDispatcher.instance as EnginePlatformDispatcher; + List? timings; + dispatcher.onReportTimings = (List data) { + timings = data; + }; + Completer frameDone = Completer(); + dispatcher.onDrawFrame = () { + final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); + sceneBuilder + ..pushOffset(0, 0) + ..pop(); + dispatcher.render(sceneBuilder.build()).then((_) { + frameDone.complete(); + }); + }; + + // Frame 1. + dispatcher.scheduleFrame(); + await frameDone.future; + expect(timings, isNull, reason: "100 ms hasn't passed yet"); + await Future.delayed(const Duration(milliseconds: 150)); + + // Frame 2. + frameDone = Completer(); + dispatcher.scheduleFrame(); + await frameDone.future; + expect(timings, hasLength(2), reason: '100 ms passed. 2 frames pumped.'); + for (final ui.FrameTiming timing in timings!) { + expect(timing.vsyncOverhead, greaterThanOrEqualTo(Duration.zero)); + expect(timing.buildDuration, greaterThanOrEqualTo(Duration.zero)); + expect(timing.rasterDuration, greaterThanOrEqualTo(Duration.zero)); + expect(timing.totalSpan, greaterThanOrEqualTo(Duration.zero)); + expect(timing.layerCacheCount, equals(0)); + expect(timing.layerCacheBytes, equals(0)); + expect(timing.pictureCacheCount, equals(0)); + expect(timing.pictureCacheBytes, equals(0)); + } + }); +} From bd7c9d78f81f99dca4f26afb5b9fc5508bfb3aa3 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:52:46 -0800 Subject: [PATCH 06/10] Pass the missing strut half leading flag over to skia paragraph builder (#50385) Not sure how this can be tested. Part of https://github.com/flutter/flutter/issues/142969 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- third_party/txt/BUILD.gn | 1 + .../txt/src/skia/paragraph_builder_skia.cc | 1 + .../txt/src/skia/paragraph_builder_skia.h | 2 + .../txt/tests/paragraph_builder_skia_tests.cc | 45 +++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 third_party/txt/tests/paragraph_builder_skia_tests.cc diff --git a/third_party/txt/BUILD.gn b/third_party/txt/BUILD.gn index fea46cd4e4116..a653adbbdfe5b 100644 --- a/third_party/txt/BUILD.gn +++ b/third_party/txt/BUILD.gn @@ -147,6 +147,7 @@ if (enable_unittests) { sources = [ "tests/font_collection_tests.cc", + "tests/paragraph_builder_skia_tests.cc", "tests/paragraph_unittests.cc", "tests/txt_run_all_unittests.cc", ] diff --git a/third_party/txt/src/skia/paragraph_builder_skia.cc b/third_party/txt/src/skia/paragraph_builder_skia.cc index 443c2b9ca2b08..8c6381a6b8a5b 100644 --- a/third_party/txt/src/skia/paragraph_builder_skia.cc +++ b/third_party/txt/src/skia/paragraph_builder_skia.cc @@ -119,6 +119,7 @@ skt::ParagraphStyle ParagraphBuilderSkia::TxtToSkia(const ParagraphStyle& txt) { strut_style.setFontSize(SkDoubleToScalar(txt.strut_font_size)); strut_style.setHeight(SkDoubleToScalar(txt.strut_height)); strut_style.setHeightOverride(txt.strut_has_height_override); + strut_style.setHalfLeading(txt.strut_half_leading); std::vector strut_fonts; std::transform(txt.strut_font_families.begin(), txt.strut_font_families.end(), diff --git a/third_party/txt/src/skia/paragraph_builder_skia.h b/third_party/txt/src/skia/paragraph_builder_skia.h index f14f7fe41f591..6269285899f58 100644 --- a/third_party/txt/src/skia/paragraph_builder_skia.h +++ b/third_party/txt/src/skia/paragraph_builder_skia.h @@ -45,6 +45,8 @@ class ParagraphBuilderSkia : public ParagraphBuilder { virtual std::unique_ptr Build() override; private: + friend class SkiaParagraphBuilderTests_ParagraphStrutStyle_Test; + skia::textlayout::ParagraphPainter::PaintID CreatePaintID( const flutter::DlPaint& dl_paint); skia::textlayout::ParagraphStyle TxtToSkia(const ParagraphStyle& txt); diff --git a/third_party/txt/tests/paragraph_builder_skia_tests.cc b/third_party/txt/tests/paragraph_builder_skia_tests.cc new file mode 100644 index 0000000000000..3131624be0a66 --- /dev/null +++ b/third_party/txt/tests/paragraph_builder_skia_tests.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gtest/gtest.h" + +#include + +#include "skia/paragraph_builder_skia.h" +#include "txt/paragraph_style.h" + +namespace txt { + +class SkiaParagraphBuilderTests : public ::testing::Test { + public: + SkiaParagraphBuilderTests() {} + + void SetUp() override {} +}; + +TEST_F(SkiaParagraphBuilderTests, ParagraphStrutStyle) { + ParagraphStyle style = ParagraphStyle(); + auto collection = std::make_shared(); + auto builder = ParagraphBuilderSkia(style, collection, false); + + auto strut_style = builder.TxtToSkia(style).getStrutStyle(); + ASSERT_FALSE(strut_style.getHalfLeading()); + + style.strut_half_leading = true; + strut_style = builder.TxtToSkia(style).getStrutStyle(); + ASSERT_TRUE(strut_style.getHalfLeading()); +} +} // namespace txt From 9bf1cecbd6db422ce2942723ad5b6b98c26b6a65 Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Thu, 22 Feb 2024 20:00:54 +0000 Subject: [PATCH 07/10] Remove 'bringup: true' from 'Mac mac_unopt' (#50865) Second half of https://github.com/flutter/flutter/issues/142010 --- .ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.ci.yaml b/.ci.yaml index 10323f566d916..13c8ba1f7e419 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -387,7 +387,6 @@ targets: - os=Mac-13 - name: Mac mac_unopt - bringup: true recipe: engine_v2/engine_v2 properties: config_name: mac_unopt From fbc41605cbf6f940a2e96941de32ae81d270fb1d Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 22 Feb 2024 15:03:53 -0500 Subject: [PATCH 08/10] Roll Skia from b9c16065b76d to dd8cd405d145 (2 revisions) (#50872) https://skia.googlesource.com/skia.git/+log/b9c16065b76d..dd8cd405d145 2024-02-22 kjlubick@google.com Revert "Add TextShaper back to normal srcs" 2024-02-22 kjlubick@google.com Add TextShaper back to normal srcs If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC brianosman@google.com,jimgraham@google.com,rmistry@google.com,scroggo@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 12da47b50825b..2b582f128c672 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'b9c16065b76d1d80940d8239199414d19bcf73e8', + 'skia_revision': 'dd8cd405d14575917d7b941cde0a7dac1f00b5f6', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 547c83466f5efceb8f88d13b1e2b782f6a551192 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:17:22 -0600 Subject: [PATCH 09/10] Delete and create iOS simulator before running Scenario app test (#50835) Make iOS Scenario app tests create the simulator it needs before the tests. Also, delete any existing matching simulators to prevent duplicates. Fixes https://github.com/flutter/flutter/issues/143870. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- testing/scenario_app/run_ios_tests.sh | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/testing/scenario_app/run_ios_tests.sh b/testing/scenario_app/run_ios_tests.sh index 9b34497bb5094..ed040b16d1b47 100755 --- a/testing/scenario_app/run_ios_tests.sh +++ b/testing/scenario_app/run_ios_tests.sh @@ -58,13 +58,34 @@ zip_and_upload_xcresult_to_luci () { exit 1 } +readonly DEVICE_NAME="iPhone SE (3rd generation)" +readonly DEVICE=com.apple.CoreSimulator.SimDeviceType.iPhone-SE-3rd-generation +readonly OS_RUNTIME=com.apple.CoreSimulator.SimRuntime.iOS-17-0 +readonly OS="17.0" + +# Delete any existing devices named "iPhone SE (3rd generation)". Having more +# than one may cause issues when builds target the device. +echo "Deleting any existing devices names $DEVICE_NAME..." +RESULT=0 +while [[ $RESULT == 0 ]]; do + xcrun simctl delete "$DEVICE_NAME" || RESULT=1 + if [ $RESULT == 0 ]; then + echo "Deleted $DEVICE_NAME" + fi +done +echo "" + +echo "Creating $DEVICE_NAME $DEVICE $OS_RUNTIME ..." +xcrun simctl create "$DEVICE_NAME" "$DEVICE" "$OS_RUNTIME" +echo "" + echo "Running simulator tests with Skia" echo "" if set -o pipefail && xcodebuild -sdk iphonesimulator \ -scheme Scenarios \ -resultBundlePath "$RESULT_BUNDLE_PATH/ios_scenario.xcresult" \ - -destination 'platform=iOS Simulator,OS=17.0,name=iPhone SE (3rd generation)' \ + -destination "platform=iOS Simulator,OS=$OS,name=$DEVICE_NAME" \ clean test \ FLUTTER_ENGINE="$FLUTTER_ENGINE"; then echo "test success." @@ -82,7 +103,7 @@ echo "" if set -o pipefail && xcodebuild -sdk iphonesimulator \ -scheme Scenarios \ -resultBundlePath "$RESULT_BUNDLE_PATH/ios_scenario.xcresult" \ - -destination 'platform=iOS Simulator,OS=17.0,name=iPhone SE (3rd generation)' \ + -destination "platform=iOS Simulator,OS=$OS,name=$DEVICE_NAME" \ clean test \ FLUTTER_ENGINE="$FLUTTER_ENGINE" \ -skip-testing ScenariosUITests/MultiplePlatformViewsBackgroundForegroundTest/testPlatformView \ From cb6115d3a6abe9af691b78c067954f75378b9f6b Mon Sep 17 00:00:00 2001 From: Dan Field Date: Thu, 22 Feb 2024 12:34:11 -0800 Subject: [PATCH 10/10] Avoid generated plugin registrant warnings for scenario_app (#50874) Cleans up some noise in those logs. --- .../java/dev/flutter/scenarios/EngineLaunchE2ETest.java | 8 +++++++- .../dev/flutter/scenarios/SpawnMultiEngineActivity.java | 8 +++++--- .../java/dev/flutter/scenarios/SpawnedEngineActivity.java | 6 ++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java index a5415b5425199..ab874f45fb741 100644 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java @@ -33,7 +33,13 @@ public void smokeTestEngineLaunch() throws Throwable { // Run the production under test on the UI thread instead of annotating the whole test // as @UiThreadTest because having the message handler and the CompletableFuture both being // on the same thread will create deadlocks. - UiThreadStatement.runOnUiThread(() -> engine.set(new FlutterEngine(applicationContext))); + UiThreadStatement.runOnUiThread( + () -> + engine.set( + new FlutterEngine( + applicationContext, + /*dartVmArgs */ null, + /* automaticallyRegisterPlugins */ false))); SettableFuture statusReceived = SettableFuture.create(); // Resolve locale to `en_US`. diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnMultiEngineActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnMultiEngineActivity.java index 02fe7a24127b2..d95adbd29c8df 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnMultiEngineActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnMultiEngineActivity.java @@ -16,14 +16,16 @@ public class SpawnMultiEngineActivity extends TestActivity { @NonNull public FlutterEngine provideFlutterEngine(@NonNull Context context) { FlutterEngineGroup engineGroup = new FlutterEngineGroup(context); - FlutterEngine firstEngine = engineGroup.createAndRunDefaultEngine(context); + FlutterEngineGroup.Options options = + new FlutterEngineGroup.Options(context).setAutomaticallyRegisterPlugins(false); + FlutterEngine firstEngine = engineGroup.createAndRunEngine(options); - FlutterEngine secondEngine = engineGroup.createAndRunDefaultEngine(context); + FlutterEngine secondEngine = engineGroup.createAndRunEngine(options); // Check that a new engine can be spawned from the group even if the group's // original engine has been destroyed. firstEngine.destroy(); - FlutterEngine thirdEngine = engineGroup.createAndRunDefaultEngine(context); + FlutterEngine thirdEngine = engineGroup.createAndRunEngine(options); return thirdEngine; } diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java index fbce67dede353..6026893f99011 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java @@ -16,9 +16,11 @@ public class SpawnedEngineActivity extends TestActivity { @NonNull public FlutterEngine provideFlutterEngine(@NonNull Context context) { FlutterEngineGroup engineGroup = new FlutterEngineGroup(context); - engineGroup.createAndRunDefaultEngine(context); + FlutterEngineGroup.Options options = + new FlutterEngineGroup.Options(context).setAutomaticallyRegisterPlugins(false); + engineGroup.createAndRunEngine(options); - FlutterEngine secondEngine = engineGroup.createAndRunDefaultEngine(context); + FlutterEngine secondEngine = engineGroup.createAndRunEngine(options); secondEngine .getDartExecutor()