• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/base/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(CPU_ONLY)
30 #include "include/gpu/GrContextOptions.h"
31 #include "tools/gpu/GrContextFactory.h"
32 #endif
33 
34 #if defined(HAVE_VIDEO_ENCODER)
35     #include "experimental/ffmpeg/SkVideoEncoder.h"
36     const char* formats_help = "Output format (png, skp, mp4, or null)";
37 #else
38     const char* formats_help = "Output format (png, skp, or null)";
39 #endif
40 
41 static DEFINE_string2(input    , i, nullptr, "Input .json file.");
42 static DEFINE_string2(writePath, w, nullptr, "Output directory.  Frames are names [0-9]{6}.png.");
43 static DEFINE_string2(format   , f, "png"  , formats_help);
44 
45 static DEFINE_double(t0,    0, "Timeline start [0..1].");
46 static DEFINE_double(t1,    1, "Timeline stop [0..1].");
47 static DEFINE_double(fps,   0, "Decode frames per second (default is animation native fps).");
48 
49 static DEFINE_int(width , 800, "Render width.");
50 static DEFINE_int(height, 600, "Render height.");
51 static DEFINE_int(threads,  0, "Number of worker threads (0 -> cores count).");
52 
53 static DEFINE_bool2(gpu, g, false, "Enable GPU rasterization.");
54 
55 namespace {
56 
57 static constexpr SkColor kClearColor = SK_ColorWHITE;
58 
59 enum class OutputFormat {
60     kPNG,
61     kSKP,
62     kNull,
63     kMP4,
64 };
65 
66 
ms_since(std::chrono::steady_clock::time_point start)67 auto ms_since(std::chrono::steady_clock::time_point start) {
68     const auto elapsed = std::chrono::steady_clock::now() - start;
69     return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
70 }
71 
make_file_stream(size_t frame_index,const char * extension)72 std::unique_ptr<SkFILEWStream> make_file_stream(size_t frame_index, const char* extension) {
73     const auto file = SkStringPrintf("0%06zu.%s", frame_index, extension);
74     const auto path = SkOSPath::Join(FLAGS_writePath[0], file.c_str());
75 
76     auto stream = std::make_unique<SkFILEWStream>(path.c_str());
77 
78     return stream->isValid() ? std::move(stream) : nullptr;
79 }
80 
81 class FrameSink {
82 public:
83     virtual ~FrameSink() = default;
84 
85     static std::unique_ptr<FrameSink> Make(OutputFormat fmt, size_t frame_count);
86 
87     virtual void writeFrame(sk_sp<SkImage> frame, size_t frame_index) = 0;
88 
finalize(double fps)89     virtual void finalize(double fps) {}
90 
91 protected:
92     FrameSink() = default;
93 
94 private:
95     FrameSink(const FrameSink&)            = delete;
96     FrameSink& operator=(const FrameSink&) = delete;
97 };
98 
99 class PNGSink final : public FrameSink {
100 public:
writeFrame(sk_sp<SkImage> frame,size_t frame_index)101     void writeFrame(sk_sp<SkImage> frame, size_t frame_index) override {
102         auto stream = make_file_stream(frame_index, "png");
103 
104         if (!frame || !stream) {
105             return;
106         }
107 
108         // Set encoding options to favor speed over size.
109         SkPngEncoder::Options options;
110         options.fZLibLevel   = 1;
111         options.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
112 
113         SkPixmap pixmap;
114         SkAssertResult(frame->peekPixels(&pixmap));
115 
116         SkPngEncoder::Encode(stream.get(), pixmap, options);
117     }
118 };
119 
120 class NullSink final : public FrameSink {
121 public:
writeFrame(sk_sp<SkImage>,size_t)122     void writeFrame(sk_sp<SkImage>, size_t) override {}
123 };
124 
125 #if defined(HAVE_VIDEO_ENCODER)
126 class MP4Sink final : public FrameSink {
127 public:
MP4Sink(size_t frame_count)128     explicit MP4Sink(size_t frame_count) {
129         fFrames.resize(frame_count);
130     }
131 
writeFrame(sk_sp<SkImage> frame,size_t frame_index)132     void writeFrame(sk_sp<SkImage> frame, size_t frame_index) override {
133         fFrames[frame_index].set_value(std::move(frame));
134     }
135 
finalize(double fps)136     void finalize(double fps) override {
137         SkVideoEncoder encoder;
138         if (!encoder.beginRecording({FLAGS_width, FLAGS_height}, sk_double_round2int(fps))) {
139             fprintf(stderr, "Invalid video stream configuration.\n");
140         }
141 
142         std::vector<double> starved_ms;
143         starved_ms.reserve(fFrames.size());
144 
145         for (auto& frame_promise : fFrames) {
146             const auto start = std::chrono::steady_clock::now();
147             auto frame = frame_promise.get_future().get();
148             starved_ms.push_back(ms_since(start));
149 
150             if (!frame) continue;
151 
152             SkPixmap pixmap;
153             SkAssertResult(frame->peekPixels(&pixmap));
154             encoder.addFrame(pixmap);
155         }
156 
157         auto mp4 = encoder.endRecording();
158 
159         SkFILEWStream{FLAGS_writePath[0]}
160             .write(mp4->data(), mp4->size());
161 
162         // If everything's going well, the first frame should account for the most,
163         // and ideally nearly all, starvation.
164         double first = starved_ms[0];
165         std::sort(starved_ms.begin(), starved_ms.end());
166         double sum = std::accumulate(starved_ms.begin(), starved_ms.end(), 0);
167         printf("Encoder starved stats: "
168                "min %gms, med %gms, avg %gms, max %gms, sum %gms, first %gms (%s)\n",
169                starved_ms[0], starved_ms[fFrames.size()/2], sum/fFrames.size(), starved_ms.back(),
170                sum, first, first == starved_ms.back() ? "ok" : "BAD");
171 
172     }
173 
174     std::vector<std::promise<sk_sp<SkImage>>> fFrames;
175 };
176 #endif // HAVE_VIDEO_ENCODER
177 
Make(OutputFormat fmt,size_t frame_count)178 std::unique_ptr<FrameSink> FrameSink::Make(OutputFormat fmt, size_t frame_count) {
179     switch (fmt) {
180     case OutputFormat::kPNG:
181         return std::make_unique<PNGSink>();
182     case OutputFormat::kSKP:
183         // The SKP generator does not use a sink.
184         [[fallthrough]];
185     case OutputFormat::kNull:
186         return std::make_unique<NullSink>();
187     case OutputFormat::kMP4:
188 #if defined(HAVE_VIDEO_ENCODER)
189         return std::make_unique<MP4Sink>(frame_count);
190 #else
191         return nullptr;
192 #endif
193     }
194 
195     SkUNREACHABLE;
196 }
197 
198 class FrameGenerator {
199 public:
200     virtual ~FrameGenerator() = default;
201 
202     static std::unique_ptr<FrameGenerator> Make(FrameSink*, OutputFormat, const SkMatrix&);
203 
generateFrame(const skottie::Animation *,size_t frame_index)204     virtual void generateFrame(const skottie::Animation*, size_t frame_index) {}
205 
206 protected:
FrameGenerator(FrameSink * sink)207     explicit FrameGenerator(FrameSink* sink) : fSink(sink) {}
208 
209     FrameSink* fSink;
210 
211 private:
212     FrameGenerator(const FrameGenerator&)            = delete;
213     FrameGenerator& operator=(const FrameGenerator&) = delete;
214 };
215 
216 class CPUGenerator final : public FrameGenerator {
217 public:
218 #if defined(GPU_ONLY)
Make(FrameSink * sink,const SkMatrix & matrix)219     static std::unique_ptr<FrameGenerator> Make(FrameSink* sink, const SkMatrix& matrix) {
220         return nullptr;
221     }
222 #else
223     static std::unique_ptr<FrameGenerator> Make(FrameSink* sink, const SkMatrix& matrix) {
224         auto surface = SkSurface::MakeRasterN32Premul(FLAGS_width, FLAGS_height);
225         if (!surface) {
226             SkDebugf("Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height);
227             return nullptr;
228         }
229 
230         return std::unique_ptr<FrameGenerator>(new CPUGenerator(sink, std::move(surface), matrix));
231     }
232 
233     void generateFrame(const skottie::Animation* anim, size_t frame_index) override {
234         fSurface->getCanvas()->clear(kClearColor);
235         anim->render(fSurface->getCanvas());
236 
237         fSink->writeFrame(fSurface->makeImageSnapshot(), frame_index);
238     }
239 
240 private:
241     CPUGenerator(FrameSink* sink, sk_sp<SkSurface> surface, const SkMatrix& scale_matrix)
242         : FrameGenerator(sink)
243         , fSurface(std::move(surface))
244     {
245         fSurface->getCanvas()->concat(scale_matrix);
246     }
247 
248     const sk_sp<SkSurface> fSurface;
249 #endif // !GPU_ONLY
250 };
251 
252 class SKPGenerator final : public FrameGenerator {
253 public:
254 #if defined(CPU_ONLY) || defined(GPU_ONLY)
Make(FrameSink * sink,const SkMatrix & matrix)255     static std::unique_ptr<FrameGenerator> Make(FrameSink* sink, const SkMatrix& matrix) {
256         return nullptr;
257     }
258 #else
259     static std::unique_ptr<FrameGenerator> Make(FrameSink* sink, const SkMatrix& scale_matrix) {
260         return std::unique_ptr<FrameGenerator>(new SKPGenerator(sink, scale_matrix));
261     }
262 
263     void generateFrame(const skottie::Animation* anim, size_t frame_index) override {
264         auto* canvas = fRecorder.beginRecording(FLAGS_width, FLAGS_height);
265         canvas->concat(fScaleMatrix);
266         anim->render(canvas);
267 
268         auto frame  = fRecorder.finishRecordingAsPicture();
269         auto stream = make_file_stream(frame_index, "skp");
270 
271         if (frame && stream) {
272             frame->serialize(stream.get());
273         }
274     }
275 
276 private:
277     SKPGenerator(FrameSink* sink, const SkMatrix& scale_matrix)
278         : FrameGenerator(sink)
279         , fScaleMatrix(scale_matrix)
280     {}
281 
282     const SkMatrix    fScaleMatrix;
283     SkPictureRecorder fRecorder;
284 #endif // !CPU_ONLY && !GPU_ONLY
285 };
286 
287 class GPUGenerator final : public FrameGenerator {
288 public:
289 #if defined(CPU_ONLY)
Make(FrameSink * sink,const SkMatrix & matrix)290     static std::unique_ptr<FrameGenerator> Make(FrameSink* sink, const SkMatrix& matrix) {
291         return nullptr;
292     }
293 #else
294     static std::unique_ptr<FrameGenerator> Make(FrameSink* sink, const SkMatrix& matrix) {
295         auto gpu_generator = std::unique_ptr<GPUGenerator>(new GPUGenerator(sink, matrix));
296 
297         return gpu_generator->isValid()
298                 ? std::unique_ptr<FrameGenerator>(gpu_generator.release())
299                 : nullptr;
300     }
301 
302     ~GPUGenerator() override {
303         // ensure all pending reads are completed
304         fCtx->flushAndSubmit(true);
305     }
306 
307     void generateFrame(const skottie::Animation* anim, size_t frame_index) override {
308         fSurface->getCanvas()->clear(kClearColor);
309         anim->render(fSurface->getCanvas());
310 
311         auto rec = std::make_unique<AsyncRec>(fSink, frame_index);
312         fSurface->asyncRescaleAndReadPixels(SkImageInfo::MakeN32Premul(FLAGS_width, FLAGS_height),
313                                             {0, 0, FLAGS_width, FLAGS_height},
314                                             SkSurface::RescaleGamma::kSrc,
315                                             SkImage::RescaleMode::kNearest,
316                                             AsyncCallback, rec.release());
317 
318         fCtx->submit();
319     }
320 
321 private:
322     GPUGenerator(FrameSink* sink, const SkMatrix& matrix)
323         : FrameGenerator(sink)
324     {
325         fCtx = fFactory.getContextInfo(sk_gpu_test::GrContextFactory::kGL_ContextType)
326                            .directContext();
327         fSurface =
328                 SkSurface::MakeRenderTarget(fCtx,
329                                             skgpu::Budgeted::kNo,
330                                             SkImageInfo::MakeN32Premul(FLAGS_width, FLAGS_height),
331                                             0,
332                                             GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
333                                             nullptr);
334         if (fSurface) {
335             fSurface->getCanvas()->concat(matrix);
336         } else {
337             fprintf(stderr, "Could not initialize GL context.\n");
338         }
339     }
340 
341     bool isValid() const { return !!fSurface; }
342 
343     struct AsyncRec {
344         FrameSink* sink;
345         size_t     index;
346 
347         AsyncRec(FrameSink* sink, size_t index) : sink(sink), index(index) {}
348     };
349 
350     static void AsyncCallback(SkSurface::ReadPixelsContext ctx,
351                               std::unique_ptr<const SkSurface::AsyncReadResult> result) {
352         std::unique_ptr<const AsyncRec> rec(reinterpret_cast<const AsyncRec*>(ctx));
353         if (result && result->count() == 1) {
354             SkPixmap pm(SkImageInfo::MakeN32Premul(FLAGS_width, FLAGS_height),
355                         result->data(0), result->rowBytes(0));
356 
357             auto release_proc = [](const void*, SkImage::ReleaseContext ctx) {
358                 std::unique_ptr<const SkSurface::AsyncReadResult>
359                         adopted(reinterpret_cast<const SkSurface::AsyncReadResult*>(ctx));
360             };
361 
362             auto frame_image = SkImage::MakeFromRaster(pm, release_proc, (void*)result.release());
363 
364             rec->sink->writeFrame(std::move(frame_image), rec->index);
365         }
366     }
367 
368     sk_gpu_test::GrContextFactory fFactory;
369     GrDirectContext*              fCtx;
370     sk_sp<SkSurface>              fSurface;
371 #endif // !CPU_ONLY
372 };
373 
Make(FrameSink * sink,OutputFormat fmt,const SkMatrix & matrix)374 std::unique_ptr<FrameGenerator> FrameGenerator::Make(FrameSink* sink,
375                                                      OutputFormat fmt,
376                                                      const SkMatrix& matrix) {
377     if (fmt == OutputFormat::kSKP) {
378         return SKPGenerator::Make(sink, matrix);
379     }
380 
381     return FLAGS_gpu
382             ? GPUGenerator::Make(sink, matrix)
383             : CPUGenerator::Make(sink, matrix);
384 }
385 
386 class Logger final : public skottie::Logger {
387 public:
388     struct LogEntry {
389         SkString fMessage,
390                  fJSON;
391     };
392 
log(skottie::Logger::Level lvl,const char message[],const char json[])393     void log(skottie::Logger::Level lvl, const char message[], const char json[]) override {
394         auto& log = lvl == skottie::Logger::Level::kError ? fErrors : fWarnings;
395         log.push_back({ SkString(message), json ? SkString(json) : SkString() });
396     }
397 
report() const398     void report() const {
399         SkDebugf("Animation loaded with %zu error%s, %zu warning%s.\n",
400                  fErrors.size(), fErrors.size() == 1 ? "" : "s",
401                  fWarnings.size(), fWarnings.size() == 1 ? "" : "s");
402 
403         const auto& show = [](const LogEntry& log, const char prefix[]) {
404             SkDebugf("%s%s", prefix, log.fMessage.c_str());
405             if (!log.fJSON.isEmpty())
406                 SkDebugf(" : %s", log.fJSON.c_str());
407             SkDebugf("\n");
408         };
409 
410         for (const auto& err : fErrors)   show(err, "  !! ");
411         for (const auto& wrn : fWarnings) show(wrn, "  ?? ");
412     }
413 
414 private:
415     std::vector<LogEntry> fErrors,
416                           fWarnings;
417 };
418 
419 } // namespace
420 
421 extern bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental;
422 
main(int argc,char ** argv)423 int main(int argc, char** argv) {
424     gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = true;
425     CommandLineFlags::Parse(argc, argv);
426     SkAutoGraphics ag;
427 
428     if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) {
429         SkDebugf("Missing required 'input' and 'writePath' args.\n");
430         return 1;
431     }
432 
433     OutputFormat fmt;
434     if (0 == strcmp(FLAGS_format[0],  "png")) {
435         fmt = OutputFormat::kPNG;
436     } else if (0 == strcmp(FLAGS_format[0],  "skp")) {
437         fmt = OutputFormat::kSKP;
438     }  else if (0 == strcmp(FLAGS_format[0], "null")) {
439         fmt = OutputFormat::kNull;
440 #if defined(HAVE_VIDEO_ENCODER)
441     } else if (0 == strcmp(FLAGS_format[0],  "mp4")) {
442         fmt = OutputFormat::kMP4;
443 #endif
444     } else {
445         fprintf(stderr, "Unknown format: %s\n", FLAGS_format[0]);
446         return 1;
447     }
448 
449     if (fmt != OutputFormat::kMP4 && !sk_mkdir(FLAGS_writePath[0])) {
450         return 1;
451     }
452 
453     auto logger = sk_make_sp<Logger>();
454     auto     rp = skresources::CachingResourceProvider::Make(
455                     skresources::DataURIResourceProviderProxy::Make(
456                       skresources::FileResourceProvider::Make(SkOSPath::Dirname(FLAGS_input[0]),
457                                                                 /*predecode=*/true),
458                       /*predecode=*/true));
459     auto data   = SkData::MakeFromFileName(FLAGS_input[0]);
460     auto precomp_interceptor =
461             sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(rp, "__");
462 
463     if (!data) {
464         SkDebugf("Could not load %s.\n", FLAGS_input[0]);
465         return 1;
466     }
467 
468     // Instantiate an animation on the main thread for two reasons:
469     //   - we need to know its duration upfront
470     //   - we want to only report parsing errors once
471     auto anim = skottie::Animation::Builder()
472             .setLogger(logger)
473             .setResourceProvider(rp)
474             .make(static_cast<const char*>(data->data()), data->size());
475     if (!anim) {
476         SkDebugf("Could not parse animation: '%s'.\n", FLAGS_input[0]);
477         return 1;
478     }
479 
480     const auto scale_matrix = SkMatrix::RectToRect(SkRect::MakeSize(anim->size()),
481                                                    SkRect::MakeIWH(FLAGS_width, FLAGS_height),
482                                                    SkMatrix::kCenter_ScaleToFit);
483     logger->report();
484 
485     const auto t0 = SkTPin(FLAGS_t0, 0.0, 1.0),
486                t1 = SkTPin(FLAGS_t1,  t0, 1.0),
487        native_fps = anim->fps(),
488            frame0 = anim->duration() * t0 * native_fps,
489          duration = anim->duration() * (t1 - t0);
490 
491     double fps = FLAGS_fps > 0 ? FLAGS_fps : native_fps;
492     if (fps <= 0) {
493         SkDebugf("Invalid fps: %f.\n", fps);
494         return 1;
495     }
496 
497     auto frame_count = static_cast<int>(duration * fps);
498     static constexpr int kMaxFrames = 10000;
499     if (frame_count > kMaxFrames) {
500         frame_count = kMaxFrames;
501         fps = frame_count / duration;
502     }
503     const auto fps_scale = native_fps / fps;
504 
505     printf("Rendering %f seconds (%d frames @%f fps).\n", duration, frame_count, fps);
506 
507     const auto sink = FrameSink::Make(fmt, frame_count);
508 
509     std::vector<double> frames_ms(frame_count);
510 
511     const auto thread_count = FLAGS_gpu ? 0 : FLAGS_threads - 1;
512     SkTaskGroup::Enabler enabler(thread_count);
513 
514     SkTaskGroup tg;
515     {
516         // Depending on type (gpu vs. everything else), we use either a single generator
517         // or one generator per worker thread, respectively.
518         // Scoping is important for the single generator case because we want its destructor to
519         // flush out any pending async operations.
520         std::unique_ptr<FrameGenerator> singleton_generator;
521         if (FLAGS_gpu) {
522             singleton_generator = FrameGenerator::Make(sink.get(), fmt, scale_matrix);
523         }
524 
525         tg.batch(frame_count, [&](int i) {
526             // SkTaskGroup::Enabler creates a LIFO work pool,
527             // but we want our early frames to start first.
528             i = frame_count - 1 - i;
529 
530             const auto start = std::chrono::steady_clock::now();
531             thread_local static auto* anim =
532                     skottie::Animation::Builder()
533                         .setResourceProvider(rp)
534                         .setPrecompInterceptor(precomp_interceptor)
535                         .make(static_cast<const char*>(data->data()), data->size())
536                         .release();
537             thread_local static auto* gen = singleton_generator
538                     ? singleton_generator.get()
539                     : FrameGenerator::Make(sink.get(), fmt, scale_matrix).release();
540 
541             if (gen && anim) {
542                 anim->seekFrame(frame0 + i * fps_scale);
543                 gen->generateFrame(anim, SkToSizeT(i));
544             } else {
545                 sink->writeFrame(nullptr, SkToSizeT(i));
546             }
547 
548             frames_ms[i] = ms_since(start);
549         });
550     }
551 
552     sink->finalize(fps);
553     tg.wait();
554 
555 
556     std::sort(frames_ms.begin(), frames_ms.end());
557     double sum = std::accumulate(frames_ms.begin(), frames_ms.end(), 0);
558     printf("Frame time stats: min %gms, med %gms, avg %gms, max %gms, sum %gms\n",
559            frames_ms[0], frames_ms[frame_count/2], sum/frame_count, frames_ms.back(), sum);
560 
561     return 0;
562 }
563