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
26namespace flutter {
27namespace testing {
28
29#if defined(OS_FUCHSIA)
30
31static 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
38class 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
192TEST_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
206TEST_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