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 | |
5 | #include <memory> |
6 | |
7 | #include "flutter/assets/directory_asset_bundle.h" |
8 | #include "flutter/flow/layers/container_layer.h" |
9 | #include "flutter/flow/layers/layer.h" |
10 | #include "flutter/flow/layers/physical_shape_layer.h" |
11 | #include "flutter/flow/layers/picture_layer.h" |
12 | #include "flutter/fml/command_line.h" |
13 | #include "flutter/fml/file.h" |
14 | #include "flutter/fml/log_settings.h" |
15 | #include "flutter/fml/unique_fd.h" |
16 | #include "flutter/shell/common/persistent_cache.h" |
17 | #include "flutter/shell/common/serialization_callbacks.h" |
18 | #include "flutter/shell/common/shell_test.h" |
19 | #include "flutter/shell/common/switches.h" |
20 | #include "flutter/shell/version/version.h" |
21 | #include "flutter/testing/testing.h" |
22 | #include "include/core/SkPicture.h" |
23 | #include "include/core/SkPictureRecorder.h" |
24 | #include "include/core/SkSerialProcs.h" |
25 | |
26 | namespace flutter { |
27 | namespace testing { |
28 | |
29 | #if defined(OS_FUCHSIA) |
30 | |
31 | static void WaitForIO(Shell* shell) { |
32 | std::promise<bool> io_task_finished; |
33 | shell->GetTaskRunners().GetIOTaskRunner()->PostTask( |
34 | [&io_task_finished]() { io_task_finished.set_value(true); }); |
35 | io_task_finished.get_future().wait(); |
36 | } |
37 | |
38 | class SkpWarmupTest : public ShellTest { |
39 | public: |
40 | SkpWarmupTest() {} |
41 | |
42 | void TestWarmup(const SkISize& draw_size, const LayerTreeBuilder& builder) { |
43 | // Create a temp dir to store the persistent cache |
44 | fml::ScopedTemporaryDirectory dir; |
45 | PersistentCache::SetCacheDirectoryPath(dir.path()); |
46 | PersistentCache::ResetCacheForProcess(); |
47 | |
48 | auto settings = CreateSettingsForFixture(); |
49 | settings.cache_sksl = true; |
50 | settings.dump_skp_on_shader_compilation = true; |
51 | |
52 | fml::AutoResetWaitableEvent firstFrameLatch; |
53 | settings.frame_rasterized_callback = |
54 | [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); }; |
55 | |
56 | auto config = RunConfiguration::InferFromSettings(settings); |
57 | config.SetEntrypoint("emptyMain" ); |
58 | std::unique_ptr<Shell> shell = CreateShell(settings); |
59 | PlatformViewNotifyCreated(shell.get()); |
60 | RunEngine(shell.get(), std::move(config)); |
61 | |
62 | // Initially, we should have no SkSL cache |
63 | auto cache = PersistentCache::GetCacheForProcess()->LoadSkSLs(); |
64 | ASSERT_EQ(cache.size(), 0u); |
65 | |
66 | PumpOneFrame(shell.get(), draw_size.width(), draw_size.height(), builder); |
67 | firstFrameLatch.Wait(); |
68 | WaitForIO(shell.get()); |
69 | |
70 | // Count the number of shaders this builder generated. We use this as a |
71 | // proxy for whether new shaders were generated, since skia will dump an skp |
72 | // any time a new shader is compiled. |
73 | int skp_count = 0; |
74 | fml::FileVisitor skp_count_visitor = [&skp_count]( |
75 | const fml::UniqueFD& directory, |
76 | const std::string& filename) { |
77 | if (filename.size() >= 4 && |
78 | filename.substr(filename.size() - 4, 4) == ".skp" ) { |
79 | skp_count += 1; |
80 | } |
81 | return true; |
82 | }; |
83 | fml::VisitFilesRecursively(dir.fd(), skp_count_visitor); |
84 | int first_skp_count = skp_count; |
85 | skp_count = 0; |
86 | ASSERT_GT(first_skp_count, 0); |
87 | |
88 | // Deserialize all skps into memory |
89 | std::vector<sk_sp<SkPicture>> pictures; |
90 | fml::FileVisitor skp_deserialize_visitor = |
91 | [&pictures](const fml::UniqueFD& directory, |
92 | const std::string& filename) { |
93 | if (filename.size() >= 4 && |
94 | filename.substr(filename.size() - 4, 4) == ".skp" ) { |
95 | auto fd = fml::OpenFileReadOnly(directory, filename.c_str()); |
96 | if (fd.get() < 0) { |
97 | FML_LOG(ERROR) << "Failed to open " << filename; |
98 | return true; |
99 | } |
100 | // Deserialize |
101 | sk_sp<SkData> data = SkData::MakeFromFD(fd.get()); |
102 | std::unique_ptr<SkMemoryStream> stream = SkMemoryStream::Make(data); |
103 | |
104 | SkDeserialProcs procs = {0}; |
105 | procs.fImageProc = DeserializeImageWithoutData; |
106 | sk_sp<SkPicture> picture = |
107 | SkPicture::MakeFromStream(stream.get(), &procs); |
108 | pictures.push_back(std::move(picture)); |
109 | fd.reset(); |
110 | } |
111 | return true; |
112 | }; |
113 | fml::VisitFilesRecursively(dir.fd(), skp_deserialize_visitor); |
114 | ASSERT_GT(pictures.size(), 0ul); |
115 | |
116 | // Reinitialize shell with clean cache and verify that drawing again dumps |
117 | // the same number of shaders |
118 | fml::RemoveFilesInDirectory(dir.fd()); |
119 | PersistentCache::ResetCacheForProcess(); |
120 | DestroyShell(std::move(shell)); |
121 | auto config2 = RunConfiguration::InferFromSettings(settings); |
122 | config2.SetEntrypoint("emptyMain" ); |
123 | shell = CreateShell(settings); |
124 | PlatformViewNotifyCreated(shell.get()); |
125 | RunEngine(shell.get(), std::move(config2)); |
126 | firstFrameLatch.Reset(); |
127 | PumpOneFrame(shell.get(), draw_size.width(), draw_size.height(), builder); |
128 | firstFrameLatch.Wait(); |
129 | WaitForIO(shell.get()); |
130 | |
131 | // Verify same number of shaders dumped |
132 | fml::VisitFilesRecursively(dir.fd(), skp_count_visitor); |
133 | int second_skp_count = skp_count; |
134 | skp_count = 0; |
135 | ASSERT_EQ(second_skp_count, first_skp_count); |
136 | |
137 | // Reinitialize shell and draw deserialized skps to warm up shaders |
138 | fml::RemoveFilesInDirectory(dir.fd()); |
139 | PersistentCache::ResetCacheForProcess(); |
140 | DestroyShell(std::move(shell)); |
141 | auto config3 = RunConfiguration::InferFromSettings(settings); |
142 | config3.SetEntrypoint("emptyMain" ); |
143 | shell = CreateShell(settings); |
144 | PlatformViewNotifyCreated(shell.get()); |
145 | RunEngine(shell.get(), std::move(config3)); |
146 | firstFrameLatch.Reset(); |
147 | |
148 | for (auto& picture : pictures) { |
149 | fml::RefPtr<SkiaUnrefQueue> queue = fml::MakeRefCounted<SkiaUnrefQueue>( |
150 | this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); |
151 | LayerTreeBuilder picture_builder = |
152 | [picture, queue](std::shared_ptr<ContainerLayer> root) { |
153 | auto picture_layer = std::make_shared<PictureLayer>( |
154 | SkPoint::Make(0, 0), SkiaGPUObject<SkPicture>(picture, queue), |
155 | /* is_complex */ false, |
156 | /* will_change */ false); |
157 | root->Add(picture_layer); |
158 | }; |
159 | PumpOneFrame(shell.get(), picture->cullRect().width(), |
160 | picture->cullRect().height(), picture_builder); |
161 | } |
162 | firstFrameLatch.Wait(); |
163 | WaitForIO(shell.get()); |
164 | |
165 | // Verify same number of shaders dumped |
166 | fml::VisitFilesRecursively(dir.fd(), skp_count_visitor); |
167 | int third_skp_count = skp_count; |
168 | skp_count = 0; |
169 | ASSERT_EQ(third_skp_count, first_skp_count); |
170 | |
171 | // Remove files generated |
172 | fml::RemoveFilesInDirectory(dir.fd()); |
173 | |
174 | // Draw orignal material again |
175 | firstFrameLatch.Reset(); |
176 | PumpOneFrame(shell.get(), draw_size.width(), draw_size.height(), builder); |
177 | |
178 | firstFrameLatch.Wait(); |
179 | WaitForIO(shell.get()); |
180 | |
181 | // Verify no new shaders dumped |
182 | fml::VisitFilesRecursively(dir.fd(), skp_count_visitor); |
183 | int fourth_skp_count = skp_count; |
184 | skp_count = 0; |
185 | ASSERT_EQ(fourth_skp_count, 0); |
186 | |
187 | // Clean Up |
188 | fml::RemoveFilesInDirectory(dir.fd()); |
189 | } |
190 | }; |
191 | |
192 | TEST_F(SkpWarmupTest, Basic) { |
193 | SkISize draw_size = SkISize::Make(100, 100); |
194 | // Draw something to trigger shader compilations. |
195 | LayerTreeBuilder builder = |
196 | [&draw_size](std::shared_ptr<ContainerLayer> root) { |
197 | SkPath path; |
198 | path.addCircle(draw_size.width() / 2, draw_size.height() / 2, 20); |
199 | auto physical_shape_layer = std::make_shared<PhysicalShapeLayer>( |
200 | SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias); |
201 | root->Add(physical_shape_layer); |
202 | }; |
203 | TestWarmup(draw_size, builder); |
204 | } |
205 | |
206 | TEST_F(SkpWarmupTest, Image) { |
207 | SkISize draw_size = SkISize::Make(100, 100); |
208 | // We reuse this builder to draw the same content sever times in this test |
209 | LayerTreeBuilder builder = [&draw_size, |
210 | this](std::shared_ptr<ContainerLayer> root) { |
211 | SkPictureRecorder recorder; |
212 | auto canvas = |
213 | recorder.beginRecording(draw_size.width(), draw_size.height()); |
214 | |
215 | // include an image so we can test that the warmup works even with image |
216 | // data excluded from the skp |
217 | auto image_size = |
218 | SkISize::Make(draw_size.width() / 2, draw_size.height() / 2); |
219 | auto color_space = SkColorSpace::MakeSRGB(); |
220 | auto info = |
221 | SkImageInfo::Make(image_size, SkColorType::kRGBA_8888_SkColorType, |
222 | SkAlphaType::kPremul_SkAlphaType, color_space); |
223 | sk_sp<SkData> image_data = |
224 | SkData::MakeUninitialized(image_size.width() * image_size.height() * 4); |
225 | memset(image_data->writable_data(), 0x0f, image_data->size()); |
226 | sk_sp<SkImage> image = |
227 | SkImage::MakeRasterData(info, image_data, image_size.width() * 4); |
228 | |
229 | canvas->drawImage(image, image_size.width(), image_size.height()); |
230 | |
231 | auto picture = recorder.finishRecordingAsPicture(); |
232 | |
233 | fml::RefPtr<SkiaUnrefQueue> queue = fml::MakeRefCounted<SkiaUnrefQueue>( |
234 | this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); |
235 | auto picture_layer = std::make_shared<PictureLayer>( |
236 | SkPoint::Make(0, 0), SkiaGPUObject<SkPicture>(picture, queue), |
237 | /* is_complex */ false, |
238 | /* will_change */ false); |
239 | root->Add(picture_layer); |
240 | }; |
241 | |
242 | TestWarmup(draw_size, builder); |
243 | } |
244 | |
245 | #endif |
246 | |
247 | } // namespace testing |
248 | } // namespace flutter |
249 | |