| // 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. |
| |
| #define FML_USED_ON_EMBEDDER |
| |
| #include <string> |
| #include <vector> |
| |
| #include "embedder.h" |
| #include "embedder_engine.h" |
| #include "flutter/flow/raster_cache.h" |
| #include "flutter/fml/file.h" |
| #include "flutter/fml/make_copyable.h" |
| #include "flutter/fml/mapping.h" |
| #include "flutter/fml/message_loop.h" |
| #include "flutter/fml/paths.h" |
| #include "flutter/fml/synchronization/count_down_latch.h" |
| #include "flutter/fml/synchronization/waitable_event.h" |
| #include "flutter/fml/thread.h" |
| #include "flutter/runtime/dart_vm.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_test.h" |
| #include "flutter/testing/assertions_skia.h" |
| #include "flutter/testing/testing.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/tonic/converter/dart_converter.h" |
| |
| namespace flutter { |
| namespace testing { |
| |
| using EmbedderTest = testing::EmbedderTest; |
| |
| TEST(EmbedderTestNoFixture, MustNotRunWithInvalidArgs) { |
| EmbedderTestContext context; |
| EmbedderConfigBuilder builder( |
| context, EmbedderConfigBuilder::InitializationPreference::kNoInitialize); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { |
| auto& context = GetEmbedderContext(); |
| fml::AutoResetWaitableEvent latch; |
| context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| // Wait for the root isolate to launch. |
| latch.Wait(); |
| engine.reset(); |
| } |
| |
| // TODO(41999): Disabled because flaky. |
| TEST_F(EmbedderTest, DISABLED_CanLaunchAndShutdownMultipleTimes) { |
| EmbedderConfigBuilder builder(GetEmbedderContext()); |
| builder.SetSoftwareRendererConfig(); |
| for (size_t i = 0; i < 3; ++i) { |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| FML_LOG(INFO) << "Engine launch count: " << i + 1; |
| } |
| } |
| |
| TEST_F(EmbedderTest, CanInvokeCustomEntrypoint) { |
| auto& context = GetEmbedderContext(); |
| static fml::AutoResetWaitableEvent latch; |
| Dart_NativeFunction entrypoint = [](Dart_NativeArguments args) { |
| latch.Signal(); |
| }; |
| context.AddNativeCallback("SayHiFromCustomEntrypoint", entrypoint); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("customEntrypoint"); |
| auto engine = builder.LaunchEngine(); |
| latch.Wait(); |
| ASSERT_TRUE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { |
| auto& context = GetEmbedderContext(); |
| |
| fml::AutoResetWaitableEvent latch1; |
| fml::AutoResetWaitableEvent latch2; |
| fml::AutoResetWaitableEvent latch3; |
| |
| // Can be defined separately. |
| auto entry1 = [&latch1](Dart_NativeArguments args) { |
| FML_LOG(INFO) << "In Callback 1"; |
| latch1.Signal(); |
| }; |
| auto native_entry1 = CREATE_NATIVE_ENTRY(entry1); |
| context.AddNativeCallback("SayHiFromCustomEntrypoint1", native_entry1); |
| |
| // Can be wrapped in in the args. |
| auto entry2 = [&latch2](Dart_NativeArguments args) { |
| FML_LOG(INFO) << "In Callback 2"; |
| latch2.Signal(); |
| }; |
| context.AddNativeCallback("SayHiFromCustomEntrypoint2", |
| CREATE_NATIVE_ENTRY(entry2)); |
| |
| // Everything can be inline. |
| context.AddNativeCallback( |
| "SayHiFromCustomEntrypoint3", |
| CREATE_NATIVE_ENTRY([&latch3](Dart_NativeArguments args) { |
| FML_LOG(INFO) << "In Callback 3"; |
| latch3.Signal(); |
| })); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("customEntrypoint1"); |
| auto engine = builder.LaunchEngine(); |
| latch1.Wait(); |
| latch2.Wait(); |
| latch3.Wait(); |
| ASSERT_TRUE(engine.is_valid()); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// @brief A task runner that we expect the embedder to provide but whose |
| /// implementation is a real FML task runner. |
| /// |
| class EmbedderTestTaskRunner { |
| public: |
| using TaskExpiryCallback = std::function<void(FlutterTask)>; |
| EmbedderTestTaskRunner(fml::RefPtr<fml::TaskRunner> real_task_runner, |
| TaskExpiryCallback on_task_expired) |
| : identifier_(++sEmbedderTaskRunnerIdentifiers), |
| real_task_runner_(real_task_runner), |
| on_task_expired_(on_task_expired) { |
| FML_CHECK(real_task_runner_); |
| FML_CHECK(on_task_expired_); |
| |
| task_runner_description_.struct_size = sizeof(FlutterTaskRunnerDescription); |
| task_runner_description_.user_data = this; |
| task_runner_description_.runs_task_on_current_thread_callback = |
| [](void* user_data) -> bool { |
| return reinterpret_cast<EmbedderTestTaskRunner*>(user_data) |
| ->real_task_runner_->RunsTasksOnCurrentThread(); |
| }; |
| task_runner_description_.post_task_callback = [](FlutterTask task, |
| uint64_t target_time_nanos, |
| void* user_data) -> void { |
| auto thiz = reinterpret_cast<EmbedderTestTaskRunner*>(user_data); |
| |
| auto target_time = fml::TimePoint::FromEpochDelta( |
| fml::TimeDelta::FromNanoseconds(target_time_nanos)); |
| auto on_task_expired = thiz->on_task_expired_; |
| auto invoke_task = [task, on_task_expired]() { on_task_expired(task); }; |
| auto real_task_runner = thiz->real_task_runner_; |
| |
| real_task_runner->PostTaskForTime(invoke_task, target_time); |
| }; |
| task_runner_description_.identifier = identifier_; |
| } |
| |
| const FlutterTaskRunnerDescription& GetFlutterTaskRunnerDescription() { |
| return task_runner_description_; |
| } |
| |
| private: |
| static std::atomic_size_t sEmbedderTaskRunnerIdentifiers; |
| const size_t identifier_; |
| fml::RefPtr<fml::TaskRunner> real_task_runner_; |
| TaskExpiryCallback on_task_expired_; |
| FlutterTaskRunnerDescription task_runner_description_ = {}; |
| |
| FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestTaskRunner); |
| }; |
| |
| std::atomic_size_t EmbedderTestTaskRunner::sEmbedderTaskRunnerIdentifiers = {}; |
| |
| TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) { |
| auto& context = GetEmbedderContext(); |
| fml::AutoResetWaitableEvent latch; |
| |
| // Run the test on its own thread with a message loop so that it can safely |
| // pump its event loop while we wait for all the conditions to be checked. |
| auto platform_task_runner = CreateNewThread("test_platform_thread"); |
| static std::mutex engine_mutex; |
| static bool signaled_once = false; |
| UniqueEngine engine; |
| |
| EmbedderTestTaskRunner test_task_runner( |
| platform_task_runner, [&](FlutterTask task) { |
| std::scoped_lock lock(engine_mutex); |
| if (!engine.is_valid()) { |
| return; |
| } |
| // There may be multiple tasks posted but we only need to check |
| // assertions once. |
| if (signaled_once) { |
| FlutterEngineRunTask(engine.get(), &task); |
| return; |
| } |
| |
| signaled_once = true; |
| ASSERT_TRUE(engine.is_valid()); |
| ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); |
| latch.Signal(); |
| }); |
| |
| platform_task_runner->PostTask([&]() { |
| EmbedderConfigBuilder builder(context); |
| const auto task_runner_description = |
| test_task_runner.GetFlutterTaskRunnerDescription(); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetPlatformTaskRunner(&task_runner_description); |
| builder.SetDartEntrypoint("invokePlatformTaskRunner"); |
| std::scoped_lock lock(engine_mutex); |
| engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| }); |
| |
| // Signaled when all the assertions are checked. |
| latch.Wait(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Since the engine was started on its own thread, it must be killed there as |
| // well. |
| fml::AutoResetWaitableEvent kill_latch; |
| platform_task_runner->PostTask(fml::MakeCopyable([&]() mutable { |
| std::scoped_lock lock(engine_mutex); |
| engine.reset(); |
| |
| // There may still be pending tasks on the platform thread that were queued |
| // by the test_task_runner. Signal the latch after these tasks have been |
| // consumed. |
| platform_task_runner->PostTask([&kill_latch] { kill_latch.Signal(); }); |
| })); |
| kill_latch.Wait(); |
| |
| ASSERT_TRUE(signaled_once); |
| signaled_once = false; |
| } |
| |
| TEST(EmbedderTestNoFixture, CanGetCurrentTimeInNanoseconds) { |
| auto point1 = fml::TimePoint::FromEpochDelta( |
| fml::TimeDelta::FromNanoseconds(FlutterEngineGetCurrentTime())); |
| auto point2 = fml::TimePoint::Now(); |
| |
| ASSERT_LT((point2 - point1), fml::TimeDelta::FromMilliseconds(1)); |
| } |
| |
| TEST_F(EmbedderTest, CanReloadSystemFonts) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| auto result = FlutterEngineReloadSystemFonts(engine.get()); |
| ASSERT_EQ(result, kSuccess); |
| } |
| |
| TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { |
| EmbedderConfigBuilder builder(GetEmbedderContext()); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, IsolateServiceIdSent) { |
| auto& context = GetEmbedderContext(); |
| fml::AutoResetWaitableEvent latch; |
| |
| fml::Thread thread; |
| UniqueEngine engine; |
| std::string isolate_message; |
| |
| thread.GetTaskRunner()->PostTask([&]() { |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("main"); |
| builder.SetPlatformMessageCallback( |
| [&](const FlutterPlatformMessage* message) { |
| if (strcmp(message->channel, "flutter/isolate") == 0) { |
| isolate_message = {reinterpret_cast<const char*>(message->message), |
| message->message_size}; |
| latch.Signal(); |
| } |
| }); |
| engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| }); |
| |
| // Wait for the isolate ID message and check its format. |
| latch.Wait(); |
| ASSERT_EQ(isolate_message.find("isolates/"), 0ul); |
| |
| // Since the engine was started on its own thread, it must be killed there as |
| // well. |
| fml::AutoResetWaitableEvent kill_latch; |
| thread.GetTaskRunner()->PostTask( |
| fml::MakeCopyable([&engine, &kill_latch]() mutable { |
| engine.reset(); |
| kill_latch.Signal(); |
| })); |
| kill_latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Creates a platform message response callbacks, does NOT send them, and |
| /// immediately collects the same. |
| /// |
| TEST_F(EmbedderTest, CanCreateAndCollectCallbacks) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("platform_messages_response"); |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([](Dart_NativeArguments args) {})); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterPlatformMessageResponseHandle* response_handle = nullptr; |
| auto callback = [](const uint8_t* data, size_t size, |
| void* user_data) -> void {}; |
| auto result = FlutterPlatformMessageCreateResponseHandle( |
| engine.get(), callback, nullptr, &response_handle); |
| ASSERT_EQ(result, kSuccess); |
| ASSERT_NE(response_handle, nullptr); |
| |
| result = FlutterPlatformMessageReleaseResponseHandle(engine.get(), |
| response_handle); |
| ASSERT_EQ(result, kSuccess); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Sends platform messages to Dart code than simply echoes the contents of the |
| /// message back to the embedder. The embedder registers a native callback to |
| /// intercept that message. |
| /// |
| TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { |
| struct Captures { |
| fml::AutoResetWaitableEvent latch; |
| std::thread::id thread_id; |
| }; |
| Captures captures; |
| |
| CreateNewThread()->PostTask([&]() { |
| captures.thread_id = std::this_thread::get_id(); |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("platform_messages_response"); |
| |
| fml::AutoResetWaitableEvent ready; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&ready](Dart_NativeArguments args) { ready.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| static std::string kMessageData = "Hello from embedder."; |
| |
| FlutterPlatformMessageResponseHandle* response_handle = nullptr; |
| auto callback = [](const uint8_t* data, size_t size, |
| void* user_data) -> void { |
| ASSERT_EQ(size, kMessageData.size()); |
| ASSERT_EQ(strncmp(reinterpret_cast<const char*>(kMessageData.data()), |
| reinterpret_cast<const char*>(data), size), |
| 0); |
| auto captures = reinterpret_cast<Captures*>(user_data); |
| ASSERT_EQ(captures->thread_id, std::this_thread::get_id()); |
| captures->latch.Signal(); |
| }; |
| auto result = FlutterPlatformMessageCreateResponseHandle( |
| engine.get(), callback, &captures, &response_handle); |
| ASSERT_EQ(result, kSuccess); |
| |
| FlutterPlatformMessage message = {}; |
| message.struct_size = sizeof(FlutterPlatformMessage); |
| message.channel = "test_channel"; |
| message.message = reinterpret_cast<const uint8_t*>(kMessageData.data()); |
| message.message_size = kMessageData.size(); |
| message.response_handle = response_handle; |
| |
| ready.Wait(); |
| result = FlutterEngineSendPlatformMessage(engine.get(), &message); |
| ASSERT_EQ(result, kSuccess); |
| |
| result = FlutterPlatformMessageReleaseResponseHandle(engine.get(), |
| response_handle); |
| ASSERT_EQ(result, kSuccess); |
| }); |
| |
| captures.latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Tests that a platform message can be sent with no response handle. Instead |
| /// of the platform message integrity checked via a response handle, a native |
| /// callback with the response is invoked to assert integrity. |
| /// |
| TEST_F(EmbedderTest, PlatformMessagesCanBeSentWithoutResponseHandles) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("platform_messages_no_response"); |
| |
| const std::string message_data = "Hello but don't call me back."; |
| |
| fml::AutoResetWaitableEvent ready, message; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&ready](Dart_NativeArguments args) { ready.Signal(); })); |
| context.AddNativeCallback( |
| "SignalNativeMessage", |
| CREATE_NATIVE_ENTRY( |
| ([&message, &message_data](Dart_NativeArguments args) { |
| auto received_message = tonic::DartConverter<std::string>::FromDart( |
| Dart_GetNativeArgument(args, 0)); |
| ASSERT_EQ(received_message, message_data); |
| message.Signal(); |
| }))); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| ready.Wait(); |
| |
| FlutterPlatformMessage platform_message = {}; |
| platform_message.struct_size = sizeof(FlutterPlatformMessage); |
| platform_message.channel = "test_channel"; |
| platform_message.message = |
| reinterpret_cast<const uint8_t*>(message_data.data()); |
| platform_message.message_size = message_data.size(); |
| platform_message.response_handle = nullptr; // No response needed. |
| |
| auto result = |
| FlutterEngineSendPlatformMessage(engine.get(), &platform_message); |
| ASSERT_EQ(result, kSuccess); |
| message.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Tests that a null platform message can be sent. |
| /// |
| TEST_F(EmbedderTest, NullPlatformMessagesCanBeSent) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("null_platform_messages"); |
| |
| fml::AutoResetWaitableEvent ready, message; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&ready](Dart_NativeArguments args) { ready.Signal(); })); |
| context.AddNativeCallback( |
| "SignalNativeMessage", |
| CREATE_NATIVE_ENTRY(([&message](Dart_NativeArguments args) { |
| auto received_message = tonic::DartConverter<std::string>::FromDart( |
| Dart_GetNativeArgument(args, 0)); |
| ASSERT_EQ("true", received_message); |
| message.Signal(); |
| }))); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| ready.Wait(); |
| |
| FlutterPlatformMessage platform_message = {}; |
| platform_message.struct_size = sizeof(FlutterPlatformMessage); |
| platform_message.channel = "test_channel"; |
| platform_message.message = nullptr; |
| platform_message.message_size = 0; |
| platform_message.response_handle = nullptr; // No response needed. |
| |
| auto result = |
| FlutterEngineSendPlatformMessage(engine.get(), &platform_message); |
| ASSERT_EQ(result, kSuccess); |
| message.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Tests that a null platform message cannot be send if the message_size |
| /// isn't equals to 0. |
| /// |
| TEST_F(EmbedderTest, InvalidPlatformMessages) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterPlatformMessage platform_message = {}; |
| platform_message.struct_size = sizeof(FlutterPlatformMessage); |
| platform_message.channel = "test_channel"; |
| platform_message.message = nullptr; |
| platform_message.message_size = 1; |
| platform_message.response_handle = nullptr; // No response needed. |
| |
| auto result = |
| FlutterEngineSendPlatformMessage(engine.get(), &platform_message); |
| ASSERT_EQ(result, kInvalidArguments); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Asserts behavior of FlutterProjectArgs::shutdown_dart_vm_when_done (which is |
| /// set to true by default in these unit-tests). |
| /// |
| TEST_F(EmbedderTest, VMShutsDownWhenNoEnginesInProcess) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| const auto launch_count = DartVM::GetVMLaunchCount(); |
| |
| { |
| auto engine = builder.LaunchEngine(); |
| ASSERT_EQ(launch_count + 1u, DartVM::GetVMLaunchCount()); |
| } |
| |
| { |
| auto engine = builder.LaunchEngine(); |
| ASSERT_EQ(launch_count + 2u, DartVM::GetVMLaunchCount()); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// These snapshots may be materialized from symbols and the size field may not |
| /// be relevant. Since this information is redundant, engine launch should not |
| /// be gated on a non-zero buffer size. |
| /// |
| TEST_F(EmbedderTest, VMAndIsolateSnapshotSizesAreRedundantInAOTMode) { |
| if (!DartVM::IsRunningPrecompiledCode()) { |
| GTEST_SKIP(); |
| return; |
| } |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| |
| // The fixture sets this up correctly. Intentionally mess up the args. |
| builder.GetProjectArgs().vm_snapshot_data_size = 0; |
| builder.GetProjectArgs().vm_snapshot_instructions_size = 0; |
| builder.GetProjectArgs().isolate_snapshot_data_size = 0; |
| builder.GetProjectArgs().isolate_snapshot_instructions_size = 0; |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// If an incorrectly configured compositor is set on the engine, the engine |
| /// must fail to launch instead of failing to render a frame at a later point in |
| /// time. |
| /// |
| TEST_F(EmbedderTest, |
| MustPreventEngineLaunchWhenRequiredCompositorArgsAreAbsent) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); |
| builder.SetCompositor(); |
| builder.GetCompositor().create_backing_store_callback = nullptr; |
| builder.GetCompositor().collect_backing_store_callback = nullptr; |
| builder.GetCompositor().present_layers_callback = nullptr; |
| auto engine = builder.LaunchEngine(); |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Must be able to render to a custom compositor whose render targets are fully |
| /// complete OpenGL textures. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLFramebuffer); |
| |
| fml::CountDownLatch latch(3); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Layers in a hierarchy containing a platform view should not be cached. The |
| /// other layers in the hierarchy should be, however. |
| TEST_F(EmbedderTest, RasterCacheDisabledWithPlatformViews) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views_with_opacity"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLFramebuffer); |
| |
| fml::CountDownLatch setup(3); |
| fml::CountDownLatch verify(1); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| setup.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&setup](Dart_NativeArguments args) { setup.CountDown(); })); |
| |
| UniqueEngine engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| setup.Wait(); |
| const flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); |
| shell.GetTaskRunners().GetRasterTaskRunner()->PostTask([&] { |
| const flutter::RasterCache& raster_cache = |
| shell.GetRasterizer()->compositor_context()->raster_cache(); |
| // 3 layers total, but one of them had the platform view. So the cache |
| // should only have 2 entries. |
| ASSERT_EQ(raster_cache.GetCachedEntriesCount(), 2u); |
| verify.CountDown(); |
| }); |
| |
| verify.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// The RasterCache should normally be enabled. |
| /// |
| TEST_F(EmbedderTest, RasterCacheEnabled) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_with_opacity"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLFramebuffer); |
| |
| fml::CountDownLatch setup(3); |
| fml::CountDownLatch verify(1); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 1u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| setup.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&setup](Dart_NativeArguments args) { setup.CountDown(); })); |
| |
| UniqueEngine engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| setup.Wait(); |
| const flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); |
| shell.GetTaskRunners().GetRasterTaskRunner()->PostTask([&] { |
| const flutter::RasterCache& raster_cache = |
| shell.GetRasterizer()->compositor_context()->raster_cache(); |
| ASSERT_EQ(raster_cache.GetCachedEntriesCount(), 1u); |
| verify.CountDown(); |
| }); |
| |
| verify.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Must be able to render using a custom compositor whose render targets for |
| /// the individual layers are OpenGL textures. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(3); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Must be able to render using a custom compositor whose render target for the |
| /// individual layers are software buffers. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kSoftwareBuffer); |
| |
| fml::CountDownLatch latch(3); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| ASSERT_FLOAT_EQ( |
| backing_store.software.row_bytes * backing_store.software.height, |
| 800 * 4 * 600.0); |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| static sk_sp<SkSurface> CreateRenderSurface(const FlutterLayer& layer, |
| GrContext* context) { |
| const auto image_info = |
| SkImageInfo::MakeN32Premul(layer.size.width, layer.size.height); |
| auto surface = context ? SkSurface::MakeRenderTarget( |
| context, // context |
| SkBudgeted::kNo, // budgeted |
| image_info, // image info |
| 1, // sample count |
| kTopLeft_GrSurfaceOrigin, // surface origin |
| nullptr, // surface properties |
| false // mipmaps |
| |
| ) |
| : SkSurface::MakeRaster(image_info); |
| FML_CHECK(surface != nullptr); |
| return surface; |
| } |
| |
| static bool RasterImagesAreSame(sk_sp<SkImage> a, sk_sp<SkImage> b) { |
| FML_CHECK(!a->isTextureBacked()); |
| FML_CHECK(!b->isTextureBacked()); |
| |
| if (!a || !b) { |
| return false; |
| } |
| |
| SkPixmap pixmapA; |
| SkPixmap pixmapB; |
| |
| if (!a->peekPixels(&pixmapA)) { |
| FML_LOG(ERROR) << "Could not peek pixels of image A."; |
| return false; |
| } |
| |
| if (!b->peekPixels(&pixmapB)) { |
| FML_LOG(ERROR) << "Could not peek pixels of image B."; |
| |
| return false; |
| } |
| |
| const auto sizeA = pixmapA.rowBytes() * pixmapA.height(); |
| const auto sizeB = pixmapB.rowBytes() * pixmapB.height(); |
| |
| if (sizeA != sizeB) { |
| FML_LOG(ERROR) << "Pixmap sizes were inconsistent."; |
| return false; |
| } |
| |
| return ::memcmp(pixmapA.addr(), pixmapB.addr(), sizeA) == 0; |
| } |
| |
| static bool WriteImageToDisk(const fml::UniqueFD& directory, |
| const std::string& name, |
| sk_sp<SkImage> image) { |
| if (!image) { |
| return false; |
| } |
| |
| auto data = image->encodeToData(SkEncodedImageFormat::kPNG, 100); |
| |
| if (!data) { |
| return false; |
| } |
| |
| fml::NonOwnedMapping mapping(static_cast<const uint8_t*>(data->data()), |
| data->size()); |
| return WriteAtomically(directory, name.c_str(), mapping); |
| } |
| |
| static bool ImageMatchesFixture(const std::string& fixture_file_name, |
| sk_sp<SkImage> scene_image) { |
| fml::FileMapping fixture_image_mapping(OpenFixture(fixture_file_name)); |
| |
| FML_CHECK(fixture_image_mapping.GetSize() != 0u) |
| << "Could not find fixture: " << fixture_file_name; |
| |
| auto encoded_image = SkData::MakeWithoutCopy( |
| fixture_image_mapping.GetMapping(), fixture_image_mapping.GetSize()); |
| auto fixture_image = |
| SkImage::MakeFromEncoded(std::move(encoded_image))->makeRasterImage(); |
| |
| FML_CHECK(fixture_image) << "Could not create image from fixture: " |
| << fixture_file_name; |
| |
| auto scene_image_subset = scene_image->makeSubset( |
| SkIRect::MakeWH(fixture_image->width(), fixture_image->height())); |
| |
| FML_CHECK(scene_image_subset) |
| << "Could not create image subset for fixture comparison: " |
| << scene_image_subset; |
| |
| const auto images_are_same = |
| RasterImagesAreSame(scene_image_subset, fixture_image); |
| |
| // If the images are not the same, this predicate is going to indicate test |
| // failure. Dump both the actual image and the expectation to disk to the |
| // test author can figure out what went wrong. |
| if (!images_are_same) { |
| const auto fixtures_path = GetFixturesPath(); |
| |
| const auto actual_file_name = "actual_" + fixture_file_name; |
| const auto expect_file_name = "expectation_" + fixture_file_name; |
| |
| auto fixtures_fd = OpenFixturesDirectory(); |
| |
| FML_CHECK( |
| WriteImageToDisk(fixtures_fd, actual_file_name, scene_image_subset)) |
| << "Could not write file to disk: " << actual_file_name; |
| |
| FML_CHECK(WriteImageToDisk(fixtures_fd, expect_file_name, fixture_image)) |
| << "Could not write file to disk: " << expect_file_name; |
| |
| FML_LOG(ERROR) << "Image did not match expectation." << std::endl |
| << "Expected:" |
| << fml::paths::JoinPaths({fixtures_path, expect_file_name}) |
| << std::endl |
| << "Got:" |
| << fml::paths::JoinPaths({fixtures_path, actual_file_name}) |
| << std::endl; |
| } |
| return images_are_same; |
| } |
| |
| static bool ImageMatchesFixture(const std::string& fixture_file_name, |
| std::future<sk_sp<SkImage>>& scene_image) { |
| return ImageMatchesFixture(fixture_file_name, scene_image.get()); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom compositor. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(5); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 5u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(50.0, 150.0); |
| layer.offset = FlutterPointMake(20.0, 20.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| // Layer 3 |
| { |
| FlutterPlatformView platform_view = *layers[3]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 2; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(50.0, 150.0); |
| layer.offset = FlutterPointMake(40.0, 40.0); |
| |
| ASSERT_EQ(*layers[3], layer); |
| } |
| |
| // Layer 4 |
| { |
| FlutterBackingStore backing_store = *layers[4]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[4], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, GrContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorGREEN); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| case 2: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorMAGENTA); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture("compositor.png", scene_image)); |
| |
| // There should no present calls on the root surface. |
| ASSERT_EQ(context.GetSoftwareSurfacePresentCount(), 0u); |
| ASSERT_EQ(context.GetGLSurfacePresentCount(), 0u); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom software |
| /// compositor. |
| /// |
| TEST_F(EmbedderTest, |
| CompositorMustBeAbleToRenderKnownSceneWithSoftwareCompositor) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kSoftwareBuffer); |
| |
| fml::CountDownLatch latch(5); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 5u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| backing_store.software.height = 600; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(50.0, 150.0); |
| layer.offset = FlutterPointMake(20.0, 20.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| backing_store.software.height = 600; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| // Layer 3 |
| { |
| FlutterPlatformView platform_view = *layers[3]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 2; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(50.0, 150.0); |
| layer.offset = FlutterPointMake(40.0, 40.0); |
| |
| ASSERT_EQ(*layers[3], layer); |
| } |
| |
| // Layer 4 |
| { |
| FlutterBackingStore backing_store = *layers[4]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| backing_store.software.height = 600; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[4], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, GrContext* |
| /* don't use because software compositor */) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface( |
| layer, nullptr /* null because software compositor */); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorGREEN); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| case 2: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorMAGENTA); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture("compositor_software.png", scene_image)); |
| |
| // There should no present calls on the root surface. |
| ASSERT_EQ(context.GetSoftwareSurfacePresentCount(), 0u); |
| ASSERT_EQ(context.GetGLSurfacePresentCount(), 0u); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Custom compositor must play nicely with a custom task runner. The raster |
| /// thread merging mechanism must not interfere with the custom compositor. |
| /// |
| TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views"); |
| |
| auto platform_task_runner = CreateNewThread("test_platform_thread"); |
| static std::mutex engine_mutex; |
| UniqueEngine engine; |
| fml::AutoResetWaitableEvent sync_latch; |
| |
| EmbedderTestTaskRunner test_task_runner( |
| platform_task_runner, [&](FlutterTask task) { |
| std::scoped_lock lock(engine_mutex); |
| if (!engine.is_valid()) { |
| return; |
| } |
| ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); |
| }); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(3); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| const auto task_runner_description = |
| test_task_runner.GetFlutterTaskRunnerDescription(); |
| |
| builder.SetPlatformTaskRunner(&task_runner_description); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| platform_task_runner->PostTask([&]() { |
| std::scoped_lock lock(engine_mutex); |
| engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| sync_latch.Signal(); |
| }); |
| sync_latch.Wait(); |
| |
| latch.Wait(); |
| |
| platform_task_runner->PostTask([&]() { |
| std::scoped_lock lock(engine_mutex); |
| engine.reset(); |
| sync_latch.Signal(); |
| }); |
| sync_latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom compositor |
| /// and a single layer. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint( |
| "can_composite_platform_views_with_root_layer_only"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(3); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 1u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE( |
| ImageMatchesFixture("compositor_with_root_layer_only.png", scene_image)); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom compositor |
| /// and ensure that a redundant layer is not added. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint( |
| "can_composite_platform_views_with_platform_layer_on_bottom"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(3); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(50.0, 150.0); |
| layer.offset = FlutterPointMake(20.0, 20.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, GrContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorGREEN); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture( |
| "compositor_with_platform_layer_on_bottom.png", scene_image)); |
| |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 1u); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom compositor |
| /// with a root surface transformation. |
| /// |
| TEST_F(EmbedderTest, |
| CompositorMustBeAbleToRenderKnownSceneWithRootSurfaceTransformation) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| // This must match the transformation provided in the |
| // |CanRenderGradientWithoutCompositorWithXform| test to ensure that |
| // transforms are consistent respected. |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| fml::CountDownLatch latch(5); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 5u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(150.0, 50.0); |
| layer.offset = FlutterPointMake(20.0, 730.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| // Layer 3 |
| { |
| FlutterPlatformView platform_view = *layers[3]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 2; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(150.0, 50.0); |
| layer.offset = FlutterPointMake(40.0, 710.0); |
| |
| ASSERT_EQ(*layers[3], layer); |
| } |
| |
| // Layer 4 |
| { |
| FlutterBackingStore backing_store = *layers[4]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[4], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, GrContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorGREEN); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| case 2: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorMAGENTA); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| // Flutter still thinks it is 800 x 600. Only the root surface is rotated. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture("compositor_root_surface_xformation.png", |
| scene_image)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositor) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("scene_without_custom_compositor.png", |
| renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { |
| auto& context = GetEmbedderContext(); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| |
| // Flutter still thinks it is 800 x 600. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture( |
| "scene_without_custom_compositor_with_xform.png", renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithoutCompositor) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient.png", renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { |
| auto& context = GetEmbedderContext(); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| const auto surface_size = SkISize::Make(600, 800); |
| |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.SetOpenGLRendererConfig(surface_size); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| // Flutter still thinks it is 800 x 600. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithCompositor) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient.png", renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { |
| auto& context = GetEmbedderContext(); |
| |
| // This must match the transformation provided in the |
| // |CanRenderGradientWithoutCompositorWithXform| test to ensure that |
| // transforms are consistent respected. |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| builder.SetCompositor(); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| // Flutter still thinks it is 800 x 600. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayer) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient_on_non_root_backing_store"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(100.0, 200.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, GrContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| FML_CHECK(layer.size.width == 100); |
| FML_CHECK(layer.size.height == 200); |
| // This is occluded anyway. We just want to make sure we see this. |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient.png", renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { |
| auto& context = GetEmbedderContext(); |
| |
| // This must match the transformation provided in the |
| // |CanRenderGradientWithoutCompositorWithXform| test to ensure that |
| // transforms are consistent respected. |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient_on_non_root_backing_store"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| builder.SetCompositor(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(200.0, 100.0); |
| layer.offset = FlutterPointMake(0.0, 700.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, GrContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| FML_CHECK(layer.size.width == 200); |
| FML_CHECK(layer.size.height == 100); |
| // This is occluded anyway. We just want to make sure we see this. |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| // Flutter still thinks it is 800 x 600. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, VerifyB141980393) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| // The Flutter application is 800 x 600 but rendering on a surface that is 600 |
| // x 800 achieved using a root surface transformation. |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| const auto flutter_application_rect = SkRect::MakeWH(800, 600); |
| const auto root_surface_rect = |
| root_surface_transformation.mapRect(flutter_application_rect); |
| |
| ASSERT_DOUBLE_EQ(root_surface_rect.width(), 600.0); |
| ASSERT_DOUBLE_EQ(root_surface_rect.height(), 800.0); |
| |
| // Configure the fixture for the surface transformation. |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| // Configure the Flutter project args for the root surface transformation. |
| builder.SetOpenGLRendererConfig( |
| SkISize::Make(root_surface_rect.width(), root_surface_rect.height())); |
| |
| // Use a compositor instead of rendering directly to the surface. |
| builder.SetCompositor(); |
| |
| builder.SetDartEntrypoint("verify_b141980393"); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 1u); |
| |
| // Layer Root |
| { |
| FlutterPlatformView platform_view = *layers[0]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1337; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| |
| // From the Dart side. These dimensions match those specified in Dart |
| // code and are free of root surface transformations. |
| const double unxformed_top_margin = 31.0; |
| const double unxformed_bottom_margin = 37.0; |
| const auto unxformed_platform_view_rect = SkRect::MakeXYWH( |
| 0.0, // x |
| unxformed_top_margin, // y (top margin) |
| 800, // width |
| 600 - unxformed_top_margin - unxformed_bottom_margin // height |
| ); |
| |
| // The platform views are in the coordinate space of the root surface |
| // with top-left origin. The embedder has specified a transformation |
| // to this surface which it must account for in the coordinates it |
| // receives here. |
| const auto xformed_platform_view_rect = |
| root_surface_transformation.mapRect(unxformed_platform_view_rect); |
| |
| // Spell out the value that we are going to be checking below for |
| // clarity. |
| ASSERT_EQ(xformed_platform_view_rect, |
| SkRect::MakeXYWH(31.0, // x |
| 0.0, // y |
| 532.0, // width |
| 800.0 // height |
| )); |
| |
| // Verify that the engine is giving us the right size and offset. |
| layer.offset = FlutterPointMake(xformed_platform_view_rect.x(), |
| xformed_platform_view_rect.y()); |
| layer.size = FlutterSizeMake(xformed_platform_view_rect.width(), |
| xformed_platform_view_rect.height()); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| |
| // The Flutter application is 800 x 600 rendering on a surface 600 x 800 |
| // achieved via a root surface transformation. |
| event.width = flutter_application_rect.width(); |
| event.height = flutter_application_rect.height(); |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test that an engine can be initialized but not run. |
| /// |
| TEST_F(EmbedderTest, CanCreateInitializedEngine) { |
| EmbedderConfigBuilder builder(GetEmbedderContext()); |
| builder.SetSoftwareRendererConfig(); |
| auto engine = builder.InitializeEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| engine.reset(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test that an initialized engine can be run exactly once. |
| /// |
| TEST_F(EmbedderTest, CanRunInitializedEngine) { |
| EmbedderConfigBuilder builder(GetEmbedderContext()); |
| builder.SetSoftwareRendererConfig(); |
| auto engine = builder.InitializeEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); |
| // Cannot re-run an already running engine. |
| ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kInvalidArguments); |
| engine.reset(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test that an engine can be deinitialized. |
| /// |
| TEST_F(EmbedderTest, CaDeinitializeAnEngine) { |
| EmbedderConfigBuilder builder(GetEmbedderContext()); |
| builder.SetSoftwareRendererConfig(); |
| auto engine = builder.InitializeEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); |
| // Cannot re-run an already running engine. |
| ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kInvalidArguments); |
| ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); |
| // It is ok to deinitialize an engine multiple times. |
| ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); |
| |
| // Sending events to a deinitalized engine fails. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kInvalidArguments); |
| engine.reset(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Asserts that embedders can provide a task runner for the render thread. |
| /// |
| TEST_F(EmbedderTest, CanCreateEmbedderWithCustomRenderTaskRunner) { |
| std::mutex engine_mutex; |
| UniqueEngine engine; |
| fml::AutoResetWaitableEvent task_latch; |
| bool task_executed = false; |
| EmbedderTestTaskRunner render_task_runner( |
| CreateNewThread("custom_render_thread"), [&](FlutterTask task) { |
| std::scoped_lock engine_lock(engine_mutex); |
| if (engine.is_valid()) { |
| ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); |
| task_executed = true; |
| task_latch.Signal(); |
| } |
| }); |
| EmbedderConfigBuilder builder(GetEmbedderContext()); |
| builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetRenderTaskRunner( |
| &render_task_runner.GetFlutterTaskRunnerDescription()); |
| |
| { |
| std::scoped_lock lock(engine_mutex); |
| engine = builder.InitializeEngine(); |
| } |
| |
| ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| task_latch.Wait(); |
| ASSERT_TRUE(task_executed); |
| ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); |
| |
| { |
| std::scoped_lock engine_lock(engine_mutex); |
| engine.reset(); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Asserts that the render task runner can be the same as the platform task |
| /// runner. |
| /// |
| TEST_F(EmbedderTest, |
| CanCreateEmbedderWithCustomRenderTaskRunnerTheSameAsPlatformTaskRunner) { |
| // A new thread needs to be created for the platform thread because the test |
| // can't wait for assertions to be completed on the same thread that services |
| // platform task runner tasks. |
| auto platform_task_runner = CreateNewThread("platform_thread"); |
| |
| static std::mutex engine_mutex; |
| static UniqueEngine engine; |
| fml::AutoResetWaitableEvent task_latch; |
| bool task_executed = false; |
| EmbedderTestTaskRunner common_task_runner( |
| platform_task_runner, [&](FlutterTask task) { |
| std::scoped_lock engine_lock(engine_mutex); |
| if (engine.is_valid()) { |
| ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); |
| task_executed = true; |
| task_latch.Signal(); |
| } |
| }); |
| |
| platform_task_runner->PostTask([&]() { |
| EmbedderConfigBuilder builder(GetEmbedderContext()); |
| builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetRenderTaskRunner( |
| &common_task_runner.GetFlutterTaskRunnerDescription()); |
| builder.SetPlatformTaskRunner( |
| &common_task_runner.GetFlutterTaskRunnerDescription()); |
| |
| { |
| std::scoped_lock lock(engine_mutex); |
| engine = builder.InitializeEngine(); |
| } |
| |
| ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| }); |
| |
| task_latch.Wait(); |
| |
| // Don't use the task latch because that may be called multiple time |
| // (including during the shutdown process). |
| fml::AutoResetWaitableEvent shutdown_latch; |
| |
| platform_task_runner->PostTask([&]() { |
| ASSERT_TRUE(task_executed); |
| ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); |
| |
| { |
| std::scoped_lock engine_lock(engine_mutex); |
| engine.reset(); |
| } |
| shutdown_latch.Signal(); |
| }); |
| |
| shutdown_latch.Wait(); |
| |
| { |
| std::scoped_lock engine_lock(engine_mutex); |
| // Engine should have been killed by this point. |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| } |
| |
| TEST_F(EmbedderTest, |
| CompositorMustBeAbleToRenderKnownScenePixelRatioOnSurface) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_display_platform_view_with_pixel_ratio"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(1); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(800.0, 560.0); |
| layer.offset = FlutterPointMake(0.0, 40.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 400 * 2.0; |
| event.height = 300 * 2.0; |
| event.pixel_ratio = 2.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture("dpr_noxform.png", renderered_scene)); |
| } |
| |
| TEST_F( |
| EmbedderTest, |
| CompositorMustBeAbleToRenderKnownScenePixelRatioOnSurfaceWithRootSurfaceXformation) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_display_platform_view_with_pixel_ratio"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| fml::CountDownLatch latch(1); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(560.0, 800.0); |
| layer.offset = FlutterPointMake(40.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 400 * 2.0; |
| event.height = 300 * 2.0; |
| event.pixel_ratio = 2.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture("dpr_xform.png", renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanUpdateLocales) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("can_receive_locale_updates"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| fml::AutoResetWaitableEvent check_latch; |
| context.AddNativeCallback( |
| "SignalNativeCount", |
| CREATE_NATIVE_ENTRY([&check_latch](Dart_NativeArguments args) { |
| ASSERT_EQ(tonic::DartConverter<int>::FromDart( |
| Dart_GetNativeArgument(args, 0)), |
| 2); |
| check_latch.Signal(); |
| })); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Wait for the application to attach the listener. |
| latch.Wait(); |
| |
| FlutterLocale locale1 = {}; |
| locale1.struct_size = sizeof(locale1); |
| locale1.language_code = ""; // invalid |
| locale1.country_code = "US"; |
| locale1.script_code = ""; |
| locale1.variant_code = nullptr; |
| |
| FlutterLocale locale2 = {}; |
| locale2.struct_size = sizeof(locale2); |
| locale2.language_code = "zh"; |
| locale2.country_code = "CN"; |
| locale2.script_code = "Hans"; |
| locale2.variant_code = nullptr; |
| |
| std::vector<const FlutterLocale*> locales; |
| locales.push_back(&locale1); |
| locales.push_back(&locale2); |
| |
| ASSERT_EQ( |
| FlutterEngineUpdateLocales(engine.get(), locales.data(), locales.size()), |
| kInvalidArguments); |
| |
| // Fix the invalid code. |
| locale1.language_code = "en"; |
| |
| ASSERT_EQ( |
| FlutterEngineUpdateLocales(engine.get(), locales.data(), locales.size()), |
| kSuccess); |
| |
| check_latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, LocalizationCallbacksCalled) { |
| auto& context = GetEmbedderContext(); |
| fml::AutoResetWaitableEvent latch; |
| context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| // Wait for the root isolate to launch. |
| latch.Wait(); |
| |
| flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); |
| std::vector<std::string> supported_locales; |
| supported_locales.push_back("es"); |
| supported_locales.push_back("MX"); |
| supported_locales.push_back(""); |
| auto result = shell.GetPlatformView()->ComputePlatformResolvedLocales( |
| supported_locales); |
| |
| ASSERT_EQ((*result).size(), supported_locales.size()); // 3 |
| ASSERT_EQ((*result)[0], supported_locales[0]); |
| ASSERT_EQ((*result)[1], supported_locales[1]); |
| ASSERT_EQ((*result)[2], supported_locales[2]); |
| |
| engine.reset(); |
| } |
| |
| TEST_F(EmbedderTest, CanQueryDartAOTMode) { |
| ASSERT_EQ(FlutterEngineRunsAOTCompiledDartCode(), |
| flutter::DartVM::IsRunningPrecompiledCode()); |
| } |
| |
| TEST_F(EmbedderTest, VerifyB143464703WithSoftwareBackend) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(SkISize::Make(1024, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("verify_b143464703"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kSoftwareBuffer); |
| |
| fml::CountDownLatch latch(1); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(1024.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(1024.0, 540.0); |
| layer.offset = FlutterPointMake(135.0, 60.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(1024.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [](const FlutterLayer& layer, GrContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface( |
| layer, nullptr /* null because software compositor */); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 42: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorGREEN); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| auto renderered_scene = context.GetNextSceneImage(); |
| |
| latch.Wait(); |
| |
| // TODO(https://github.com/flutter/flutter/issues/53784): enable this on all |
| // platforms. |
| #if !defined(OS_LINUX) |
| GTEST_SKIP() << "Skipping golden tests on non-Linux OSes"; |
| #endif // OS_LINUX |
| ASSERT_TRUE(ImageMatchesFixture("verifyb143464703_soft_noxform.png", |
| renderered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, |
| PushingMutlipleFramesSetsUpNewRecordingCanvasWithCustomCompositor) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("push_frames_over_and_over"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| constexpr size_t frames_expected = 10; |
| fml::CountDownLatch frame_latch(frames_expected); |
| size_t frames_seen = 0; |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| frames_seen++; |
| frame_latch.CountDown(); |
| })); |
| frame_latch.Wait(); |
| |
| ASSERT_EQ(frames_expected, frames_seen); |
| } |
| |
| TEST_F(EmbedderTest, |
| PushingMutlipleFramesSetsUpNewRecordingCanvasWithoutCustomCompositor) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); |
| builder.SetDartEntrypoint("push_frames_over_and_over"); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| constexpr size_t frames_expected = 10; |
| fml::CountDownLatch frame_latch(frames_expected); |
| size_t frames_seen = 0; |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| frames_seen++; |
| frame_latch.CountDown(); |
| })); |
| frame_latch.Wait(); |
| |
| ASSERT_EQ(frames_expected, frames_seen); |
| } |
| |
| TEST_F(EmbedderTest, PlatformViewMutatorsAreValid) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("platform_view_mutators"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(1); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| platform_view.mutations_count = 3; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| |
| // There are no ordering guarantees. |
| for (size_t i = 0; i < platform_view.mutations_count; i++) { |
| FlutterPlatformViewMutation mutation = *platform_view.mutations[i]; |
| switch (mutation.type) { |
| case kFlutterPlatformViewMutationTypeClipRoundedRect: |
| mutation.clip_rounded_rect = |
| FlutterRoundedRectMake(SkRRect::MakeRectXY( |
| SkRect::MakeLTRB(10.0, 10.0, 800.0 - 10.0, |
| 600.0 - 10.0), |
| 14.0, 14.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeClipRect: |
| mutation.type = kFlutterPlatformViewMutationTypeClipRect; |
| mutation.clip_rect = FlutterRectMake( |
| SkRect::MakeXYWH(10.0, 10.0, 800.0 - 20.0, 600.0 - 20.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeOpacity: |
| mutation.type = kFlutterPlatformViewMutationTypeOpacity; |
| mutation.opacity = 128.0 / 255.0; |
| break; |
| case kFlutterPlatformViewMutationTypeTransformation: |
| FML_CHECK(false) |
| << "There should be no transformation in the test."; |
| break; |
| } |
| |
| ASSERT_EQ(*platform_view.mutations[i], mutation); |
| } |
| } |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, PlatformViewMutatorsAreValidWithPixelRatio) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("platform_view_mutators_with_pixel_ratio"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(1); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| platform_view.mutations_count = 3; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| |
| // There are no ordering guarantees. |
| for (size_t i = 0; i < platform_view.mutations_count; i++) { |
| FlutterPlatformViewMutation mutation = *platform_view.mutations[i]; |
| switch (mutation.type) { |
| case kFlutterPlatformViewMutationTypeClipRoundedRect: |
| mutation.clip_rounded_rect = |
| FlutterRoundedRectMake(SkRRect::MakeRectXY( |
| SkRect::MakeLTRB(5.0, 5.0, 400.0 - 5.0, 300.0 - 5.0), |
| 7.0, 7.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeClipRect: |
| mutation.type = kFlutterPlatformViewMutationTypeClipRect; |
| mutation.clip_rect = FlutterRectMake( |
| SkRect::MakeXYWH(5.0, 5.0, 400.0 - 10.0, 300.0 - 10.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeOpacity: |
| mutation.type = kFlutterPlatformViewMutationTypeOpacity; |
| mutation.opacity = 128.0 / 255.0; |
| break; |
| case kFlutterPlatformViewMutationTypeTransformation: |
| mutation.type = kFlutterPlatformViewMutationTypeTransformation; |
| mutation.transformation = |
| FlutterTransformationMake(SkMatrix::Scale(2.0, 2.0)); |
| break; |
| } |
| |
| ASSERT_EQ(*platform_view.mutations[i], mutation); |
| } |
| } |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 2.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, |
| PlatformViewMutatorsAreValidWithPixelRatioAndRootSurfaceTransformation) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("platform_view_mutators_with_pixel_ratio"); |
| |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| static const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| fml::CountDownLatch latch(1); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| platform_view.mutations_count = 4; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| |
| // There are no ordering guarantees. |
| for (size_t i = 0; i < platform_view.mutations_count; i++) { |
| FlutterPlatformViewMutation mutation = *platform_view.mutations[i]; |
| switch (mutation.type) { |
| case kFlutterPlatformViewMutationTypeClipRoundedRect: |
| mutation.clip_rounded_rect = |
| FlutterRoundedRectMake(SkRRect::MakeRectXY( |
| SkRect::MakeLTRB(5.0, 5.0, 400.0 - 5.0, 300.0 - 5.0), |
| 7.0, 7.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeClipRect: |
| mutation.type = kFlutterPlatformViewMutationTypeClipRect; |
| mutation.clip_rect = FlutterRectMake( |
| SkRect::MakeXYWH(5.0, 5.0, 400.0 - 10.0, 300.0 - 10.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeOpacity: |
| mutation.type = kFlutterPlatformViewMutationTypeOpacity; |
| mutation.opacity = 128.0 / 255.0; |
| break; |
| case kFlutterPlatformViewMutationTypeTransformation: |
| mutation.type = kFlutterPlatformViewMutationTypeTransformation; |
| mutation.transformation = |
| FlutterTransformationMake(root_surface_transformation); |
| |
| break; |
| } |
| |
| ASSERT_EQ(*platform_view.mutations[i], mutation); |
| } |
| } |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 2.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, EmptySceneIsAcceptable) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, SceneWithNoRootContainerIsAcceptable) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("scene_with_no_container"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| latch.Wait(); |
| } |
| |
| // Verifies that https://skia-review.googlesource.com/c/skia/+/259174 is pulled |
| // into the engine. |
| TEST_F(EmbedderTest, ArcEndCapsAreDrawnCorrectly) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("arc_end_caps_correct"); |
| |
| const auto root_surface_transformation = SkMatrix() |
| .preScale(1.0, -1.0) |
| .preTranslate(1024.0, -800.0) |
| .preRotate(90.0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 800; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("arc_end_caps.png", scene_image)); |
| } |
| |
| static void FilterMutationsByType( |
| const FlutterPlatformViewMutation** mutations, |
| size_t count, |
| FlutterPlatformViewMutationType type, |
| std::function<void(const FlutterPlatformViewMutation& mutation)> handler) { |
| if (mutations == nullptr) { |
| return; |
| } |
| |
| for (size_t i = 0; i < count; ++i) { |
| const FlutterPlatformViewMutation* mutation = mutations[i]; |
| if (mutation->type != type) { |
| continue; |
| } |
| |
| handler(*mutation); |
| } |
| } |
| |
| static void FilterMutationsByType( |
| const FlutterPlatformView* view, |
| FlutterPlatformViewMutationType type, |
| std::function<void(const FlutterPlatformViewMutation& mutation)> handler) { |
| return FilterMutationsByType(view->mutations, view->mutations_count, type, |
| handler); |
| } |
| |
| static SkMatrix GetTotalMutationTransformationMatrix( |
| const FlutterPlatformViewMutation** mutations, |
| size_t count) { |
| SkMatrix collected; |
| |
| FilterMutationsByType( |
| mutations, count, kFlutterPlatformViewMutationTypeTransformation, |
| [&](const auto& mutation) { |
| collected.preConcat(SkMatrixMake(mutation.transformation)); |
| }); |
| |
| return collected; |
| } |
| |
| static SkMatrix GetTotalMutationTransformationMatrix( |
| const FlutterPlatformView* view) { |
| return GetTotalMutationTransformationMatrix(view->mutations, |
| view->mutations_count); |
| } |
| |
| TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(400, 300)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("scene_builder_with_clips"); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 400).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| fml::AutoResetWaitableEvent latch; |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| { |
| FlutterPlatformView platform_view = *layers[0]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(300.0, 400.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| |
| bool clip_assertions_checked = false; |
| |
| // The total transformation on the stack upto the platform view. |
| const auto total_xformation = |
| GetTotalMutationTransformationMatrix(layers[0]->platform_view); |
| |
| FilterMutationsByType( |
| layers[0]->platform_view, |
| kFlutterPlatformViewMutationTypeClipRect, |
| [&](const auto& mutation) { |
| FlutterRect clip = mutation.clip_rect; |
| |
| // The test is only setup to supply one clip. Make sure it is |
| // the one we expect. |
| const auto rect_to_compare = |
| SkRect::MakeLTRB(10.0, 10.0, 390, 290); |
| ASSERT_EQ(clip, FlutterRectMake(rect_to_compare)); |
| |
| // This maps the clip from device space into surface space. |
| SkRect mapped; |
| ASSERT_TRUE(total_xformation.mapRect(&mapped, rect_to_compare)); |
| ASSERT_EQ(mapped, SkRect::MakeLTRB(10, 10, 290, 390)); |
| clip_assertions_checked = true; |
| }); |
| |
| ASSERT_TRUE(clip_assertions_checked); |
| } |
| |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 400; |
| event.height = 300; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, ComplexClipsAreCorrectlyCalculated) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1024, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("scene_builder_with_complex_clips"); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| fml::AutoResetWaitableEvent latch; |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| { |
| FlutterPlatformView platform_view = *layers[0]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(600.0, 1024.0); |
| layer.offset = FlutterPointMake(0.0, -256.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| |
| const auto** mutations = platform_view.mutations; |
| |
| ASSERT_EQ(mutations[0]->type, |
| kFlutterPlatformViewMutationTypeTransformation); |
| ASSERT_EQ(SkMatrixMake(mutations[0]->transformation), |
| root_surface_transformation); |
| |
| ASSERT_EQ(mutations[1]->type, |
| kFlutterPlatformViewMutationTypeClipRect); |
| ASSERT_EQ(SkRectMake(mutations[1]->clip_rect), |
| SkRect::MakeLTRB(0.0, 0.0, 1024.0, 600.0)); |
| |
| ASSERT_EQ(mutations[2]->type, |
| kFlutterPlatformViewMutationTypeTransformation); |
| ASSERT_EQ(SkMatrixMake(mutations[2]->transformation), |
| SkMatrix::Translate(512.0, 0.0)); |
| |
| ASSERT_EQ(mutations[3]->type, |
| kFlutterPlatformViewMutationTypeClipRect); |
| ASSERT_EQ(SkRectMake(mutations[3]->clip_rect), |
| SkRect::MakeLTRB(0.0, 0.0, 512.0, 600.0)); |
| |
| ASSERT_EQ(mutations[4]->type, |
| kFlutterPlatformViewMutationTypeTransformation); |
| ASSERT_EQ(SkMatrixMake(mutations[4]->transformation), |
| SkMatrix::Translate(-256.0, 0.0)); |
| |
| ASSERT_EQ(mutations[5]->type, |
| kFlutterPlatformViewMutationTypeClipRect); |
| ASSERT_EQ(SkRectMake(mutations[5]->clip_rect), |
| SkRect::MakeLTRB(0.0, 0.0, 1024.0, 600.0)); |
| } |
| |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 400; |
| event.height = 300; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, ObjectsCanBePostedViaPorts) { |
| auto& context = GetEmbedderContext(); |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); |
| builder.SetDartEntrypoint("objects_can_be_posted"); |
| |
| // Synchronously acquire the send port from the Dart end. We will be using |
| // this to send message. The Dart end will just echo those messages back to us |
| // for inspection. |
| FlutterEngineDartPort port = 0; |
| fml::AutoResetWaitableEvent event; |
| context.AddNativeCallback("SignalNativeCount", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| port = tonic::DartConverter<int64_t>::FromDart( |
| Dart_GetNativeArgument(args, 0)); |
| event.Signal(); |
| })); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| event.Wait(); |
| ASSERT_NE(port, 0); |
| |
| using Trampoline = std::function<void(Dart_Handle message)>; |
| Trampoline trampoline; |
| |
| context.AddNativeCallback("SendObjectToNativeCode", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| FML_CHECK(trampoline); |
| auto trampoline_copy = trampoline; |
| trampoline = nullptr; |
| trampoline_copy(Dart_GetNativeArgument(args, 0)); |
| })); |
| |
| // Check null. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeNull; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_TRUE(Dart_IsNull(handle)); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check bool. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeBool; |
| object.bool_value = true; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(handle)); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check int32. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeInt32; |
| object.int32_value = 1988; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_EQ(tonic::DartConverter<int32_t>::FromDart(handle), 1988); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check int64. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeInt64; |
| object.int64_value = 1988; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_EQ(tonic::DartConverter<int64_t>::FromDart(handle), 1988); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check double. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeDouble; |
| object.double_value = 1988.0; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_DOUBLE_EQ(tonic::DartConverter<double>::FromDart(handle), 1988.0); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check string. |
| { |
| const char* message = "Hello. My name is Inigo Montoya."; |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeString; |
| object.string_value = message; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_EQ(tonic::DartConverter<std::string>::FromDart(handle), |
| std::string{message}); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check buffer (copied out). |
| { |
| std::vector<uint8_t> message; |
| message.resize(1988); |
| |
| ASSERT_TRUE(MemsetPatternSetOrCheck( |
| message, MemsetPatternOp::kMemsetPatternOpSetBuffer)); |
| |
| FlutterEngineDartBuffer buffer = {}; |
| |
| buffer.struct_size = sizeof(buffer); |
| buffer.user_data = nullptr; |
| buffer.buffer_collect_callback = nullptr; |
| buffer.buffer = message.data(); |
| buffer.buffer_size = message.size(); |
| |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeBuffer; |
| object.buffer_value = &buffer; |
| trampoline = [&](Dart_Handle handle) { |
| intptr_t length = 0; |
| Dart_ListLength(handle, &length); |
| ASSERT_EQ(length, 1988); |
| // TODO(chinmaygarde); The std::vector<uint8_t> specialization for |
| // DartConvertor in tonic is broken which is preventing the buffer |
| // being checked here. Fix tonic and strengthen this check. For now, just |
| // the buffer length is checked. |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| std::vector<uint8_t> message; |
| fml::AutoResetWaitableEvent buffer_released_latch; |
| |
| // Check buffer (caller own buffer with zero copy transfer). |
| { |
| message.resize(1988); |
| |
| ASSERT_TRUE(MemsetPatternSetOrCheck( |
| message, MemsetPatternOp::kMemsetPatternOpSetBuffer)); |
| |
| FlutterEngineDartBuffer buffer = {}; |
| |
| buffer.struct_size = sizeof(buffer); |
| buffer.user_data = &buffer_released_latch; |
| buffer.buffer_collect_callback = +[](void* user_data) { |
| reinterpret_cast<fml::AutoResetWaitableEvent*>(user_data)->Signal(); |
| }; |
| buffer.buffer = message.data(); |
| buffer.buffer_size = message.size(); |
| |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeBuffer; |
| object.buffer_value = &buffer; |
| trampoline = [&](Dart_Handle handle) { |
| intptr_t length = 0; |
| Dart_ListLength(handle, &length); |
| ASSERT_EQ(length, 1988); |
| // TODO(chinmaygarde); The std::vector<uint8_t> specialization for |
| // DartConvertor in tonic is broken which is preventing the buffer |
| // being checked here. Fix tonic and strengthen this check. For now, just |
| // the buffer length is checked. |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| engine.reset(); |
| |
| // We cannot determine when the VM will GC objects that might have external |
| // typed data finalizers. Since we need to ensure that we correctly wired up |
| // finalizers from the embedders, we force the VM to collect all objects but |
| // just shutting it down. |
| buffer_released_latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, CanSendLowMemoryNotification) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // TODO(chinmaygarde): The shell ought to have a mechanism for notification |
| // dispatch that engine subsystems can register handlers to. This would allow |
| // the raster cache and the secondary context caches to respond to |
| // notifications. Once that is in place, this test can be updated to actually |
| // ensure that the dispatched message is visible to engine subsystems. |
| ASSERT_EQ(FlutterEngineNotifyLowMemoryWarning(engine.get()), kSuccess); |
| } |
| |
| TEST_F(EmbedderTest, CanPostTaskToAllNativeThreads) { |
| UniqueEngine engine; |
| size_t worker_count = 0; |
| fml::AutoResetWaitableEvent sync_latch; |
| |
| // One of the threads that the callback will be posted to is the platform |
| // thread. So we cannot wait for assertions to complete on the platform |
| // thread. Create a new thread to manage the engine instance and wait for |
| // assertions on the test thread. |
| auto platform_task_runner = CreateNewThread("platform_thread"); |
| |
| platform_task_runner->PostTask([&]() { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| |
| engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| worker_count = ToEmbedderEngine(engine.get()) |
| ->GetShell() |
| .GetDartVM() |
| ->GetConcurrentMessageLoop() |
| ->GetWorkerCount(); |
| |
| sync_latch.Signal(); |
| }); |
| |
| sync_latch.Wait(); |
| |
| const auto engine_threads_count = worker_count + 4u; |
| |
| struct Captures { |
| // Waits the adequate number of callbacks to fire. |
| fml::CountDownLatch latch; |
| |
| // This class will be accessed from multiple threads concurrently to track |
| // thread specific information that is later checked. All updates to fields |
| // in this struct must be made with this mutex acquired. |
| |
| std::mutex captures_mutex; |
| // Ensures that the expect number of distinct threads were serviced. |
| std::set<std::thread::id> thread_ids; |
| |
| size_t platform_threads_count = 0; |
| size_t render_threads_count = 0; |
| size_t ui_threads_count = 0; |
| size_t worker_threads_count = 0; |
| |
| Captures(size_t count) : latch(count) {} |
| }; |
| |
| Captures captures(engine_threads_count); |
| |
| platform_task_runner->PostTask([&]() { |
| ASSERT_EQ(FlutterEnginePostCallbackOnAllNativeThreads( |
| engine.get(), |
| [](FlutterNativeThreadType type, void* baton) { |
| auto captures = reinterpret_cast<Captures*>(baton); |
| { |
| std::scoped_lock lock(captures->captures_mutex); |
| switch (type) { |
| case kFlutterNativeThreadTypeRender: |
| captures->render_threads_count++; |
| break; |
| case kFlutterNativeThreadTypeWorker: |
| captures->worker_threads_count++; |
| break; |
| case kFlutterNativeThreadTypeUI: |
| captures->ui_threads_count++; |
| break; |
| case kFlutterNativeThreadTypePlatform: |
| captures->platform_threads_count++; |
| break; |
| } |
| captures->thread_ids.insert(std::this_thread::get_id()); |
| } |
| captures->latch.CountDown(); |
| }, |
| &captures), |
| kSuccess); |
| }); |
| |
| captures.latch.Wait(); |
| ASSERT_EQ(captures.thread_ids.size(), engine_threads_count); |
| ASSERT_EQ(captures.platform_threads_count, 1u); |
| ASSERT_EQ(captures.render_threads_count, 1u); |
| ASSERT_EQ(captures.ui_threads_count, 1u); |
| ASSERT_EQ(captures.worker_threads_count, worker_count + 1u /* for IO */); |
| |
| platform_task_runner->PostTask([&]() { |
| engine.reset(); |
| sync_latch.Signal(); |
| }); |
| sync_latch.Wait(); |
| |
| // The engine should have already been destroyed on the platform task runner. |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, CompositorCanPostZeroLayersForPresentation) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene_posts_zero_layers_to_compositor"); |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 0u); |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| latch.Wait(); |
| |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 0u); |
| } |
| |
| TEST_F(EmbedderTest, CompositorCanPostOnlyPlatformViews) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("compositor_can_post_only_platform_views"); |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer 0 |
| { |
| FlutterPlatformView platform_view = *layers[0]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(300.0, 200.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 24; |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(300.0, 200.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| latch.Wait(); |
| |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 0u); |
| } |
| |
| TEST_F(EmbedderTest, CompositorRenderTargetsAreRecycled) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("render_targets_are_recycled"); |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(2); |
| |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| latch.CountDown(); |
| })); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 20u); |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 10u); |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCreatedCount(), 10u); |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCollectedCount(), 0u); |
| // Killing the engine should immediately collect all pending render targets. |
| engine.reset(); |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 0u); |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCreatedCount(), 10u); |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCollectedCount(), 10u); |
| } |
| |
| TEST_F(EmbedderTest, CompositorRenderTargetsAreInStableOrder) { |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("render_targets_are_recycled"); |
| context.GetCompositor().SetRenderTargetType( |
| EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(2); |
| |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| latch.CountDown(); |
| })); |
| |
| size_t frame_count = 0; |
| std::vector<void*> first_frame_backing_store_user_data; |
| context.GetCompositor().SetPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 20u); |
| |
| if (first_frame_backing_store_user_data.size() == 0u) { |
| for (size_t i = 0; i < layers_count; ++i) { |
| if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { |
| first_frame_backing_store_user_data.push_back( |
| layers[i]->backing_store->user_data); |
| } |
| } |
| return; |
| } |
| |
| ASSERT_EQ(first_frame_backing_store_user_data.size(), 10u); |
| |
| frame_count++; |
| std::vector<void*> backing_store_user_data; |
| for (size_t i = 0; i < layers_count; ++i) { |
| if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { |
| backing_store_user_data.push_back( |
| layers[i]->backing_store->user_data); |
| } |
| } |
| |
| ASSERT_EQ(backing_store_user_data.size(), 10u); |
| |
| ASSERT_EQ(first_frame_backing_store_user_data, backing_store_user_data); |
| |
| if (frame_count == 20) { |
| latch.CountDown(); |
| } |
| }, |
| false // one shot |
| ); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, InvalidAOTDataSourcesMustReturnError) { |
| if (!DartVM::IsRunningPrecompiledCode()) { |
| GTEST_SKIP(); |
| return; |
| } |
| FlutterEngineAOTDataSource data_in = {}; |
| FlutterEngineAOTData data_out = nullptr; |
| |
| // Null source specified. |
| ASSERT_EQ(FlutterEngineCreateAOTData(nullptr, &data_out), kInvalidArguments); |
| ASSERT_EQ(data_out, nullptr); |
| |
| // Null data_out specified. |
| ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, nullptr), kInvalidArguments); |
| |
| // Invalid FlutterEngineAOTDataSourceType type specified. |
| data_in.type = FlutterEngineAOTDataSourceType(-1); |
| ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kInvalidArguments); |
| ASSERT_EQ(data_out, nullptr); |
| |
| // Invalid ELF path specified. |
| data_in.type = kFlutterEngineAOTDataSourceTypeElfPath; |
| data_in.elf_path = nullptr; |
| ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kInvalidArguments); |
| ASSERT_EQ(data_in.type, kFlutterEngineAOTDataSourceTypeElfPath); |
| ASSERT_EQ(data_in.elf_path, nullptr); |
| ASSERT_EQ(data_out, nullptr); |
| |
| // Invalid ELF path specified. |
| data_in.elf_path = ""; |
| ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kInvalidArguments); |
| ASSERT_EQ(data_in.type, kFlutterEngineAOTDataSourceTypeElfPath); |
| ASSERT_EQ(data_in.elf_path, ""); |
| ASSERT_EQ(data_out, nullptr); |
| |
| // Could not find VM snapshot data. |
| data_in.elf_path = "/bin/true"; |
| ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kInvalidArguments); |
| ASSERT_EQ(data_in.type, kFlutterEngineAOTDataSourceTypeElfPath); |
| ASSERT_EQ(data_in.elf_path, "/bin/true"); |
| ASSERT_EQ(data_out, nullptr); |
| } |
| |
| TEST_F(EmbedderTest, MustNotRunWithMultipleAOTSources) { |
| if (!DartVM::IsRunningPrecompiledCode()) { |
| GTEST_SKIP(); |
| return; |
| } |
| auto& context = GetEmbedderContext(); |
| |
| EmbedderConfigBuilder builder( |
| context, |
| EmbedderConfigBuilder::InitializationPreference::kMultiAOTInitialize); |
| |
| builder.SetSoftwareRendererConfig(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, CanCreateAndCollectAValidElfSource) { |
| if (!DartVM::IsRunningPrecompiledCode()) { |
| GTEST_SKIP(); |
| return; |
| } |
| FlutterEngineAOTDataSource data_in = {}; |
| FlutterEngineAOTData data_out = nullptr; |
| |
| // Collecting a null object should be allowed |
| ASSERT_EQ(FlutterEngineCollectAOTData(data_out), kSuccess); |
| |
| const auto elf_path = |
| fml::paths::JoinPaths({GetFixturesPath(), kAOTAppELFFileName}); |
| |
| data_in.type = kFlutterEngineAOTDataSourceTypeElfPath; |
| data_in.elf_path = elf_path.c_str(); |
| |
| ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kSuccess); |
| ASSERT_EQ(data_in.type, kFlutterEngineAOTDataSourceTypeElfPath); |
| ASSERT_EQ(data_in.elf_path, elf_path.c_str()); |
| ASSERT_NE(data_out, nullptr); |
| |
| ASSERT_EQ(FlutterEngineCollectAOTData(data_out), kSuccess); |
| } |
| |
| TEST_F(EmbedderTest, CanLaunchAndShutdownWithAValidElfSource) { |
| if (!DartVM::IsRunningPrecompiledCode()) { |
| GTEST_SKIP(); |
| return; |
| } |
| auto& context = GetEmbedderContext(); |
| |
| fml::AutoResetWaitableEvent latch; |
| context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); |
| |
| EmbedderConfigBuilder builder( |
| context, |
| EmbedderConfigBuilder::InitializationPreference::kAOTDataInitialize); |
| |
| builder.SetSoftwareRendererConfig(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Wait for the root isolate to launch. |
| latch.Wait(); |
| engine.reset(); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |