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