blob: 82e392bbf8e56b04150b6af4ec5950ab9d799ae9 [file] [log] [blame]
// 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 "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(); }));
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);
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();
}));
check_latch.Wait();
}
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::MakeScale(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::MakeTrans(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::MakeTrans(-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