1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | // FLUTTER_NOLINT |
5 | |
6 | #define FML_USED_ON_EMBEDDER |
7 | |
8 | #include <time.h> |
9 | #include <algorithm> |
10 | #include <functional> |
11 | #include <future> |
12 | #include <memory> |
13 | |
14 | #include "flutter/flow/layers/layer_tree.h" |
15 | #include "flutter/flow/layers/picture_layer.h" |
16 | #include "flutter/flow/layers/transform_layer.h" |
17 | #include "flutter/fml/command_line.h" |
18 | #include "flutter/fml/dart/dart_converter.h" |
19 | #include "flutter/fml/make_copyable.h" |
20 | #include "flutter/fml/message_loop.h" |
21 | #include "flutter/fml/synchronization/count_down_latch.h" |
22 | #include "flutter/fml/synchronization/waitable_event.h" |
23 | #include "flutter/runtime/dart_vm.h" |
24 | #include "flutter/shell/common/persistent_cache.h" |
25 | #include "flutter/shell/common/platform_view.h" |
26 | #include "flutter/shell/common/rasterizer.h" |
27 | #include "flutter/shell/common/shell_test.h" |
28 | #include "flutter/shell/common/shell_test_external_view_embedder.h" |
29 | #include "flutter/shell/common/shell_test_platform_view.h" |
30 | #include "flutter/shell/common/switches.h" |
31 | #include "flutter/shell/common/thread_host.h" |
32 | #include "flutter/shell/common/vsync_waiter_fallback.h" |
33 | #include "flutter/shell/version/version.h" |
34 | #include "flutter/testing/testing.h" |
35 | #include "third_party/rapidjson/include/rapidjson/writer.h" |
36 | #include "third_party/skia/include/core/SkPictureRecorder.h" |
37 | #include "third_party/tonic/converter/dart_converter.h" |
38 | |
39 | #ifdef SHELL_ENABLE_VULKAN |
40 | #include "flutter/vulkan/vulkan_application.h" // nogncheck |
41 | #endif |
42 | |
43 | namespace flutter { |
44 | namespace testing { |
45 | |
46 | static bool ValidateShell(Shell* shell) { |
47 | if (!shell) { |
48 | return false; |
49 | } |
50 | |
51 | if (!shell->IsSetup()) { |
52 | return false; |
53 | } |
54 | |
55 | ShellTest::PlatformViewNotifyCreated(shell); |
56 | |
57 | { |
58 | fml::AutoResetWaitableEvent latch; |
59 | fml::TaskRunner::RunNowOrPostTask( |
60 | shell->GetTaskRunners().GetPlatformTaskRunner(), [shell, &latch]() { |
61 | shell->GetPlatformView()->NotifyDestroyed(); |
62 | latch.Signal(); |
63 | }); |
64 | latch.Wait(); |
65 | } |
66 | |
67 | return true; |
68 | } |
69 | |
70 | TEST_F(ShellTest, InitializeWithInvalidThreads) { |
71 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
72 | Settings settings = CreateSettingsForFixture(); |
73 | TaskRunners task_runners("test" , nullptr, nullptr, nullptr, nullptr); |
74 | auto shell = CreateShell(std::move(settings), std::move(task_runners)); |
75 | ASSERT_FALSE(shell); |
76 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
77 | } |
78 | |
79 | TEST_F(ShellTest, InitializeWithDifferentThreads) { |
80 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
81 | Settings settings = CreateSettingsForFixture(); |
82 | ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + "." , |
83 | ThreadHost::Type::Platform | ThreadHost::Type::GPU | |
84 | ThreadHost::Type::IO | ThreadHost::Type::UI); |
85 | TaskRunners task_runners("test" , thread_host.platform_thread->GetTaskRunner(), |
86 | thread_host.raster_thread->GetTaskRunner(), |
87 | thread_host.ui_thread->GetTaskRunner(), |
88 | thread_host.io_thread->GetTaskRunner()); |
89 | auto shell = CreateShell(std::move(settings), std::move(task_runners)); |
90 | ASSERT_TRUE(ValidateShell(shell.get())); |
91 | ASSERT_TRUE(DartVMRef::IsInstanceRunning()); |
92 | DestroyShell(std::move(shell), std::move(task_runners)); |
93 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
94 | } |
95 | |
96 | TEST_F(ShellTest, InitializeWithSingleThread) { |
97 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
98 | Settings settings = CreateSettingsForFixture(); |
99 | ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + "." , |
100 | ThreadHost::Type::Platform); |
101 | auto task_runner = thread_host.platform_thread->GetTaskRunner(); |
102 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
103 | task_runner); |
104 | auto shell = CreateShell(std::move(settings), task_runners); |
105 | ASSERT_TRUE(DartVMRef::IsInstanceRunning()); |
106 | ASSERT_TRUE(ValidateShell(shell.get())); |
107 | DestroyShell(std::move(shell), std::move(task_runners)); |
108 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
109 | } |
110 | |
111 | TEST_F(ShellTest, InitializeWithSingleThreadWhichIsTheCallingThread) { |
112 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
113 | Settings settings = CreateSettingsForFixture(); |
114 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
115 | auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); |
116 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
117 | task_runner); |
118 | auto shell = CreateShell(std::move(settings), task_runners); |
119 | ASSERT_TRUE(ValidateShell(shell.get())); |
120 | ASSERT_TRUE(DartVMRef::IsInstanceRunning()); |
121 | DestroyShell(std::move(shell), std::move(task_runners)); |
122 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
123 | } |
124 | |
125 | TEST_F(ShellTest, |
126 | InitializeWithMultipleThreadButCallingThreadAsPlatformThread) { |
127 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
128 | Settings settings = CreateSettingsForFixture(); |
129 | ThreadHost thread_host( |
130 | "io.flutter.test." + GetCurrentTestName() + "." , |
131 | ThreadHost::Type::GPU | ThreadHost::Type::IO | ThreadHost::Type::UI); |
132 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
133 | TaskRunners task_runners("test" , |
134 | fml::MessageLoop::GetCurrent().GetTaskRunner(), |
135 | thread_host.raster_thread->GetTaskRunner(), |
136 | thread_host.ui_thread->GetTaskRunner(), |
137 | thread_host.io_thread->GetTaskRunner()); |
138 | auto shell = Shell::Create( |
139 | std::move(task_runners), settings, |
140 | [](Shell& shell) { |
141 | // This is unused in the platform view as we are not using the simulated |
142 | // vsync mechanism. We should have better DI in the tests. |
143 | const auto vsync_clock = std::make_shared<ShellTestVsyncClock>(); |
144 | return ShellTestPlatformView::Create( |
145 | shell, shell.GetTaskRunners(), vsync_clock, |
146 | [task_runners = shell.GetTaskRunners()]() { |
147 | return static_cast<std::unique_ptr<VsyncWaiter>>( |
148 | std::make_unique<VsyncWaiterFallback>(task_runners)); |
149 | }, |
150 | ShellTestPlatformView::BackendType::kDefaultBackend, nullptr); |
151 | }, |
152 | [](Shell& shell) { return std::make_unique<Rasterizer>(shell); }); |
153 | ASSERT_TRUE(ValidateShell(shell.get())); |
154 | ASSERT_TRUE(DartVMRef::IsInstanceRunning()); |
155 | DestroyShell(std::move(shell), std::move(task_runners)); |
156 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
157 | } |
158 | |
159 | TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) { |
160 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
161 | Settings settings = CreateSettingsForFixture(); |
162 | ThreadHost thread_host( |
163 | "io.flutter.test." + GetCurrentTestName() + "." , |
164 | ThreadHost::Type::Platform | ThreadHost::Type::IO | ThreadHost::Type::UI); |
165 | TaskRunners task_runners( |
166 | "test" , |
167 | thread_host.platform_thread->GetTaskRunner(), // platform |
168 | thread_host.platform_thread->GetTaskRunner(), // raster |
169 | thread_host.ui_thread->GetTaskRunner(), // ui |
170 | thread_host.io_thread->GetTaskRunner() // io |
171 | ); |
172 | auto shell = CreateShell(std::move(settings), std::move(task_runners)); |
173 | ASSERT_TRUE(DartVMRef::IsInstanceRunning()); |
174 | ASSERT_TRUE(ValidateShell(shell.get())); |
175 | DestroyShell(std::move(shell), std::move(task_runners)); |
176 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
177 | } |
178 | |
179 | TEST_F(ShellTest, FixturesAreFunctional) { |
180 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
181 | auto settings = CreateSettingsForFixture(); |
182 | auto shell = CreateShell(settings); |
183 | ASSERT_TRUE(ValidateShell(shell.get())); |
184 | |
185 | auto configuration = RunConfiguration::InferFromSettings(settings); |
186 | ASSERT_TRUE(configuration.IsValid()); |
187 | configuration.SetEntrypoint("fixturesAreFunctionalMain" ); |
188 | |
189 | fml::AutoResetWaitableEvent main_latch; |
190 | AddNativeCallback( |
191 | "SayHiFromFixturesAreFunctionalMain" , |
192 | CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); })); |
193 | |
194 | RunEngine(shell.get(), std::move(configuration)); |
195 | main_latch.Wait(); |
196 | ASSERT_TRUE(DartVMRef::IsInstanceRunning()); |
197 | DestroyShell(std::move(shell)); |
198 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
199 | } |
200 | |
201 | TEST_F(ShellTest, SecondaryIsolateBindingsAreSetupViaShellSettings) { |
202 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
203 | auto settings = CreateSettingsForFixture(); |
204 | auto shell = CreateShell(settings); |
205 | ASSERT_TRUE(ValidateShell(shell.get())); |
206 | |
207 | auto configuration = RunConfiguration::InferFromSettings(settings); |
208 | ASSERT_TRUE(configuration.IsValid()); |
209 | configuration.SetEntrypoint("testCanLaunchSecondaryIsolate" ); |
210 | |
211 | fml::CountDownLatch latch(2); |
212 | AddNativeCallback("NotifyNative" , CREATE_NATIVE_ENTRY([&latch](auto args) { |
213 | latch.CountDown(); |
214 | })); |
215 | |
216 | RunEngine(shell.get(), std::move(configuration)); |
217 | |
218 | latch.Wait(); |
219 | |
220 | ASSERT_TRUE(DartVMRef::IsInstanceRunning()); |
221 | DestroyShell(std::move(shell)); |
222 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
223 | } |
224 | |
225 | TEST_F(ShellTest, LastEntrypoint) { |
226 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
227 | auto settings = CreateSettingsForFixture(); |
228 | auto shell = CreateShell(settings); |
229 | ASSERT_TRUE(ValidateShell(shell.get())); |
230 | |
231 | auto configuration = RunConfiguration::InferFromSettings(settings); |
232 | ASSERT_TRUE(configuration.IsValid()); |
233 | std::string entry_point = "fixturesAreFunctionalMain" ; |
234 | configuration.SetEntrypoint(entry_point); |
235 | |
236 | fml::AutoResetWaitableEvent main_latch; |
237 | std::string last_entry_point; |
238 | AddNativeCallback( |
239 | "SayHiFromFixturesAreFunctionalMain" , CREATE_NATIVE_ENTRY([&](auto args) { |
240 | last_entry_point = shell->GetEngine()->GetLastEntrypoint(); |
241 | main_latch.Signal(); |
242 | })); |
243 | |
244 | RunEngine(shell.get(), std::move(configuration)); |
245 | main_latch.Wait(); |
246 | EXPECT_EQ(entry_point, last_entry_point); |
247 | ASSERT_TRUE(DartVMRef::IsInstanceRunning()); |
248 | DestroyShell(std::move(shell)); |
249 | ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
250 | } |
251 | |
252 | TEST(ShellTestNoFixture, EnableMirrorsIsAllowed) { |
253 | if (DartVM::IsRunningPrecompiledCode()) { |
254 | // This covers profile and release modes which use AOT (where this flag does |
255 | // not make sense anyway). |
256 | GTEST_SKIP(); |
257 | return; |
258 | } |
259 | #if FLUTTER_RELEASE |
260 | GTEST_SKIP(); |
261 | return; |
262 | #endif |
263 | |
264 | const std::vector<fml::CommandLine::Option> options = { |
265 | fml::CommandLine::Option("dart-flags" , "--enable_mirrors" )}; |
266 | fml::CommandLine command_line("" , options, std::vector<std::string>()); |
267 | flutter::Settings settings = flutter::SettingsFromCommandLine(command_line); |
268 | EXPECT_EQ(settings.dart_flags.size(), 1u); |
269 | } |
270 | |
271 | TEST_F(ShellTest, DisallowedDartVMFlag) { |
272 | // Run this test in a thread-safe manner, otherwise gtest will complain. |
273 | ::testing::FLAGS_gtest_death_test_style = "threadsafe" ; |
274 | |
275 | const std::vector<fml::CommandLine::Option> options = { |
276 | fml::CommandLine::Option("dart-flags" , "--verify_after_gc" )}; |
277 | fml::CommandLine command_line("" , options, std::vector<std::string>()); |
278 | |
279 | // Upon encountering a disallowed Dart flag the process terminates. |
280 | const char* expected = |
281 | "Encountered disallowed Dart VM flag: --verify_after_gc" ; |
282 | ASSERT_DEATH(flutter::SettingsFromCommandLine(command_line), expected); |
283 | } |
284 | |
285 | TEST_F(ShellTest, AllowedDartVMFlag) { |
286 | const std::vector<fml::CommandLine::Option> options = { |
287 | #if !FLUTTER_RELEASE |
288 | fml::CommandLine::Option("dart-flags" , |
289 | "--max_profile_depth 1,--random_seed 42" ) |
290 | #endif |
291 | }; |
292 | fml::CommandLine command_line("" , options, std::vector<std::string>()); |
293 | flutter::Settings settings = flutter::SettingsFromCommandLine(command_line); |
294 | |
295 | #if !FLUTTER_RELEASE |
296 | EXPECT_EQ(settings.dart_flags.size(), 2u); |
297 | EXPECT_EQ(settings.dart_flags[0], "--max_profile_depth 1" ); |
298 | EXPECT_EQ(settings.dart_flags[1], "--random_seed 42" ); |
299 | #else |
300 | EXPECT_EQ(settings.dart_flags.size(), 0u); |
301 | #endif |
302 | } |
303 | |
304 | TEST_F(ShellTest, NoNeedToReportTimingsByDefault) { |
305 | auto settings = CreateSettingsForFixture(); |
306 | std::unique_ptr<Shell> shell = CreateShell(settings); |
307 | |
308 | // Create the surface needed by rasterizer |
309 | PlatformViewNotifyCreated(shell.get()); |
310 | |
311 | auto configuration = RunConfiguration::InferFromSettings(settings); |
312 | configuration.SetEntrypoint("emptyMain" ); |
313 | |
314 | RunEngine(shell.get(), std::move(configuration)); |
315 | PumpOneFrame(shell.get()); |
316 | ASSERT_FALSE(GetNeedsReportTimings(shell.get())); |
317 | |
318 | // This assertion may or may not be the direct result of needs_report_timings_ |
319 | // being false. The count could be 0 simply because we just cleared |
320 | // unreported timings by reporting them. Hence this can't replace the |
321 | // ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added |
322 | // this assertion for an additional confidence that we're not pushing |
323 | // back to unreported timings unnecessarily. |
324 | // |
325 | // Conversely, do not assert UnreportedTimingsCount(shell.get()) to be |
326 | // positive in any tests. Otherwise those tests will be flaky as the clearing |
327 | // of unreported timings is unpredictive. |
328 | ASSERT_EQ(UnreportedTimingsCount(shell.get()), 0); |
329 | DestroyShell(std::move(shell)); |
330 | } |
331 | |
332 | TEST_F(ShellTest, NeedsReportTimingsIsSetWithCallback) { |
333 | auto settings = CreateSettingsForFixture(); |
334 | std::unique_ptr<Shell> shell = CreateShell(settings); |
335 | |
336 | // Create the surface needed by rasterizer |
337 | PlatformViewNotifyCreated(shell.get()); |
338 | |
339 | auto configuration = RunConfiguration::InferFromSettings(settings); |
340 | configuration.SetEntrypoint("dummyReportTimingsMain" ); |
341 | |
342 | RunEngine(shell.get(), std::move(configuration)); |
343 | PumpOneFrame(shell.get()); |
344 | ASSERT_TRUE(GetNeedsReportTimings(shell.get())); |
345 | DestroyShell(std::move(shell)); |
346 | } |
347 | |
348 | static void CheckFrameTimings(const std::vector<FrameTiming>& timings, |
349 | fml::TimePoint start, |
350 | fml::TimePoint finish) { |
351 | fml::TimePoint last_frame_start; |
352 | for (size_t i = 0; i < timings.size(); i += 1) { |
353 | // Ensure that timings are sorted. |
354 | ASSERT_TRUE(timings[i].Get(FrameTiming::kPhases[0]) >= last_frame_start); |
355 | last_frame_start = timings[i].Get(FrameTiming::kPhases[0]); |
356 | |
357 | fml::TimePoint last_phase_time; |
358 | for (auto phase : FrameTiming::kPhases) { |
359 | ASSERT_TRUE(timings[i].Get(phase) >= start); |
360 | ASSERT_TRUE(timings[i].Get(phase) <= finish); |
361 | |
362 | // phases should have weakly increasing time points |
363 | ASSERT_TRUE(last_phase_time <= timings[i].Get(phase)); |
364 | last_phase_time = timings[i].Get(phase); |
365 | } |
366 | } |
367 | } |
368 | |
369 | // TODO(43192): This test is disable because of flakiness. |
370 | TEST_F(ShellTest, DISABLED_ReportTimingsIsCalled) { |
371 | fml::TimePoint start = fml::TimePoint::Now(); |
372 | auto settings = CreateSettingsForFixture(); |
373 | std::unique_ptr<Shell> shell = CreateShell(settings); |
374 | |
375 | // Create the surface needed by rasterizer |
376 | PlatformViewNotifyCreated(shell.get()); |
377 | |
378 | auto configuration = RunConfiguration::InferFromSettings(settings); |
379 | ASSERT_TRUE(configuration.IsValid()); |
380 | configuration.SetEntrypoint("reportTimingsMain" ); |
381 | fml::AutoResetWaitableEvent reportLatch; |
382 | std::vector<int64_t> timestamps; |
383 | auto nativeTimingCallback = [&reportLatch, |
384 | ×tamps](Dart_NativeArguments args) { |
385 | Dart_Handle exception = nullptr; |
386 | timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments( |
387 | args, 0, exception); |
388 | reportLatch.Signal(); |
389 | }; |
390 | AddNativeCallback("NativeReportTimingsCallback" , |
391 | CREATE_NATIVE_ENTRY(nativeTimingCallback)); |
392 | RunEngine(shell.get(), std::move(configuration)); |
393 | |
394 | // Pump many frames so we can trigger the report quickly instead of waiting |
395 | // for the 1 second threshold. |
396 | for (int i = 0; i < 200; i += 1) { |
397 | PumpOneFrame(shell.get()); |
398 | } |
399 | |
400 | reportLatch.Wait(); |
401 | DestroyShell(std::move(shell)); |
402 | |
403 | fml::TimePoint finish = fml::TimePoint::Now(); |
404 | ASSERT_TRUE(timestamps.size() > 0); |
405 | ASSERT_TRUE(timestamps.size() % FrameTiming::kCount == 0); |
406 | std::vector<FrameTiming> timings(timestamps.size() / FrameTiming::kCount); |
407 | |
408 | for (size_t i = 0; i * FrameTiming::kCount < timestamps.size(); i += 1) { |
409 | for (auto phase : FrameTiming::kPhases) { |
410 | timings[i].Set( |
411 | phase, |
412 | fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds( |
413 | timestamps[i * FrameTiming::kCount + phase]))); |
414 | } |
415 | } |
416 | CheckFrameTimings(timings, start, finish); |
417 | } |
418 | |
419 | TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) { |
420 | fml::TimePoint start = fml::TimePoint::Now(); |
421 | |
422 | auto settings = CreateSettingsForFixture(); |
423 | fml::AutoResetWaitableEvent timingLatch; |
424 | FrameTiming timing; |
425 | |
426 | for (auto phase : FrameTiming::kPhases) { |
427 | timing.Set(phase, fml::TimePoint()); |
428 | // Check that the time points are initially smaller than start, so |
429 | // CheckFrameTimings will fail if they're not properly set later. |
430 | ASSERT_TRUE(timing.Get(phase) < start); |
431 | } |
432 | |
433 | settings.frame_rasterized_callback = [&timing, |
434 | &timingLatch](const FrameTiming& t) { |
435 | timing = t; |
436 | timingLatch.Signal(); |
437 | }; |
438 | |
439 | std::unique_ptr<Shell> shell = CreateShell(settings); |
440 | |
441 | // Create the surface needed by rasterizer |
442 | PlatformViewNotifyCreated(shell.get()); |
443 | |
444 | auto configuration = RunConfiguration::InferFromSettings(settings); |
445 | configuration.SetEntrypoint("onBeginFrameMain" ); |
446 | |
447 | int64_t frame_target_time; |
448 | auto nativeOnBeginFrame = [&frame_target_time](Dart_NativeArguments args) { |
449 | Dart_Handle exception = nullptr; |
450 | frame_target_time = |
451 | tonic::DartConverter<int64_t>::FromArguments(args, 0, exception); |
452 | }; |
453 | AddNativeCallback("NativeOnBeginFrame" , |
454 | CREATE_NATIVE_ENTRY(nativeOnBeginFrame)); |
455 | |
456 | RunEngine(shell.get(), std::move(configuration)); |
457 | |
458 | PumpOneFrame(shell.get()); |
459 | |
460 | // Check that timing is properly set. This implies that |
461 | // settings.frame_rasterized_callback is called. |
462 | timingLatch.Wait(); |
463 | fml::TimePoint finish = fml::TimePoint::Now(); |
464 | std::vector<FrameTiming> timings = {timing}; |
465 | CheckFrameTimings(timings, start, finish); |
466 | |
467 | // Check that onBeginFrame, which is the frame_target_time, is after |
468 | // FrameTiming's build start |
469 | int64_t build_start = |
470 | timing.Get(FrameTiming::kBuildStart).ToEpochDelta().ToMicroseconds(); |
471 | ASSERT_GT(frame_target_time, build_start); |
472 | DestroyShell(std::move(shell)); |
473 | } |
474 | |
475 | #if !defined(OS_FUCHSIA) |
476 | // TODO(sanjayc77): https://github.com/flutter/flutter/issues/53179. Add |
477 | // support for raster thread merger for Fuchsia. |
478 | TEST_F(ShellTest, |
479 | ExternalEmbedderEndFrameIsCalledWhenPostPrerollResultIsResubmit) { |
480 | auto settings = CreateSettingsForFixture(); |
481 | fml::AutoResetWaitableEvent endFrameLatch; |
482 | bool end_frame_called = false; |
483 | auto end_frame_callback = [&](bool should_resubmit_frame) { |
484 | ASSERT_TRUE(should_resubmit_frame); |
485 | end_frame_called = true; |
486 | endFrameLatch.Signal(); |
487 | }; |
488 | auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>( |
489 | end_frame_callback, PostPrerollResult::kResubmitFrame); |
490 | auto shell = CreateShell(std::move(settings), GetTaskRunnersForFixture(), |
491 | false, external_view_embedder); |
492 | |
493 | // Create the surface needed by rasterizer |
494 | PlatformViewNotifyCreated(shell.get()); |
495 | |
496 | auto configuration = RunConfiguration::InferFromSettings(settings); |
497 | configuration.SetEntrypoint("emptyMain" ); |
498 | |
499 | RunEngine(shell.get(), std::move(configuration)); |
500 | |
501 | LayerTreeBuilder builder = [&](std::shared_ptr<ContainerLayer> root) { |
502 | SkPictureRecorder recorder; |
503 | SkCanvas* recording_canvas = |
504 | recorder.beginRecording(SkRect::MakeXYWH(0, 0, 80, 80)); |
505 | recording_canvas->drawRect(SkRect::MakeXYWH(0, 0, 80, 80), |
506 | SkPaint(SkColor4f::FromColor(SK_ColorRED))); |
507 | auto sk_picture = recorder.finishRecordingAsPicture(); |
508 | fml::RefPtr<SkiaUnrefQueue> queue = fml::MakeRefCounted<SkiaUnrefQueue>( |
509 | this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); |
510 | auto picture_layer = std::make_shared<PictureLayer>( |
511 | SkPoint::Make(10, 10), |
512 | flutter::SkiaGPUObject<SkPicture>({sk_picture, queue}), false, false); |
513 | root->Add(picture_layer); |
514 | }; |
515 | |
516 | PumpOneFrame(shell.get(), 100, 100, builder); |
517 | endFrameLatch.Wait(); |
518 | |
519 | ASSERT_TRUE(end_frame_called); |
520 | |
521 | DestroyShell(std::move(shell)); |
522 | } |
523 | #endif |
524 | |
525 | TEST(SettingsTest, FrameTimingSetsAndGetsProperly) { |
526 | // Ensure that all phases are in kPhases. |
527 | ASSERT_EQ(sizeof(FrameTiming::kPhases), |
528 | FrameTiming::kCount * sizeof(FrameTiming::Phase)); |
529 | |
530 | int lastPhaseIndex = -1; |
531 | FrameTiming timing; |
532 | for (auto phase : FrameTiming::kPhases) { |
533 | ASSERT_TRUE(phase > lastPhaseIndex); // Ensure that kPhases are in order. |
534 | lastPhaseIndex = phase; |
535 | auto fake_time = |
536 | fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(phase)); |
537 | timing.Set(phase, fake_time); |
538 | ASSERT_TRUE(timing.Get(phase) == fake_time); |
539 | } |
540 | } |
541 | |
542 | #if FLUTTER_RELEASE |
543 | TEST_F(ShellTest, ReportTimingsIsCalledLaterInReleaseMode) { |
544 | #else |
545 | TEST_F(ShellTest, ReportTimingsIsCalledSoonerInNonReleaseMode) { |
546 | #endif |
547 | fml::TimePoint start = fml::TimePoint::Now(); |
548 | auto settings = CreateSettingsForFixture(); |
549 | std::unique_ptr<Shell> shell = CreateShell(settings); |
550 | |
551 | // Create the surface needed by rasterizer |
552 | PlatformViewNotifyCreated(shell.get()); |
553 | |
554 | auto configuration = RunConfiguration::InferFromSettings(settings); |
555 | ASSERT_TRUE(configuration.IsValid()); |
556 | configuration.SetEntrypoint("reportTimingsMain" ); |
557 | |
558 | // Wait for 2 reports: the first one is the immediate callback of the first |
559 | // frame; the second one will exercise the batching logic. |
560 | fml::CountDownLatch reportLatch(2); |
561 | std::vector<int64_t> timestamps; |
562 | auto nativeTimingCallback = [&reportLatch, |
563 | ×tamps](Dart_NativeArguments args) { |
564 | Dart_Handle exception = nullptr; |
565 | timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments( |
566 | args, 0, exception); |
567 | reportLatch.CountDown(); |
568 | }; |
569 | AddNativeCallback("NativeReportTimingsCallback" , |
570 | CREATE_NATIVE_ENTRY(nativeTimingCallback)); |
571 | RunEngine(shell.get(), std::move(configuration)); |
572 | |
573 | PumpOneFrame(shell.get()); |
574 | PumpOneFrame(shell.get()); |
575 | |
576 | reportLatch.Wait(); |
577 | DestroyShell(std::move(shell)); |
578 | |
579 | fml::TimePoint finish = fml::TimePoint::Now(); |
580 | fml::TimeDelta elapsed = finish - start; |
581 | |
582 | #if FLUTTER_RELEASE |
583 | // Our batch time is 1000ms. Hopefully the 800ms limit is relaxed enough to |
584 | // make it not too flaky. |
585 | ASSERT_TRUE(elapsed >= fml::TimeDelta::FromMilliseconds(800)); |
586 | #else |
587 | // Our batch time is 100ms. Hopefully the 500ms limit is relaxed enough to |
588 | // make it not too flaky. |
589 | ASSERT_TRUE(elapsed <= fml::TimeDelta::FromMilliseconds(500)); |
590 | #endif |
591 | } |
592 | |
593 | TEST_F(ShellTest, ReportTimingsIsCalledImmediatelyAfterTheFirstFrame) { |
594 | auto settings = CreateSettingsForFixture(); |
595 | std::unique_ptr<Shell> shell = CreateShell(settings); |
596 | |
597 | // Create the surface needed by rasterizer |
598 | PlatformViewNotifyCreated(shell.get()); |
599 | |
600 | auto configuration = RunConfiguration::InferFromSettings(settings); |
601 | ASSERT_TRUE(configuration.IsValid()); |
602 | configuration.SetEntrypoint("reportTimingsMain" ); |
603 | fml::AutoResetWaitableEvent reportLatch; |
604 | std::vector<int64_t> timestamps; |
605 | auto nativeTimingCallback = [&reportLatch, |
606 | ×tamps](Dart_NativeArguments args) { |
607 | Dart_Handle exception = nullptr; |
608 | timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments( |
609 | args, 0, exception); |
610 | reportLatch.Signal(); |
611 | }; |
612 | AddNativeCallback("NativeReportTimingsCallback" , |
613 | CREATE_NATIVE_ENTRY(nativeTimingCallback)); |
614 | RunEngine(shell.get(), std::move(configuration)); |
615 | |
616 | for (int i = 0; i < 10; i += 1) { |
617 | PumpOneFrame(shell.get()); |
618 | } |
619 | |
620 | reportLatch.Wait(); |
621 | DestroyShell(std::move(shell)); |
622 | |
623 | // Check for the immediate callback of the first frame that doesn't wait for |
624 | // the other 9 frames to be rasterized. |
625 | ASSERT_EQ(timestamps.size(), FrameTiming::kCount); |
626 | } |
627 | |
628 | TEST_F(ShellTest, ReloadSystemFonts) { |
629 | auto settings = CreateSettingsForFixture(); |
630 | |
631 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
632 | auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); |
633 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
634 | task_runner); |
635 | auto shell = CreateShell(std::move(settings), std::move(task_runners)); |
636 | |
637 | auto fontCollection = GetFontCollection(shell.get()); |
638 | std::vector<std::string> families(1, "Robotofake" ); |
639 | auto font = |
640 | fontCollection->GetMinikinFontCollectionForFamilies(families, "en" ); |
641 | if (font == nullptr) { |
642 | // The system does not have default font. Aborts this test. |
643 | return; |
644 | } |
645 | unsigned int id = font->getId(); |
646 | // The result should be cached. |
647 | font = fontCollection->GetMinikinFontCollectionForFamilies(families, "en" ); |
648 | ASSERT_EQ(font->getId(), id); |
649 | bool result = shell->ReloadSystemFonts(); |
650 | |
651 | // The cache is cleared, and FontCollection will be assigned a new id. |
652 | font = fontCollection->GetMinikinFontCollectionForFamilies(families, "en" ); |
653 | ASSERT_NE(font->getId(), id); |
654 | ASSERT_TRUE(result); |
655 | shell.reset(); |
656 | } |
657 | |
658 | TEST_F(ShellTest, WaitForFirstFrame) { |
659 | auto settings = CreateSettingsForFixture(); |
660 | std::unique_ptr<Shell> shell = CreateShell(settings); |
661 | |
662 | // Create the surface needed by rasterizer |
663 | PlatformViewNotifyCreated(shell.get()); |
664 | |
665 | auto configuration = RunConfiguration::InferFromSettings(settings); |
666 | configuration.SetEntrypoint("emptyMain" ); |
667 | |
668 | RunEngine(shell.get(), std::move(configuration)); |
669 | PumpOneFrame(shell.get()); |
670 | fml::Status result = |
671 | shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000)); |
672 | ASSERT_TRUE(result.ok()); |
673 | |
674 | DestroyShell(std::move(shell)); |
675 | } |
676 | |
677 | TEST_F(ShellTest, WaitForFirstFrameZeroSizeFrame) { |
678 | auto settings = CreateSettingsForFixture(); |
679 | std::unique_ptr<Shell> shell = CreateShell(settings); |
680 | |
681 | // Create the surface needed by rasterizer |
682 | PlatformViewNotifyCreated(shell.get()); |
683 | |
684 | auto configuration = RunConfiguration::InferFromSettings(settings); |
685 | configuration.SetEntrypoint("emptyMain" ); |
686 | |
687 | RunEngine(shell.get(), std::move(configuration)); |
688 | PumpOneFrame(shell.get(), {1.0, 0.0, 0.0}); |
689 | fml::Status result = |
690 | shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000)); |
691 | ASSERT_FALSE(result.ok()); |
692 | ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded); |
693 | |
694 | DestroyShell(std::move(shell)); |
695 | } |
696 | |
697 | TEST_F(ShellTest, WaitForFirstFrameTimeout) { |
698 | auto settings = CreateSettingsForFixture(); |
699 | std::unique_ptr<Shell> shell = CreateShell(settings); |
700 | |
701 | // Create the surface needed by rasterizer |
702 | PlatformViewNotifyCreated(shell.get()); |
703 | |
704 | auto configuration = RunConfiguration::InferFromSettings(settings); |
705 | configuration.SetEntrypoint("emptyMain" ); |
706 | |
707 | RunEngine(shell.get(), std::move(configuration)); |
708 | fml::Status result = |
709 | shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(10)); |
710 | ASSERT_FALSE(result.ok()); |
711 | ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded); |
712 | |
713 | DestroyShell(std::move(shell)); |
714 | } |
715 | |
716 | TEST_F(ShellTest, WaitForFirstFrameMultiple) { |
717 | auto settings = CreateSettingsForFixture(); |
718 | std::unique_ptr<Shell> shell = CreateShell(settings); |
719 | |
720 | // Create the surface needed by rasterizer |
721 | PlatformViewNotifyCreated(shell.get()); |
722 | |
723 | auto configuration = RunConfiguration::InferFromSettings(settings); |
724 | configuration.SetEntrypoint("emptyMain" ); |
725 | |
726 | RunEngine(shell.get(), std::move(configuration)); |
727 | PumpOneFrame(shell.get()); |
728 | fml::Status result = |
729 | shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000)); |
730 | ASSERT_TRUE(result.ok()); |
731 | for (int i = 0; i < 100; ++i) { |
732 | result = shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1)); |
733 | ASSERT_TRUE(result.ok()); |
734 | } |
735 | |
736 | DestroyShell(std::move(shell)); |
737 | } |
738 | |
739 | /// Makes sure that WaitForFirstFrame works if we rendered a frame with the |
740 | /// single-thread setup. |
741 | TEST_F(ShellTest, WaitForFirstFrameInlined) { |
742 | Settings settings = CreateSettingsForFixture(); |
743 | auto task_runner = CreateNewThread(); |
744 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
745 | task_runner); |
746 | std::unique_ptr<Shell> shell = |
747 | CreateShell(std::move(settings), std::move(task_runners)); |
748 | |
749 | // Create the surface needed by rasterizer |
750 | PlatformViewNotifyCreated(shell.get()); |
751 | |
752 | auto configuration = RunConfiguration::InferFromSettings(settings); |
753 | configuration.SetEntrypoint("emptyMain" ); |
754 | |
755 | RunEngine(shell.get(), std::move(configuration)); |
756 | PumpOneFrame(shell.get()); |
757 | fml::AutoResetWaitableEvent event; |
758 | task_runner->PostTask([&shell, &event] { |
759 | fml::Status result = |
760 | shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000)); |
761 | ASSERT_FALSE(result.ok()); |
762 | ASSERT_EQ(result.code(), fml::StatusCode::kFailedPrecondition); |
763 | event.Signal(); |
764 | }); |
765 | ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::FromMilliseconds(1000))); |
766 | |
767 | DestroyShell(std::move(shell), std::move(task_runners)); |
768 | } |
769 | |
770 | static size_t GetRasterizerResourceCacheBytesSync(Shell& shell) { |
771 | size_t bytes = 0; |
772 | fml::AutoResetWaitableEvent latch; |
773 | fml::TaskRunner::RunNowOrPostTask( |
774 | shell.GetTaskRunners().GetRasterTaskRunner(), [&]() { |
775 | if (auto rasterizer = shell.GetRasterizer()) { |
776 | bytes = rasterizer->GetResourceCacheMaxBytes().value_or(0U); |
777 | } |
778 | latch.Signal(); |
779 | }); |
780 | latch.Wait(); |
781 | return bytes; |
782 | } |
783 | |
784 | TEST_F(ShellTest, SetResourceCacheSize) { |
785 | Settings settings = CreateSettingsForFixture(); |
786 | auto task_runner = CreateNewThread(); |
787 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
788 | task_runner); |
789 | std::unique_ptr<Shell> shell = |
790 | CreateShell(std::move(settings), std::move(task_runners)); |
791 | |
792 | // Create the surface needed by rasterizer |
793 | PlatformViewNotifyCreated(shell.get()); |
794 | |
795 | auto configuration = RunConfiguration::InferFromSettings(settings); |
796 | configuration.SetEntrypoint("emptyMain" ); |
797 | |
798 | RunEngine(shell.get(), std::move(configuration)); |
799 | PumpOneFrame(shell.get()); |
800 | |
801 | // The Vulkan and GL backends set different default values for the resource |
802 | // cache size. |
803 | #ifdef SHELL_ENABLE_VULKAN |
804 | EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), |
805 | vulkan::kGrCacheMaxByteSize); |
806 | #else |
807 | EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), |
808 | static_cast<size_t>(24 * (1 << 20))); |
809 | #endif |
810 | |
811 | fml::TaskRunner::RunNowOrPostTask( |
812 | shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { |
813 | shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); |
814 | }); |
815 | PumpOneFrame(shell.get()); |
816 | |
817 | EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 3840000U); |
818 | |
819 | std::string request_json = R"json({ |
820 | "method": "Skia.setResourceCacheMaxBytes", |
821 | "args": 10000 |
822 | })json" ; |
823 | std::vector<uint8_t> data(request_json.begin(), request_json.end()); |
824 | auto platform_message = fml::MakeRefCounted<PlatformMessage>( |
825 | "flutter/skia" , std::move(data), nullptr); |
826 | SendEnginePlatformMessage(shell.get(), std::move(platform_message)); |
827 | PumpOneFrame(shell.get()); |
828 | EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U); |
829 | |
830 | fml::TaskRunner::RunNowOrPostTask( |
831 | shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { |
832 | shell->GetPlatformView()->SetViewportMetrics({1.0, 800, 400}); |
833 | }); |
834 | PumpOneFrame(shell.get()); |
835 | |
836 | EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U); |
837 | DestroyShell(std::move(shell), std::move(task_runners)); |
838 | } |
839 | |
840 | TEST_F(ShellTest, SetResourceCacheSizeEarly) { |
841 | Settings settings = CreateSettingsForFixture(); |
842 | auto task_runner = CreateNewThread(); |
843 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
844 | task_runner); |
845 | std::unique_ptr<Shell> shell = |
846 | CreateShell(std::move(settings), std::move(task_runners)); |
847 | |
848 | fml::TaskRunner::RunNowOrPostTask( |
849 | shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { |
850 | shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); |
851 | }); |
852 | PumpOneFrame(shell.get()); |
853 | |
854 | // Create the surface needed by rasterizer |
855 | PlatformViewNotifyCreated(shell.get()); |
856 | |
857 | auto configuration = RunConfiguration::InferFromSettings(settings); |
858 | configuration.SetEntrypoint("emptyMain" ); |
859 | |
860 | RunEngine(shell.get(), std::move(configuration)); |
861 | PumpOneFrame(shell.get()); |
862 | |
863 | EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), |
864 | static_cast<size_t>(3840000U)); |
865 | DestroyShell(std::move(shell), std::move(task_runners)); |
866 | } |
867 | |
868 | TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) { |
869 | Settings settings = CreateSettingsForFixture(); |
870 | auto task_runner = CreateNewThread(); |
871 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
872 | task_runner); |
873 | std::unique_ptr<Shell> shell = |
874 | CreateShell(std::move(settings), std::move(task_runners)); |
875 | |
876 | fml::TaskRunner::RunNowOrPostTask( |
877 | shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { |
878 | shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); |
879 | }); |
880 | PumpOneFrame(shell.get()); |
881 | |
882 | // Create the surface needed by rasterizer |
883 | PlatformViewNotifyCreated(shell.get()); |
884 | |
885 | auto configuration = RunConfiguration::InferFromSettings(settings); |
886 | configuration.SetEntrypoint("testSkiaResourceCacheSendsResponse" ); |
887 | |
888 | EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), |
889 | static_cast<size_t>(3840000U)); |
890 | |
891 | fml::AutoResetWaitableEvent latch; |
892 | AddNativeCallback("NotifyNative" , CREATE_NATIVE_ENTRY([&latch](auto args) { |
893 | latch.Signal(); |
894 | })); |
895 | |
896 | RunEngine(shell.get(), std::move(configuration)); |
897 | PumpOneFrame(shell.get()); |
898 | |
899 | latch.Wait(); |
900 | |
901 | EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), |
902 | static_cast<size_t>(10000U)); |
903 | DestroyShell(std::move(shell), std::move(task_runners)); |
904 | } |
905 | |
906 | TEST_F(ShellTest, CanCreateImagefromDecompressedBytes) { |
907 | Settings settings = CreateSettingsForFixture(); |
908 | auto task_runner = CreateNewThread(); |
909 | |
910 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
911 | task_runner); |
912 | |
913 | std::unique_ptr<Shell> shell = |
914 | CreateShell(std::move(settings), std::move(task_runners)); |
915 | |
916 | // Create the surface needed by rasterizer |
917 | PlatformViewNotifyCreated(shell.get()); |
918 | |
919 | auto configuration = RunConfiguration::InferFromSettings(settings); |
920 | configuration.SetEntrypoint("canCreateImageFromDecompressedData" ); |
921 | |
922 | fml::AutoResetWaitableEvent latch; |
923 | AddNativeCallback("NotifyWidthHeight" , |
924 | CREATE_NATIVE_ENTRY([&latch](auto args) { |
925 | auto width = tonic::DartConverter<int>::FromDart( |
926 | Dart_GetNativeArgument(args, 0)); |
927 | auto height = tonic::DartConverter<int>::FromDart( |
928 | Dart_GetNativeArgument(args, 1)); |
929 | ASSERT_EQ(width, 10); |
930 | ASSERT_EQ(height, 10); |
931 | latch.Signal(); |
932 | })); |
933 | |
934 | RunEngine(shell.get(), std::move(configuration)); |
935 | |
936 | latch.Wait(); |
937 | DestroyShell(std::move(shell), std::move(task_runners)); |
938 | } |
939 | |
940 | class MockTexture : public Texture { |
941 | public: |
942 | MockTexture(int64_t textureId, |
943 | std::shared_ptr<fml::AutoResetWaitableEvent> latch) |
944 | : Texture(textureId), latch_(latch) {} |
945 | |
946 | ~MockTexture() override = default; |
947 | |
948 | // Called from raster thread. |
949 | void Paint(SkCanvas& canvas, |
950 | const SkRect& bounds, |
951 | bool freeze, |
952 | GrDirectContext* context, |
953 | SkFilterQuality filter_quality) override {} |
954 | |
955 | void OnGrContextCreated() override {} |
956 | |
957 | void OnGrContextDestroyed() override {} |
958 | |
959 | void MarkNewFrameAvailable() override { |
960 | frames_available_++; |
961 | latch_->Signal(); |
962 | } |
963 | |
964 | void OnTextureUnregistered() override { |
965 | unregistered_ = true; |
966 | latch_->Signal(); |
967 | } |
968 | |
969 | bool unregistered() { return unregistered_; } |
970 | int frames_available() { return frames_available_; } |
971 | |
972 | private: |
973 | bool unregistered_ = false; |
974 | int frames_available_ = 0; |
975 | std::shared_ptr<fml::AutoResetWaitableEvent> latch_; |
976 | }; |
977 | |
978 | TEST_F(ShellTest, TextureFrameMarkedAvailableAndUnregister) { |
979 | Settings settings = CreateSettingsForFixture(); |
980 | auto configuration = RunConfiguration::InferFromSettings(settings); |
981 | auto task_runner = CreateNewThread(); |
982 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
983 | task_runner); |
984 | std::unique_ptr<Shell> shell = |
985 | CreateShell(std::move(settings), std::move(task_runners)); |
986 | |
987 | ASSERT_TRUE(ValidateShell(shell.get())); |
988 | PlatformViewNotifyCreated(shell.get()); |
989 | |
990 | RunEngine(shell.get(), std::move(configuration)); |
991 | |
992 | std::shared_ptr<fml::AutoResetWaitableEvent> latch = |
993 | std::make_shared<fml::AutoResetWaitableEvent>(); |
994 | |
995 | std::shared_ptr<MockTexture> mockTexture = |
996 | std::make_shared<MockTexture>(0, latch); |
997 | |
998 | fml::TaskRunner::RunNowOrPostTask( |
999 | shell->GetTaskRunners().GetRasterTaskRunner(), [&]() { |
1000 | shell->GetPlatformView()->RegisterTexture(mockTexture); |
1001 | shell->GetPlatformView()->MarkTextureFrameAvailable(0); |
1002 | }); |
1003 | latch->Wait(); |
1004 | |
1005 | EXPECT_EQ(mockTexture->frames_available(), 1); |
1006 | |
1007 | fml::TaskRunner::RunNowOrPostTask( |
1008 | shell->GetTaskRunners().GetRasterTaskRunner(), |
1009 | [&]() { shell->GetPlatformView()->UnregisterTexture(0); }); |
1010 | latch->Wait(); |
1011 | |
1012 | EXPECT_EQ(mockTexture->unregistered(), true); |
1013 | DestroyShell(std::move(shell), std::move(task_runners)); |
1014 | } |
1015 | |
1016 | TEST_F(ShellTest, IsolateCanAccessPersistentIsolateData) { |
1017 | const std::string message = "dummy isolate launch data." ; |
1018 | |
1019 | Settings settings = CreateSettingsForFixture(); |
1020 | settings.persistent_isolate_data = |
1021 | std::make_shared<fml::DataMapping>(message); |
1022 | TaskRunners task_runners("test" , // label |
1023 | GetCurrentTaskRunner(), // platform |
1024 | CreateNewThread(), // raster |
1025 | CreateNewThread(), // ui |
1026 | CreateNewThread() // io |
1027 | ); |
1028 | |
1029 | fml::AutoResetWaitableEvent message_latch; |
1030 | AddNativeCallback("NotifyMessage" , |
1031 | CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
1032 | const auto message_from_dart = |
1033 | tonic::DartConverter<std::string>::FromDart( |
1034 | Dart_GetNativeArgument(args, 0)); |
1035 | ASSERT_EQ(message, message_from_dart); |
1036 | message_latch.Signal(); |
1037 | })); |
1038 | |
1039 | std::unique_ptr<Shell> shell = |
1040 | CreateShell(std::move(settings), std::move(task_runners)); |
1041 | |
1042 | ASSERT_TRUE(shell->IsSetup()); |
1043 | auto configuration = RunConfiguration::InferFromSettings(settings); |
1044 | configuration.SetEntrypoint("canAccessIsolateLaunchData" ); |
1045 | |
1046 | fml::AutoResetWaitableEvent event; |
1047 | shell->RunEngine(std::move(configuration), [&](auto result) { |
1048 | ASSERT_EQ(result, Engine::RunStatus::Success); |
1049 | }); |
1050 | |
1051 | message_latch.Wait(); |
1052 | DestroyShell(std::move(shell), std::move(task_runners)); |
1053 | } |
1054 | |
1055 | TEST_F(ShellTest, Screenshot) { |
1056 | auto settings = CreateSettingsForFixture(); |
1057 | fml::AutoResetWaitableEvent firstFrameLatch; |
1058 | settings.frame_rasterized_callback = |
1059 | [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); }; |
1060 | |
1061 | std::unique_ptr<Shell> shell = CreateShell(settings); |
1062 | |
1063 | // Create the surface needed by rasterizer |
1064 | PlatformViewNotifyCreated(shell.get()); |
1065 | |
1066 | auto configuration = RunConfiguration::InferFromSettings(settings); |
1067 | configuration.SetEntrypoint("emptyMain" ); |
1068 | |
1069 | RunEngine(shell.get(), std::move(configuration)); |
1070 | |
1071 | LayerTreeBuilder builder = [&](std::shared_ptr<ContainerLayer> root) { |
1072 | SkPictureRecorder recorder; |
1073 | SkCanvas* recording_canvas = |
1074 | recorder.beginRecording(SkRect::MakeXYWH(0, 0, 80, 80)); |
1075 | recording_canvas->drawRect(SkRect::MakeXYWH(0, 0, 80, 80), |
1076 | SkPaint(SkColor4f::FromColor(SK_ColorRED))); |
1077 | auto sk_picture = recorder.finishRecordingAsPicture(); |
1078 | fml::RefPtr<SkiaUnrefQueue> queue = fml::MakeRefCounted<SkiaUnrefQueue>( |
1079 | this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); |
1080 | auto picture_layer = std::make_shared<PictureLayer>( |
1081 | SkPoint::Make(10, 10), |
1082 | flutter::SkiaGPUObject<SkPicture>({sk_picture, queue}), false, false); |
1083 | root->Add(picture_layer); |
1084 | }; |
1085 | |
1086 | PumpOneFrame(shell.get(), 100, 100, builder); |
1087 | firstFrameLatch.Wait(); |
1088 | |
1089 | std::promise<Rasterizer::Screenshot> screenshot_promise; |
1090 | auto screenshot_future = screenshot_promise.get_future(); |
1091 | |
1092 | fml::TaskRunner::RunNowOrPostTask( |
1093 | shell->GetTaskRunners().GetRasterTaskRunner(), |
1094 | [&screenshot_promise, &shell]() { |
1095 | auto rasterizer = shell->GetRasterizer(); |
1096 | screenshot_promise.set_value(rasterizer->ScreenshotLastLayerTree( |
1097 | Rasterizer::ScreenshotType::CompressedImage, false)); |
1098 | }); |
1099 | |
1100 | auto fixtures_dir = |
1101 | fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead); |
1102 | |
1103 | auto reference_png = fml::FileMapping::CreateReadOnly( |
1104 | fixtures_dir, "shelltest_screenshot.png" ); |
1105 | |
1106 | // Use MakeWithoutCopy instead of MakeWithCString because we don't want to |
1107 | // encode the null sentinel |
1108 | sk_sp<SkData> reference_data = SkData::MakeWithoutCopy( |
1109 | reference_png->GetMapping(), reference_png->GetSize()); |
1110 | |
1111 | ASSERT_TRUE(reference_data->equals(screenshot_future.get().data.get())); |
1112 | |
1113 | DestroyShell(std::move(shell)); |
1114 | } |
1115 | |
1116 | TEST_F(ShellTest, CanConvertToAndFromMappings) { |
1117 | const size_t buffer_size = 2 << 20; |
1118 | |
1119 | uint8_t* buffer = static_cast<uint8_t*>(::malloc(buffer_size)); |
1120 | ASSERT_NE(buffer, nullptr); |
1121 | ASSERT_TRUE(MemsetPatternSetOrCheck( |
1122 | buffer, buffer_size, MemsetPatternOp::kMemsetPatternOpSetBuffer)); |
1123 | |
1124 | std::unique_ptr<fml::Mapping> mapping = |
1125 | std::make_unique<fml::NonOwnedMapping>( |
1126 | buffer, buffer_size, [](const uint8_t* buffer, size_t size) { |
1127 | ::free(const_cast<uint8_t*>(buffer)); |
1128 | }); |
1129 | |
1130 | ASSERT_EQ(mapping->GetSize(), buffer_size); |
1131 | |
1132 | fml::AutoResetWaitableEvent latch; |
1133 | AddNativeCallback( |
1134 | "SendFixtureMapping" , CREATE_NATIVE_ENTRY([&](auto args) { |
1135 | auto mapping_from_dart = |
1136 | tonic::DartConverter<std::unique_ptr<fml::Mapping>>::FromDart( |
1137 | Dart_GetNativeArgument(args, 0)); |
1138 | ASSERT_NE(mapping_from_dart, nullptr); |
1139 | ASSERT_EQ(mapping_from_dart->GetSize(), buffer_size); |
1140 | ASSERT_TRUE(MemsetPatternSetOrCheck( |
1141 | const_cast<uint8_t*>(mapping_from_dart->GetMapping()), // buffer |
1142 | mapping_from_dart->GetSize(), // size |
1143 | MemsetPatternOp::kMemsetPatternOpCheckBuffer // op |
1144 | )); |
1145 | latch.Signal(); |
1146 | })); |
1147 | |
1148 | AddNativeCallback( |
1149 | "GetFixtureMapping" , CREATE_NATIVE_ENTRY([&](auto args) { |
1150 | tonic::DartConverter<tonic::DartConverterMapping>::SetReturnValue( |
1151 | args, mapping); |
1152 | })); |
1153 | |
1154 | auto settings = CreateSettingsForFixture(); |
1155 | auto configuration = RunConfiguration::InferFromSettings(settings); |
1156 | configuration.SetEntrypoint("canConvertMappings" ); |
1157 | std::unique_ptr<Shell> shell = CreateShell(settings); |
1158 | ASSERT_NE(shell.get(), nullptr); |
1159 | RunEngine(shell.get(), std::move(configuration)); |
1160 | latch.Wait(); |
1161 | DestroyShell(std::move(shell)); |
1162 | } |
1163 | |
1164 | // Compares local times as seen by the dart isolate and as seen by this test |
1165 | // fixture, to a resolution of 1 hour. |
1166 | // |
1167 | // This verifies that (1) the isolate is able to get a timezone (doesn't lock |
1168 | // up for example), and (2) that the host and the isolate agree on what the |
1169 | // timezone is. |
1170 | TEST_F(ShellTest, LocaltimesMatch) { |
1171 | fml::AutoResetWaitableEvent latch; |
1172 | std::string dart_isolate_time_str; |
1173 | |
1174 | // See fixtures/shell_test.dart, the callback NotifyLocalTime is declared |
1175 | // there. |
1176 | AddNativeCallback("NotifyLocalTime" , CREATE_NATIVE_ENTRY([&](auto args) { |
1177 | dart_isolate_time_str = |
1178 | tonic::DartConverter<std::string>::FromDart( |
1179 | Dart_GetNativeArgument(args, 0)); |
1180 | latch.Signal(); |
1181 | })); |
1182 | |
1183 | auto settings = CreateSettingsForFixture(); |
1184 | auto configuration = RunConfiguration::InferFromSettings(settings); |
1185 | configuration.SetEntrypoint("localtimesMatch" ); |
1186 | std::unique_ptr<Shell> shell = CreateShell(settings); |
1187 | ASSERT_NE(shell.get(), nullptr); |
1188 | RunEngine(shell.get(), std::move(configuration)); |
1189 | latch.Wait(); |
1190 | |
1191 | char timestr[200]; |
1192 | const time_t timestamp = time(nullptr); |
1193 | const struct tm* local_time = localtime(×tamp); |
1194 | ASSERT_NE(local_time, nullptr) |
1195 | << "Could not get local time: errno=" << errno << ": " << strerror(errno); |
1196 | // Example: "2020-02-26 14" for 2pm on February 26, 2020. |
1197 | const size_t format_size = |
1198 | strftime(timestr, sizeof(timestr), "%Y-%m-%d %H" , local_time); |
1199 | ASSERT_NE(format_size, 0UL) |
1200 | << "strftime failed: host time: " << std::string(timestr) |
1201 | << " dart isolate time: " << dart_isolate_time_str; |
1202 | |
1203 | const std::string host_local_time_str = timestr; |
1204 | |
1205 | ASSERT_EQ(dart_isolate_time_str, host_local_time_str) |
1206 | << "Local times in the dart isolate and the local time seen by the test " |
1207 | << "differ by more than 1 hour, but are expected to be about equal" ; |
1208 | |
1209 | DestroyShell(std::move(shell)); |
1210 | } |
1211 | |
1212 | TEST_F(ShellTest, CanDecompressImageFromAsset) { |
1213 | fml::AutoResetWaitableEvent latch; |
1214 | AddNativeCallback("NotifyWidthHeight" , CREATE_NATIVE_ENTRY([&](auto args) { |
1215 | auto width = tonic::DartConverter<int>::FromDart( |
1216 | Dart_GetNativeArgument(args, 0)); |
1217 | auto height = tonic::DartConverter<int>::FromDart( |
1218 | Dart_GetNativeArgument(args, 1)); |
1219 | ASSERT_EQ(width, 100); |
1220 | ASSERT_EQ(height, 100); |
1221 | latch.Signal(); |
1222 | })); |
1223 | |
1224 | AddNativeCallback( |
1225 | "GetFixtureImage" , CREATE_NATIVE_ENTRY([](auto args) { |
1226 | auto fixture = OpenFixtureAsMapping("shelltest_screenshot.png" ); |
1227 | tonic::DartConverter<tonic::DartConverterMapping>::SetReturnValue( |
1228 | args, fixture); |
1229 | })); |
1230 | |
1231 | auto settings = CreateSettingsForFixture(); |
1232 | auto configuration = RunConfiguration::InferFromSettings(settings); |
1233 | configuration.SetEntrypoint("canDecompressImageFromAsset" ); |
1234 | std::unique_ptr<Shell> shell = CreateShell(settings); |
1235 | ASSERT_NE(shell.get(), nullptr); |
1236 | RunEngine(shell.get(), std::move(configuration)); |
1237 | latch.Wait(); |
1238 | DestroyShell(std::move(shell)); |
1239 | } |
1240 | |
1241 | TEST_F(ShellTest, OnServiceProtocolGetSkSLsWorks) { |
1242 | fml::ScopedTemporaryDirectory base_dir; |
1243 | ASSERT_TRUE(base_dir.fd().is_valid()); |
1244 | PersistentCache::SetCacheDirectoryPath(base_dir.path()); |
1245 | PersistentCache::ResetCacheForProcess(); |
1246 | |
1247 | // Create 2 dummy SkSL cache file IE (base32 encoding of A), II (base32 |
1248 | // encoding of B) with content x and y. |
1249 | std::vector<std::string> components = { |
1250 | "flutter_engine" , GetFlutterEngineVersion(), "skia" , GetSkiaVersion(), |
1251 | PersistentCache::kSkSLSubdirName}; |
1252 | auto sksl_dir = fml::CreateDirectory(base_dir.fd(), components, |
1253 | fml::FilePermission::kReadWrite); |
1254 | const std::string x = "x" ; |
1255 | const std::string y = "y" ; |
1256 | auto x_data = std::make_unique<fml::DataMapping>( |
1257 | std::vector<uint8_t>{x.begin(), x.end()}); |
1258 | auto y_data = std::make_unique<fml::DataMapping>( |
1259 | std::vector<uint8_t>{y.begin(), y.end()}); |
1260 | ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "IE" , *x_data)); |
1261 | ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "II" , *y_data)); |
1262 | |
1263 | Settings settings = CreateSettingsForFixture(); |
1264 | std::unique_ptr<Shell> shell = CreateShell(settings); |
1265 | ServiceProtocol::Handler::ServiceProtocolMap empty_params; |
1266 | rapidjson::Document document; |
1267 | OnServiceProtocol(shell.get(), ServiceProtocolEnum::kGetSkSLs, |
1268 | shell->GetTaskRunners().GetIOTaskRunner(), empty_params, |
1269 | &document); |
1270 | rapidjson::StringBuffer buffer; |
1271 | rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
1272 | document.Accept(writer); |
1273 | DestroyShell(std::move(shell)); |
1274 | |
1275 | const std::string expected_json1 = |
1276 | "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"II\":\"eQ==\",\"IE\":\"eA==\"}}" ; |
1277 | const std::string expected_json2 = |
1278 | "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"IE\":\"eA==\",\"II\":\"eQ==\"}}" ; |
1279 | bool json_is_expected = (expected_json1 == buffer.GetString()) || |
1280 | (expected_json2 == buffer.GetString()); |
1281 | ASSERT_TRUE(json_is_expected) << buffer.GetString() << " is not equal to " |
1282 | << expected_json1 << " or " << expected_json2; |
1283 | } |
1284 | |
1285 | TEST_F(ShellTest, RasterizerScreenshot) { |
1286 | Settings settings = CreateSettingsForFixture(); |
1287 | auto configuration = RunConfiguration::InferFromSettings(settings); |
1288 | auto task_runner = CreateNewThread(); |
1289 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
1290 | task_runner); |
1291 | std::unique_ptr<Shell> shell = |
1292 | CreateShell(std::move(settings), std::move(task_runners)); |
1293 | |
1294 | ASSERT_TRUE(ValidateShell(shell.get())); |
1295 | PlatformViewNotifyCreated(shell.get()); |
1296 | |
1297 | RunEngine(shell.get(), std::move(configuration)); |
1298 | |
1299 | auto latch = std::make_shared<fml::AutoResetWaitableEvent>(); |
1300 | |
1301 | PumpOneFrame(shell.get()); |
1302 | |
1303 | fml::TaskRunner::RunNowOrPostTask( |
1304 | shell->GetTaskRunners().GetRasterTaskRunner(), [&shell, &latch]() { |
1305 | Rasterizer::Screenshot screenshot = |
1306 | shell->GetRasterizer()->ScreenshotLastLayerTree( |
1307 | Rasterizer::ScreenshotType::CompressedImage, true); |
1308 | EXPECT_NE(screenshot.data, nullptr); |
1309 | |
1310 | latch->Signal(); |
1311 | }); |
1312 | latch->Wait(); |
1313 | DestroyShell(std::move(shell), std::move(task_runners)); |
1314 | } |
1315 | |
1316 | TEST_F(ShellTest, RasterizerMakeRasterSnapshot) { |
1317 | Settings settings = CreateSettingsForFixture(); |
1318 | auto configuration = RunConfiguration::InferFromSettings(settings); |
1319 | auto task_runner = CreateNewThread(); |
1320 | TaskRunners task_runners("test" , task_runner, task_runner, task_runner, |
1321 | task_runner); |
1322 | std::unique_ptr<Shell> shell = |
1323 | CreateShell(std::move(settings), std::move(task_runners)); |
1324 | |
1325 | ASSERT_TRUE(ValidateShell(shell.get())); |
1326 | PlatformViewNotifyCreated(shell.get()); |
1327 | |
1328 | RunEngine(shell.get(), std::move(configuration)); |
1329 | |
1330 | auto latch = std::make_shared<fml::AutoResetWaitableEvent>(); |
1331 | |
1332 | PumpOneFrame(shell.get()); |
1333 | |
1334 | fml::TaskRunner::RunNowOrPostTask( |
1335 | shell->GetTaskRunners().GetRasterTaskRunner(), [&shell, &latch]() { |
1336 | SnapshotDelegate* delegate = |
1337 | reinterpret_cast<Rasterizer*>(shell->GetRasterizer().get()); |
1338 | sk_sp<SkImage> image = delegate->MakeRasterSnapshot( |
1339 | SkPicture::MakePlaceholder({0, 0, 50, 50}), SkISize::Make(50, 50)); |
1340 | EXPECT_NE(image, nullptr); |
1341 | |
1342 | latch->Signal(); |
1343 | }); |
1344 | latch->Wait(); |
1345 | DestroyShell(std::move(shell), std::move(task_runners)); |
1346 | } |
1347 | |
1348 | static sk_sp<SkPicture> MakeSizedPicture(int width, int height) { |
1349 | SkPictureRecorder recorder; |
1350 | SkCanvas* recording_canvas = |
1351 | recorder.beginRecording(SkRect::MakeXYWH(0, 0, width, height)); |
1352 | recording_canvas->drawRect(SkRect::MakeXYWH(0, 0, width, height), |
1353 | SkPaint(SkColor4f::FromColor(SK_ColorRED))); |
1354 | return recorder.finishRecordingAsPicture(); |
1355 | } |
1356 | |
1357 | TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { |
1358 | Settings settings = CreateSettingsForFixture(); |
1359 | std::unique_ptr<Shell> shell = CreateShell(settings); |
1360 | |
1361 | // 1. Construct a picture and a picture layer to be raster cached. |
1362 | sk_sp<SkPicture> picture = MakeSizedPicture(10, 10); |
1363 | fml::RefPtr<SkiaUnrefQueue> queue = fml::MakeRefCounted<SkiaUnrefQueue>( |
1364 | GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); |
1365 | auto picture_layer = std::make_shared<PictureLayer>( |
1366 | SkPoint::Make(0, 0), |
1367 | flutter::SkiaGPUObject<SkPicture>({MakeSizedPicture(100, 100), queue}), |
1368 | false, false); |
1369 | picture_layer->set_paint_bounds(SkRect::MakeWH(100, 100)); |
1370 | |
1371 | // 2. Rasterize the picture and the picture layer in the raster cache. |
1372 | std::promise<bool> rasterized; |
1373 | shell->GetTaskRunners().GetRasterTaskRunner()->PostTask( |
1374 | [&shell, &rasterized, &picture, &picture_layer] { |
1375 | auto* compositor_context = shell->GetRasterizer()->compositor_context(); |
1376 | auto& raster_cache = compositor_context->raster_cache(); |
1377 | // 2.1. Rasterize the picture. Call Draw multiple times to pass the |
1378 | // access threshold (default to 3) so a cache can be generated. |
1379 | SkCanvas dummy_canvas; |
1380 | bool picture_cache_generated; |
1381 | for (int i = 0; i < 4; i += 1) { |
1382 | picture_cache_generated = |
1383 | raster_cache.Prepare(nullptr, // GrDirectContext |
1384 | picture.get(), SkMatrix::I(), |
1385 | nullptr, // SkColorSpace |
1386 | true, // isComplex |
1387 | false // willChange |
1388 | ); |
1389 | raster_cache.Draw(*picture, dummy_canvas); |
1390 | } |
1391 | ASSERT_TRUE(picture_cache_generated); |
1392 | |
1393 | // 2.2. Rasterize the picture layer. |
1394 | Stopwatch raster_time; |
1395 | Stopwatch ui_time; |
1396 | MutatorsStack mutators_stack; |
1397 | TextureRegistry texture_registry; |
1398 | PrerollContext preroll_context = { |
1399 | nullptr, /* raster_cache */ |
1400 | nullptr, /* gr_context */ |
1401 | nullptr, /* external_view_embedder */ |
1402 | mutators_stack, nullptr, /* color_space */ |
1403 | kGiantRect, /* cull_rect */ |
1404 | false, /* layer reads from surface */ |
1405 | raster_time, ui_time, texture_registry, |
1406 | false, /* checkerboard_offscreen_layers */ |
1407 | 1.0f, /* frame_device_pixel_ratio */ |
1408 | false, /* has_platform_view */ |
1409 | }; |
1410 | raster_cache.Prepare(&preroll_context, picture_layer.get(), |
1411 | SkMatrix::I()); |
1412 | rasterized.set_value(true); |
1413 | }); |
1414 | rasterized.get_future().wait(); |
1415 | |
1416 | // 3. Call the service protocol and check its output. |
1417 | ServiceProtocol::Handler::ServiceProtocolMap empty_params; |
1418 | rapidjson::Document document; |
1419 | OnServiceProtocol( |
1420 | shell.get(), ServiceProtocolEnum::kEstimateRasterCacheMemory, |
1421 | shell->GetTaskRunners().GetRasterTaskRunner(), empty_params, &document); |
1422 | rapidjson::StringBuffer buffer; |
1423 | rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
1424 | document.Accept(writer); |
1425 | std::string expected_json = |
1426 | "{\"type\":\"EstimateRasterCacheMemory\",\"layerBytes\":40000,\"picture" |
1427 | "Bytes\":400}" ; |
1428 | std::string actual_json = buffer.GetString(); |
1429 | ASSERT_EQ(actual_json, expected_json); |
1430 | |
1431 | DestroyShell(std::move(shell)); |
1432 | } |
1433 | |
1434 | } // namespace testing |
1435 | } // namespace flutter |
1436 | |