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
36static DEFINE_string2(input    , i, nullptr, "Input .json file.");
37static DEFINE_string2(writePath, w, nullptr, "Output directory.  Frames are names [0-9]{6}.png.");
38static DEFINE_string2(format   , f, "png"  , formats_help);
39
40static DEFINE_double(t0,    0, "Timeline start [0..1].");
41static DEFINE_double(t1,    1, "Timeline stop [0..1].");
42static DEFINE_double(fps,   0, "Decode frames per second (default is animation native fps).");
43
44static DEFINE_int(width , 800, "Render width.");
45static DEFINE_int(height, 600, "Render height.");
46static DEFINE_int(threads,  0, "Number of worker threads (0 -> cores count).");
47
48namespace {
49
50static constexpr SkColor kClearColor = SK_ColorWHITE;
51
52std::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
63class Sink {
64public:
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
74class PNGSink final : public Sink {
75public:
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
86private:
87    PNGSink(sk_sp<SkSurface> surface, const SkMatrix& scale_matrix)
88        : fSurface(std::move(surface)) {
89        fSurface->getCanvas()->concat(scale_matrix);
90    }
91
92    SkCanvas* beginFrame(size_t) override {
93        auto* canvas = fSurface->getCanvas();
94        canvas->clear(kClearColor);
95        return canvas;
96    }
97
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
118class SKPSink final : public Sink {
119public:
120    static std::unique_ptr<Sink> Make(const SkMatrix& scale_matrix) {
121        return std::unique_ptr<Sink>(new SKPSink(scale_matrix));
122    }
123
124private:
125    explicit SKPSink(const SkMatrix& scale_matrix)
126        : fScaleMatrix(scale_matrix) {}
127
128    SkCanvas* beginFrame(size_t) override {
129        auto canvas = fRecorder.beginRecording(FLAGS_width, FLAGS_height);
130        canvas->concat(fScaleMatrix);
131        return canvas;
132    }
133
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
148class NullSink final : public Sink {
149public:
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
160private:
161    NullSink(sk_sp<SkSurface> surface, const SkMatrix& scale_matrix)
162        : fSurface(std::move(surface)) {
163        fSurface->getCanvas()->concat(scale_matrix);
164    }
165
166    SkCanvas* beginFrame(size_t) override {
167        auto* canvas = fSurface->getCanvas();
168        canvas->clear(kClearColor);
169        return canvas;
170    }
171
172    bool endFrame(size_t) override {
173        return true;
174    }
175
176    const sk_sp<SkSurface> fSurface;
177};
178
179static std::vector<std::promise<sk_sp<SkImage>>> gMP4Frames;
180
181struct MP4Sink final : public Sink {
182    explicit MP4Sink(const SkMatrix& scale_matrix)
183        : fSurface(SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height)) {
184        fSurface->getCanvas()->concat(scale_matrix);
185    }
186
187    SkCanvas* beginFrame(size_t) override {
188        SkCanvas* canvas = fSurface->getCanvas();
189        canvas->clear(kClearColor);
190        return canvas;
191    }
192
193    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
204class Logger final : public skottie::Logger {
205public:
206    struct LogEntry {
207        SkString fMessage,
208                 fJSON;
209    };
210
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
216    void report() const {
217        SkDebugf("Animation loaded with %zu error%s, %zu 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
232private:
233    std::vector<LogEntry> fErrors,
234                          fWarnings;
235};
236
237std::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
249extern bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental;
250
251int 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        thread_local static auto* anim =
340                skottie::Animation::Builder()
341                    .setResourceProvider(rp)
342                    .setPrecompInterceptor(precomp_interceptor)
343                    .make(static_cast<const char*>(data->data()), data->size())
344                    .release();
345        thread_local static auto* sink = MakeSink(FLAGS_format[0], scale_matrix).release();
346
347        if (sink && anim) {
348            anim->seekFrame(frame0 + i * fps_scale);
349            anim->render(sink->beginFrame(i));
350            sink->endFrame(i);
351        }
352
353        frames_ms[i] = ms_since(start);
354    });
355
356#if defined(HAVE_VIDEO_ENCODER)
357    if (FLAGS_format.contains("mp4")) {
358        SkVideoEncoder enc;
359        if (!enc.beginRecording({FLAGS_width, FLAGS_height}, fps)) {
360            SkDEBUGF("Invalid video stream configuration.\n");
361            return -1;
362        }
363
364        std::vector<double> starved_ms;
365        for (std::promise<sk_sp<SkImage>>& frame : gMP4Frames) {
366            const auto start = std::chrono::steady_clock::now();
367            sk_sp<SkImage> img = frame.get_future().get();
368            starved_ms.push_back(ms_since(start));
369
370            SkPixmap pm;
371            SkAssertResult(img->peekPixels(&pm));
372            enc.addFrame(pm);
373        }
374        sk_sp<SkData> mp4 = enc.endRecording();
375
376        SkFILEWStream{FLAGS_writePath[0]}
377            .write(mp4->data(), mp4->size());
378
379        // If everything's going well, the first frame should account for the most,
380        // and ideally nearly all, starvation.
381        double first = starved_ms[0];
382        std::sort(starved_ms.begin(), starved_ms.end());
383        double sum = std::accumulate(starved_ms.begin(), starved_ms.end(), 0);
384        SkDebugf("starved min %gms, med %gms, avg %gms, max %gms, sum %gms, first %gms (%s)\n",
385                 starved_ms[0], starved_ms[frame_count/2], sum/frame_count, starved_ms.back(), sum,
386                 first, first == starved_ms.back() ? "ok" : "BAD");
387    }
388#endif
389    tg.wait();
390
391    std::sort(frames_ms.begin(), frames_ms.end());
392    double sum = std::accumulate(frames_ms.begin(), frames_ms.end(), 0);
393    SkDebugf("frame time min %gms, med %gms, avg %gms, max %gms, sum %gms\n",
394             frames_ms[0], frames_ms[frame_count/2], sum/frame_count, frames_ms.back(), sum);
395    return 0;
396}
397