1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/core/SkCanvas.h"
9 #include "include/core/SkGraphics.h"
10 #include "include/core/SkPictureRecorder.h"
11 #include "include/core/SkStream.h"
12 #include "include/core/SkSurface.h"
13 #include "include/encode/SkPngEncoder.h"
14 #include "include/private/SkTPin.h"
15 #include "modules/skottie/include/Skottie.h"
16 #include "modules/skottie/utils/SkottieUtils.h"
17 #include "modules/skresources/include/SkResources.h"
18 #include "src/core/SkOSFile.h"
19 #include "src/core/SkTaskGroup.h"
20 #include "src/utils/SkOSPath.h"
21 #include "tools/flags/CommandLineFlags.h"
22 
23 #include <algorithm>
24 #include <chrono>
25 #include <future>
26 #include <numeric>
27 #include <vector>
28 
29 #if defined(HAVE_VIDEO_ENCODER)
30     #include "experimental/ffmpeg/SkVideoEncoder.h"
31     const char* formats_help = "Output format (png, skp, mp4, or null)";
32 #else
33     const char* formats_help = "Output format (png, skp, or null)";
34 #endif
35 
36 static DEFINE_string2(input    , i, nullptr, "Input .json file.");
37 static DEFINE_string2(writePath, w, nullptr, "Output directory.  Frames are names [0-9]{6}.png.");
38 static DEFINE_string2(format   , f, "png"  , formats_help);
39 
40 static DEFINE_double(t0,    0, "Timeline start [0..1].");
41 static DEFINE_double(t1,    1, "Timeline stop [0..1].");
42 static DEFINE_double(fps,   0, "Decode frames per second (default is animation native fps).");
43 
44 static DEFINE_int(width , 800, "Render width.");
45 static DEFINE_int(height, 600, "Render height.");
46 static DEFINE_int(threads,  0, "Number of worker threads (0 -> cores count).");
47 
48 namespace {
49 
50 static constexpr SkColor kClearColor = SK_ColorWHITE;
51 
MakeFrameStream(size_t idx,const char * ext)52 std::unique_ptr<SkFILEWStream> MakeFrameStream(size_t idx, const char* ext) {
53     const auto frame_file = SkStringPrintf("0%06zu.%s", idx, ext);
54     auto stream = std::make_unique<SkFILEWStream>(SkOSPath::Join(FLAGS_writePath[0],
55                                                                    frame_file.c_str()).c_str());
56     if (!stream->isValid()) {
57         return nullptr;
58     }
59 
60     return stream;
61 }
62 
63 class Sink {
64 public:
65     Sink() = default;
66     virtual ~Sink() = default;
67     Sink(const Sink&) = delete;
68     Sink& operator=(const Sink&) = delete;
69 
70     virtual SkCanvas* beginFrame(size_t idx) = 0;
71     virtual bool endFrame(size_t idx) = 0;
72 };
73 
74 class PNGSink final : public Sink {
75 public:
Make(const SkMatrix & scale_matrix)76     static std::unique_ptr<Sink> Make(const SkMatrix& scale_matrix) {
77         auto surface = SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height);
78         if (!surface) {
79             SkDebugf("Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height);
80             return nullptr;
81         }
82 
83         return std::unique_ptr<Sink>(new PNGSink(std::move(surface), scale_matrix));
84     }
85 
86 private:
PNGSink(sk_sp<SkSurface> surface,const SkMatrix & scale_matrix)87     PNGSink(sk_sp<SkSurface> surface, const SkMatrix& scale_matrix)
88         : fSurface(std::move(surface)) {
89         fSurface->getCanvas()->concat(scale_matrix);
90     }
91 
beginFrame(size_t)92     SkCanvas* beginFrame(size_t) override {
93         auto* canvas = fSurface->getCanvas();
94         canvas->clear(kClearColor);
95         return canvas;
96     }
97 
endFrame(size_t idx)98     bool endFrame(size_t idx) override {
99         auto stream = MakeFrameStream(idx, "png");
100         if (!stream) {
101             return false;
102         }
103 
104         // Set encoding options to favor speed over size.
105         SkPngEncoder::Options options;
106         options.fZLibLevel   = 1;
107         options.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
108 
109         sk_sp<SkImage> img = fSurface->makeImageSnapshot();
110         SkPixmap pixmap;
111         return img->peekPixels(&pixmap)
112             && SkPngEncoder::Encode(stream.get(), pixmap, options);
113     }
114 
115     const sk_sp<SkSurface> fSurface;
116 };
117 
118 class SKPSink final : public Sink {
119 public:
Make(const SkMatrix & scale_matrix)120     static std::unique_ptr<Sink> Make(const SkMatrix& scale_matrix) {
121         return std::unique_ptr<Sink>(new SKPSink(scale_matrix));
122     }
123 
124 private:
SKPSink(const SkMatrix & scale_matrix)125     explicit SKPSink(const SkMatrix& scale_matrix)
126         : fScaleMatrix(scale_matrix) {}
127 
beginFrame(size_t)128     SkCanvas* beginFrame(size_t) override {
129         auto canvas = fRecorder.beginRecording(FLAGS_width, FLAGS_height);
130         canvas->concat(fScaleMatrix);
131         return canvas;
132     }
133 
endFrame(size_t idx)134     bool endFrame(size_t idx) override {
135         auto stream = MakeFrameStream(idx, "skp");
136         if (!stream) {
137             return false;
138         }
139 
140         fRecorder.finishRecordingAsPicture()->serialize(stream.get());
141         return true;
142     }
143 
144     const SkMatrix    fScaleMatrix;
145     SkPictureRecorder fRecorder;
146 };
147 
148 class NullSink final : public Sink {
149 public:
Make(const SkMatrix & scale_matrix)150     static std::unique_ptr<Sink> Make(const SkMatrix& scale_matrix) {
151         auto surface = SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height);
152         if (!surface) {
153             SkDebugf("Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height);
154             return nullptr;
155         }
156 
157         return std::unique_ptr<Sink>(new NullSink(std::move(surface), scale_matrix));
158     }
159 
160 private:
NullSink(sk_sp<SkSurface> surface,const SkMatrix & scale_matrix)161     NullSink(sk_sp<SkSurface> surface, const SkMatrix& scale_matrix)
162         : fSurface(std::move(surface)) {
163         fSurface->getCanvas()->concat(scale_matrix);
164     }
165 
beginFrame(size_t)166     SkCanvas* beginFrame(size_t) override {
167         auto* canvas = fSurface->getCanvas();
168         canvas->clear(kClearColor);
169         return canvas;
170     }
171 
endFrame(size_t)172     bool endFrame(size_t) override {
173         return true;
174     }
175 
176     const sk_sp<SkSurface> fSurface;
177 };
178 
179 static std::vector<std::promise<sk_sp<SkImage>>> gMP4Frames;
180 
181 struct MP4Sink final : public Sink {
MP4Sink__anon886712ab0111::MP4Sink182     explicit MP4Sink(const SkMatrix& scale_matrix)
183         : fSurface(SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height)) {
184         fSurface->getCanvas()->concat(scale_matrix);
185     }
186 
beginFrame__anon886712ab0111::MP4Sink187     SkCanvas* beginFrame(size_t) override {
188         SkCanvas* canvas = fSurface->getCanvas();
189         canvas->clear(kClearColor);
190         return canvas;
191     }
192 
endFrame__anon886712ab0111::MP4Sink193     bool endFrame(size_t i) override {
194         if (sk_sp<SkImage> img = fSurface->makeImageSnapshot()) {
195             gMP4Frames[i].set_value(std::move(img));
196             return true;
197         }
198         return false;
199     }
200 
201     const sk_sp<SkSurface> fSurface;
202 };
203 
204 class Logger final : public skottie::Logger {
205 public:
206     struct LogEntry {
207         SkString fMessage,
208                  fJSON;
209     };
210 
log(skottie::Logger::Level lvl,const char message[],const char json[])211     void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
212         auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
213         log.push_back({ SkString(message), json ? SkString(json) : SkString() });
214     }
215 
report() const216     void report() const {
217         SkDebugf("Animation loaded with %lu error%s, %lu warning%s.\n",
218                  fErrors.size(), fErrors.size() == 1 ? "" : "s",
219                  fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
220 
221         const auto& show = [](const LogEntry& log, const char prefix[]) {
222             SkDebugf("%s%s", prefix, log.fMessage.c_str());
223             if (!log.fJSON.isEmpty())
224                 SkDebugf(" : %s", log.fJSON.c_str());
225             SkDebugf("\n");
226         };
227 
228         for (const auto& err : fErrors)   show(err, "  !! ");
229         for (const auto& wrn : fWarnings) show(wrn, "  ?? ");
230     }
231 
232 private:
233     std::vector<LogEntry> fErrors,
234                           fWarnings;
235 };
236 
MakeSink(const char * fmt,const SkMatrix & scale_matrix)237 std::unique_ptr<Sink> MakeSink(const char* fmt, const SkMatrix& scale_matrix) {
238     if (0 == strcmp(fmt,  "png")) return  PNGSink::Make(scale_matrix);
239     if (0 == strcmp(fmt,  "skp")) return  SKPSink::Make(scale_matrix);
240     if (0 == strcmp(fmt, "null")) return NullSink::Make(scale_matrix);
241     if (0 == strcmp(fmt,  "mp4")) return std::make_unique<MP4Sink>(scale_matrix);
242 
243     SkDebugf("Unknown format: %s\n", FLAGS_format[0]);
244     return nullptr;
245 }
246 
247 } // namespace
248 
249 extern bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental;
250 
main(int argc,char ** argv)251 int main(int argc, char** argv) {
252     gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = true;
253     CommandLineFlags::Parse(argc, argv);
254     SkAutoGraphics ag;
255 
256     if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) {
257         SkDebugf("Missing required 'input' and 'writePath' args.\n");
258         return 1;
259     }
260 
261     if (!FLAGS_format.contains("mp4") && !sk_mkdir(FLAGS_writePath[0])) {
262         return 1;
263     }
264 
265     auto logger = sk_make_sp<Logger>();
266     auto     rp = skresources::CachingResourceProvider::Make(
267                     skresources::DataURIResourceProviderProxy::Make(
268                       skresources::FileResourceProvider::Make(SkOSPath::Dirname(FLAGS_input[0]),
269                                                                 /*predecode=*/true),
270                       /*predecode=*/true));
271     auto data   = SkData::MakeFromFileName(FLAGS_input[0]);
272     auto precomp_interceptor =
273             sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(rp, "__");
274 
275     if (!data) {
276         SkDebugf("Could not load %s.\n", FLAGS_input[0]);
277         return 1;
278     }
279 
280     // Instantiate an animation on the main thread for two reasons:
281     //   - we need to know its duration upfront
282     //   - we want to only report parsing errors once
283     auto anim = skottie::Animation::Builder()
284             .setLogger(logger)
285             .setResourceProvider(rp)
286             .make(static_cast<const char*>(data->data()), data->size());
287     if (!anim) {
288         SkDebugf("Could not parse animation: '%s'.\n", FLAGS_input[0]);
289         return 1;
290     }
291 
292     const auto scale_matrix = SkMatrix::RectToRect(SkRect::MakeSize(anim->size()),
293                                                    SkRect::MakeIWH(FLAGS_width, FLAGS_height),
294                                                    SkMatrix::kCenter_ScaleToFit);
295     logger->report();
296 
297     const auto t0 = SkTPin(FLAGS_t0, 0.0, 1.0),
298                t1 = SkTPin(FLAGS_t1,  t0, 1.0),
299        native_fps = anim->fps(),
300            frame0 = anim->duration() * t0 * native_fps,
301          duration = anim->duration() * (t1 - t0);
302 
303     double fps = FLAGS_fps > 0 ? FLAGS_fps : native_fps;
304     if (fps <= 0) {
305         SkDebugf("Invalid fps: %f.\n", fps);
306         return 1;
307     }
308 
309     auto frame_count = static_cast<int>(duration * fps);
310     static constexpr int kMaxFrames = 10000;
311     if (frame_count > kMaxFrames) {
312         frame_count = kMaxFrames;
313         fps = frame_count / duration;
314     }
315     const auto fps_scale = native_fps / fps;
316 
317     SkDebugf("Rendering %f seconds (%d frames @%f fps).\n", duration, frame_count, fps);
318 
319     if (FLAGS_format.contains("mp4")) {
320         gMP4Frames.resize(frame_count);
321     }
322 
323     std::vector<double> frames_ms(frame_count);
324 
325     auto ms_since = [](auto start) {
326         const auto elapsed = std::chrono::steady_clock::now() - start;
327         return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
328     };
329 
330     SkTaskGroup::Enabler enabler(FLAGS_threads - 1);
331 
332     SkTaskGroup tg;
333     tg.batch(frame_count, [&](int i) {
334         // SkTaskGroup::Enabler creates a LIFO work pool,
335         // but we want our early frames to start first.
336         i = frame_count - 1 - i;
337 
338         const auto start = std::chrono::steady_clock::now();
339 #if defined(SK_BUILD_FOR_IOS)
340         // iOS doesn't support thread_local on versions less than 9.0.
341         auto anim = skottie::Animation::Builder()
342                             .setResourceProvider(rp)
343                             .setPrecompInterceptor(precomp_interceptor)
344                             .make(static_cast<const char*>(data->data()), data->size());
345         auto sink = MakeSink(FLAGS_format[0], scale_matrix);
346 #else
347         thread_local static auto* anim =
348                 skottie::Animation::Builder()
349                     .setResourceProvider(rp)
350                     .setPrecompInterceptor(precomp_interceptor)
351                     .make(static_cast<const char*>(data->data()), data->size())
352                     .release();
353         thread_local static auto* sink = MakeSink(FLAGS_format[0], scale_matrix).release();
354 #endif
355 
356         if (sink && anim) {
357             anim->seekFrame(frame0 + i * fps_scale);
358             anim->render(sink->beginFrame(i));
359             sink->endFrame(i);
360         }
361 
362         frames_ms[i] = ms_since(start);
363     });
364 
365 #if defined(HAVE_VIDEO_ENCODER)
366     if (FLAGS_format.contains("mp4")) {
367         SkVideoEncoder enc;
368         if (!enc.beginRecording({FLAGS_width, FLAGS_height}, fps)) {
369             SkDEBUGF("Invalid video stream configuration.\n");
370             return -1;
371         }
372 
373         std::vector<double> starved_ms;
374         for (std::promise<sk_sp<SkImage>>& frame : gMP4Frames) {
375             const auto start = std::chrono::steady_clock::now();
376             sk_sp<SkImage> img = frame.get_future().get();
377             starved_ms.push_back(ms_since(start));
378 
379             SkPixmap pm;
380             SkAssertResult(img->peekPixels(&pm));
381             enc.addFrame(pm);
382         }
383         sk_sp<SkData> mp4 = enc.endRecording();
384 
385         SkFILEWStream{FLAGS_writePath[0]}
386             .write(mp4->data(), mp4->size());
387 
388         // If everything's going well, the first frame should account for the most,
389         // and ideally nearly all, starvation.
390         double first = starved_ms[0];
391         std::sort(starved_ms.begin(), starved_ms.end());
392         double sum = std::accumulate(starved_ms.begin(), starved_ms.end(), 0);
393         SkDebugf("starved min %gms, med %gms, avg %gms, max %gms, sum %gms, first %gms (%s)\n",
394                  starved_ms[0], starved_ms[frame_count/2], sum/frame_count, starved_ms.back(), sum,
395                  first, first == starved_ms.back() ? "ok" : "BAD");
396     }
397 #endif
398     tg.wait();
399 
400     std::sort(frames_ms.begin(), frames_ms.end());
401     double sum = std::accumulate(frames_ms.begin(), frames_ms.end(), 0);
402     SkDebugf("frame time min %gms, med %gms, avg %gms, max %gms, sum %gms\n",
403              frames_ms[0], frames_ms[frame_count/2], sum/frame_count, frames_ms.back(), sum);
404     return 0;
405 }
406